diff --git a/.agents/skills/docs-current-state/SKILL.md b/.agents/skills/docs-current-state/SKILL.md index d4bcbf19..b1bb63ff 100644 --- a/.agents/skills/docs-current-state/SKILL.md +++ b/.agents/skills/docs-current-state/SKILL.md @@ -1,21 +1,43 @@ --- name: docs-current-state description: >- - Update project READMEs and Markdown guides as current-state truth only. - Use when writing or revising README.md, docs/, examples/**/README.md, or any - project guide after API, layout, architecture, or dependency changes; when - removing stale docs; or when the user asks to refine documentation. + Update project READMEs and Markdown/MDX guides as current-state truth for + end users and SDK callers (not SDK builders). Use when writing or revising + README.md, docs/, examples/**/README.md, sdks/**/README.md, + website/content/docs/**, or any product guide after API, layout, + architecture, or dependency changes; when removing stale docs; or when the + user asks to refine documentation. --- -# Docs: current state only +# Docs: current state, SDK-user audience -Write documentation that matches the repository **as it exists now**. Pair with -`crafting-effective-readmes` for README structure, `writing-clearly-and-concisely` -for prose, and `golang-documentation` for Go/doc-comment work. Use -`baoyu-format-markdown` only when the user asks to format or beautify markdown -layout without rewriting meaning. +Write documentation that matches the repository **as it exists now**, for +**people who call the SDK**. Pair with `crafting-effective-readmes` for README +structure, `writing-clearly-and-concisely` for prose, and +`golang-documentation` for Go/doc-comment work. Use `baoyu-format-markdown` +only when the user asks to format or beautify markdown layout without +rewriting meaning. -This skill aligns with `.cursor/rules/docs-current-state.mdc` and `AGENTS.md`. +This skill aligns with `.cursor/rules/docs-current-state.mdc`, +`.cursor/rules/docs-sdk-user-audience.mdc`, and `AGENTS.md`. + +## Audience + +Primary readers are application developers integrating `@sandbox-kit/*` or the +Go sandbox modules. Write like a technical documentation engineer for that +audience. + +| For SDK users | Not for SDK builders | +| ----------------------------------- | --------------------------------------------------------- | +| What to call, pass, and expect back | Mapping layers, wire shapes, adapter internals | +| Provider-specific usage differences | Inventory YAML, proto regen, contributor workflows | +| Outcomes on the handle (`state`, …) | “Unified metadata”, ignored-option plumbing | +| Unsupported → say so + alternative | Negated field catalogs unless they prevent a real mistake | + +Omit options the provider does not use from examples. If you must mention an +unused option, say the provider does not use it—do not say it is “accepted and +ignored.” Prefer listing fields the caller receives over listing fields that +stay empty. ## Rules @@ -26,27 +48,31 @@ This skill aligns with `.cursor/rules/docs-current-state.mdc` and `AGENTS.md`. 3. **Replace stale content.** Delete outdated sections, examples, commands, and claims. Do not keep deprecated paths "for context." 4. **Update with the code change.** When APIs, layout, architecture, examples, - or dependencies change, edit the relevant Markdown in the same task. + or dependencies change, edit the relevant Markdown/MDX in the same task. 5. **Stay silent in chat.** Do not announce "I also updated the README" unless the user asked about docs. PR/commit summaries may mention docs when that is part of the deliverable. 6. **Verify.** Check links and commands against the repo. Match examples to the implemented API. Prefer one catalog of truth (`examples/README.md`) over duplicated incomplete tables. +7. **Provider- and language-specific.** When behavior differs, say so under the + right provider/language section—do not paper over with generic wording. ## Scope | Include | Exclude | | --------------------------------------------- | --------------------------------------------- | | Root/`docs/`/`examples/**` READMEs and guides | CHANGELOG, commit/PR history | -| SDK and tooling READMEs that describe usage | Skill packages under `.agents/skills/` | +| SDK package READMEs that describe usage | Skill packages under `.agents/skills/` | +| `website/content/docs/**` product + SDK pages | Contributor-only architecture unless asked | | Out-of-scope notes stated as present facts | Roadmap or "planned / future work" narratives | ## Workflow -1. Identify which Markdown files describe the changed behavior. -2. Read those files and the matching code/examples. -3. Rewrite so each file is a clean current description; remove contradictions. +1. Identify which Markdown/MDX files describe the changed behavior. +2. Read those files and the matching public API / examples. +3. Rewrite so each file is a clean current description for an SDK caller; + remove contradictions and builder-only detail. 4. For README structure or audience fit, follow `crafting-effective-readmes`. 5. For sentence-level clarity, follow `writing-clearly-and-concisely`. 6. Do not prompt about incidental README edits after feature work unless diff --git a/.cursor/rules/docs-current-state.mdc b/.cursor/rules/docs-current-state.mdc index e0ebf8b3..51d6b673 100644 --- a/.cursor/rules/docs-current-state.mdc +++ b/.cursor/rules/docs-current-state.mdc @@ -1,5 +1,5 @@ --- -description: Keep docs and READMEs as current-state truth; update silently; remove outdated content +description: Keep docs and READMEs as current-state truth for SDK users; update silently; remove outdated content alwaysApply: true --- @@ -8,6 +8,9 @@ alwaysApply: true When APIs, layout, architecture, examples, or dependencies change, update the relevant README and project Markdown as part of the same work. +Audience for product and SDK docs is **end users and SDK callers**, not SDK +builders. Follow `.cursor/rules/docs-sdk-user-audience.mdc` for voice. + ## Write what is true now - Describe the project as it exists **today**. Present tense. @@ -30,6 +33,7 @@ relevant README and project Markdown as part of the same work. ## Scope -- Applies to `README.md`, `docs/`, `examples/**/README.md`, and other project - Markdown that describes current behavior. +- Applies to `README.md`, `docs/`, `examples/**/README.md`, + `website/content/docs/**`, SDK package READMEs, and other project Markdown + that describes current behavior. - Does not replace CHANGELOG or commit/PR history; keep those elsewhere. diff --git a/.cursor/rules/docs-sdk-user-audience.mdc b/.cursor/rules/docs-sdk-user-audience.mdc new file mode 100644 index 00000000..0ed94cb8 --- /dev/null +++ b/.cursor/rules/docs-sdk-user-audience.mdc @@ -0,0 +1,51 @@ +--- +description: Write product and SDK docs for end users and SDK callers, not SDK builders +alwaysApply: true +--- + +# Docs audience: SDK users, not builders + +Write README, guides, website docs, and SDK reference as a technical +documentation engineer for **people who call the SDK** (application +developers). Not for people who implement providers or maintain this repo. + +## Voice + +- Lead with what to call, what to pass, and what you get back. +- Prefer short how-to prose and working snippets over internal design notes. +- Keep provider- and language-specific behavior explicit when it differs. +- Present tense. Current behavior only (see `docs-current-state`). + +## Do + +- Name the public method and show a realistic call. +- Describe outcomes the caller can use (`state`, `terminal`, `exitCode`, …). +- Say what the caller must supply (credentials, name, image, region). +- Note unsupported operations as “not available” / “not supported” for that + provider, with the alternative when there is one. + +## Do not + +- Explain Kit internals, wire formats, mapping layers, or “unified” plumbing. +- Document ignored options as acceptance theater (“`wait` is accepted and + ignored”). Omit unused options from examples; if needed, say the provider + does not wait / does not use that option. +- Surface native `providerState` strings unless the caller must read them. +- Negate field catalogs (“does not fill `mode`”) unless that prevents a + real mistake; prefer listing what the caller actually receives. +- Write for SDK maintainers (inventory YAML, proto regeneration, adapter + shapes) in user-facing docs. Keep that in `AGENTS.md` / contributor docs. + +## Examples + +```md + + +Deletes and returns unified Deleted metadata (`providerState: "TERMINATED"`). +`wait` is accepted and ignored. + + + +Deletes the sandbox. On success, `info.state` is `deleted` and `terminal` is +true. Pass `wait: true` to wait until removal finishes. +``` diff --git a/AGENTS.md b/AGENTS.md index 7ec86a4c..4344d7d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,9 +82,13 @@ Keep examples aligned with the implemented API. Label test doubles and local verification limits clearly. Verify changed examples before handoff and update `examples/README.md` when adding a language. -Update project Markdown whenever APIs, layout, architecture, or dependencies -change. Write docs as current-state truth: present tense, remove outdated -sections, and verify links and commands against the repository. +Update project Markdown and `website/content/docs/` whenever APIs, layout, +architecture, or dependencies change. Write docs as current-state truth for +**end users and SDK callers** (not SDK builders): present tense, what to call +and what you get back, provider- and language-specific when behavior differs. +Remove outdated sections, and verify links and commands against the +repository. See `.cursor/rules/docs-sdk-user-audience.mdc` and +`.cursor/skills/docs-current-state/`. ## Development tooling diff --git a/README.md b/README.md index a6a3cebc..79943467 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,20 @@ A multi-provider SDK for creating and managing cloud sandboxes. Choose a provider, install the core SDK and its provider package, then create and manage -sandboxes through a consistent client interface. Daytona and Modal are -supported. +sandboxes through a consistent client interface. Daytona, Modal, and Blaxel are supported. ## npm -The TypeScript SDK is published on npm at version 0.1.7. Each badge counts every -download npm has recorded for that package. +TypeScript packages on npm: `@sandbox-kit/sandbox`, `@sandbox-kit/daytona`, and +`@sandbox-kit/modal` are 0.1.7; `@sandbox-kit/blaxel` is 0.1.0. Each badge shows +that package's registry version and download count. | Package | Version | Downloads | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | [`@sandbox-kit/sandbox`](https://www.npmjs.com/package/@sandbox-kit/sandbox) | [![npm](https://img.shields.io/npm/v/%40sandbox-kit%2Fsandbox)](https://www.npmjs.com/package/@sandbox-kit/sandbox) | [![downloads](https://img.shields.io/npm/d18m/%40sandbox-kit%2Fsandbox?label=downloads)](https://www.npmjs.com/package/@sandbox-kit/sandbox) | | [`@sandbox-kit/daytona`](https://www.npmjs.com/package/@sandbox-kit/daytona) | [![npm](https://img.shields.io/npm/v/%40sandbox-kit%2Fdaytona)](https://www.npmjs.com/package/@sandbox-kit/daytona) | [![downloads](https://img.shields.io/npm/d18m/%40sandbox-kit%2Fdaytona?label=downloads)](https://www.npmjs.com/package/@sandbox-kit/daytona) | | [`@sandbox-kit/modal`](https://www.npmjs.com/package/@sandbox-kit/modal) | [![npm](https://img.shields.io/npm/v/%40sandbox-kit%2Fmodal)](https://www.npmjs.com/package/@sandbox-kit/modal) | [![downloads](https://img.shields.io/npm/d18m/%40sandbox-kit%2Fmodal?label=downloads)](https://www.npmjs.com/package/@sandbox-kit/modal) | +| [`@sandbox-kit/blaxel`](https://www.npmjs.com/package/@sandbox-kit/blaxel) | [![npm](https://img.shields.io/npm/v/%40sandbox-kit%2Fblaxel)](https://www.npmjs.com/package/@sandbox-kit/blaxel) | [![downloads](https://img.shields.io/npm/d18m/%40sandbox-kit%2Fblaxel?label=downloads)](https://www.npmjs.com/package/@sandbox-kit/blaxel) | Install `@sandbox-kit/sandbox` and one provider. @@ -27,9 +28,10 @@ Requires Node.js 20, 22, or 24.15+. Install the core SDK and one provider: ```sh npm install @sandbox-kit/sandbox @sandbox-kit/daytona # or: npm install @sandbox-kit/sandbox @sandbox-kit/modal +# or: npm install @sandbox-kit/sandbox @sandbox-kit/blaxel ``` -Set `DAYTONA_API_KEY`, then create a sandbox: +Set `DAYTONA_API_KEY`, then create a Daytona sandbox: ```ts import { daytona } from "@sandbox-kit/daytona"; @@ -53,8 +55,10 @@ await sandbox.filesystem.writeFile( ); ``` -Use `await client.close()` in a `finally` block when `await using` is not -available. Timeouts are milliseconds in TypeScript. +Empty `create()` uses Daytona's default snapshot. Modal needs an image. Blaxel +needs `name`, an image, and client `region`. Use `await client.close()` in a +`finally` block when `await using` is not available. Timeouts are milliseconds +in TypeScript. ### Go @@ -64,9 +68,10 @@ Requires Go 1.26.0 or later. Install the core SDK and one provider: go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest go get github.com/sandbox-kit/kit/sdks/go/providers/daytona@latest # or: go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest +# or: go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ``` -Set `DAYTONA_API_KEY`, then create a sandbox: +Set `DAYTONA_API_KEY`, then create a Daytona sandbox: ```go package main @@ -113,15 +118,17 @@ func main() { } ``` +`Create` with a nil request uses Daytona's default snapshot. Modal needs an +image. Blaxel needs `Name`, an image, and client `Region`. + ### What gets installed -| Language | Core SDK | Providers | -| ---------- | -------------------------------------------- | ---------------------------------------------- | -| TypeScript | `@sandbox-kit/sandbox` | `@sandbox-kit/daytona`, `@sandbox-kit/modal` | -| Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` | `.../providers/daytona`, `.../providers/modal` | +| Language | Core SDK | Providers | +| ---------- | -------------------------------------------- | ---------------------------------------------------------------------- | +| TypeScript | `@sandbox-kit/sandbox` | `@sandbox-kit/daytona`, `@sandbox-kit/modal`, `@sandbox-kit/blaxel` | +| Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` | `.../providers/daytona`, `.../providers/modal`, `.../providers/blaxel` | -Install only the provider you use. The core SDK exposes the shared client -interface; the provider package connects it to the provider's native SDK. +Install only the provider you use. Closing a client or sandbox handle releases local SDK resources. It does not stop, terminate, archive, or delete the cloud sandbox. @@ -153,11 +160,16 @@ Modal needs a token pair and an existing app: [TypeScript](examples/typescript/modal/configuration/create-modal-sandbox/README.md) · [Go](examples/go/modal/configuration/create-modal-sandbox/README.md). -Creation examples provision real sandboxes. Filesystem, process, and lifecycle examples -use the ID in `.env` (stop / terminate / archive / delete can destroy that -resource; filesystem examples use the path you set; process examples run one -command). Error-handling examples -fail locally and create nothing. Skip copying `.env.example` if you already +Blaxel needs an API key, a workspace, and a client region: +[TypeScript](examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md) · +[Go](examples/go/blaxel/configuration/create-blaxel-sandbox/README.md). + +Creation examples provision real sandboxes. Filesystem, process, and lifecycle +examples use the sandbox id in `.env` (`DAYTONA_SANDBOX_ID`, `MODAL_SANDBOX_ID`, +or Blaxel `BL_SANDBOX_NAME` — Blaxel id is the create name). Stop / terminate / +archive / delete can destroy that resource; filesystem examples use the path +you set; process examples run one command. Error-handling examples fail +locally and create nothing. Skip copying `.env.example` if you already have a configured `.env`. Full catalog: [examples/README.md](examples/README.md) · @@ -165,7 +177,7 @@ Full catalog: [examples/README.md](examples/README.md) · ## Handle errors -Same portable kinds in both languages. Native causes stay on the TypeScript +The same error kinds in both languages. Native causes stay on the TypeScript `cause` property and on Go `Unwrap` / `errors.As`. Kinds do not imply safe retries. ### TypeScript @@ -192,6 +204,15 @@ function handleError(error: unknown): void { "Your account does not have permission for this operation.", ); break; + case ErrorKind.NotFound: + console.error("Sandbox or file path not found."); + break; + case ErrorKind.AlreadyExists: + console.error("Named resource already exists."); + break; + case ErrorKind.Conflict: + console.error("Current state rejects this operation."); + break; case ErrorKind.RateLimited: case ErrorKind.ResourceExhausted: console.error("The provider's rate or resource limit was reached."); @@ -233,6 +254,12 @@ func handleError(err error) { fmt.Fprintln(os.Stderr, "Check your provider credentials.") case sandbox.ErrorKindPermissionDenied: fmt.Fprintln(os.Stderr, "Your account does not have permission for this operation.") + case sandbox.ErrorKindNotFound: + fmt.Fprintln(os.Stderr, "Sandbox or file path not found.") + case sandbox.ErrorKindAlreadyExists: + fmt.Fprintln(os.Stderr, "Named resource already exists.") + case sandbox.ErrorKindConflict: + fmt.Fprintln(os.Stderr, "Current state rejects this operation.") case sandbox.ErrorKindRateLimited, sandbox.ErrorKindResourceExhausted: fmt.Fprintln(os.Stderr, "The provider's rate or resource limit was reached.") case sandbox.ErrorKindCanceled: @@ -256,7 +283,9 @@ More detail: [responses and errors](docs/responses-and-errors.md). Go helpers: `Value(v)` for optional scalars, `Duration(d)` for timeouts. - Unsupported settings fail before provider calls; quotas stay provider-enforced. - Memory and disk are MiB. Modal CPU: fractional cores from 0.125 (0.001 steps). - Daytona: whole cores and whole GiB expressed as MiB. + Daytona: whole cores and whole GiB expressed as MiB. Blaxel: set + `memoryMib` / `memory_mib` (Kit converts MiB to Blaxel's decimal MB); CPU + follows memory. Guides: [configuration](docs/configuration.md) · [creation](docs/sandbox-creation.md) · @@ -277,17 +306,17 @@ Open http://localhost:3000. Docs are organized as Quickstart, Concepts, Provider These pages are the behavior reference for the same API: -| Guide | Covers | -| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| [Client configuration](docs/configuration.md) | Auth, scope, defaults, deadlines | -| [Sandbox creation](docs/sandbox-creation.md) | Fields, units, policies | -| [Sandbox lifecycle](docs/sandbox-lifecycle.md) | Get, attach, capabilities | -| [Filesystem](docs/filesystem.md) | Read, write, copy, list, stat, mkdir, remove | -| [Process](docs/process.md) | Run a command and wait for exit code + output | -| [Providers](docs/providers.md) | Daytona and Modal | -| [Responses and errors](docs/responses-and-errors.md) | Error details and provider causes | -| [TS Daytona](sdks/typescript/providers/daytona/README.md) / [Modal](sdks/typescript/providers/modal/README.md) | TypeScript provider notes | -| [Go Daytona](sdks/go/providers/daytona/README.md) / [Modal](sdks/go/providers/modal/README.md) | Go provider notes | +| Guide | Covers | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| [Client configuration](docs/configuration.md) | Auth, scope, defaults, deadlines | +| [Sandbox creation](docs/sandbox-creation.md) | Fields, units, policies | +| [Sandbox lifecycle](docs/sandbox-lifecycle.md) | Get, attach, capabilities | +| [Filesystem](docs/filesystem.md) | Read, write, copy, list, stat, mkdir, remove | +| [Process](docs/process.md) | Run a command and wait for exit code + output | +| [Providers](docs/providers.md) | Daytona, Modal, and Blaxel | +| [Responses and errors](docs/responses-and-errors.md) | Error details and provider causes | +| [TS Daytona](sdks/typescript/providers/daytona/README.md) / [Modal](sdks/typescript/providers/modal/README.md) / [Blaxel](sdks/typescript/providers/blaxel/README.md) | TypeScript provider notes | +| [Go Daytona](sdks/go/providers/daytona/README.md) / [Modal](sdks/go/providers/modal/README.md) / [Blaxel](sdks/go/providers/blaxel/README.md) | Go provider notes | ## License diff --git a/docs/code-generation.md b/docs/code-generation.md index 3e02852a..6d385ef6 100644 --- a/docs/code-generation.md +++ b/docs/code-generation.md @@ -94,9 +94,9 @@ Portable messages come from standard protobuf generation into contract packages. SDK runtimes alias them and keep handwritten clone helpers (for example Go `clone.go` and TypeScript metadata helpers). -Provider error classification is not generated: Modal and Daytona mappers live -in handwritten `errors.go` / `errors.ts`. Unavailable fields receive no -fabricated values. +Provider error classification is not generated: Modal, Daytona, and Blaxel +mappers live in handwritten `errors.go` / `errors.ts`. Unavailable fields +receive no fabricated values. See [responses and errors](responses-and-errors.md) for the public contract, native cause preservation, and copy restrictions. @@ -121,8 +121,12 @@ field uses that field, such as `resources.cpu_limit_cores`. A constraint that names several fields uses their parent message. List indexes and oneof names are omitted. Serialization policy is separate from validation. Client representation metadata -marks credential fields `sensitive`, which excludes them from Go JSON output -without treating secrecy as a value-validity rule. +marks credential fields `sensitive`. Handwritten Go contract serialization +follows that inventory and excludes credentials from standard-library +`encoding/json` output when credentials are passed by pointer, including within +`*sandbox.Config`. Credential values passed directly to `json.Marshal` are not +redacted; do not serialize them by value. Protobuf JSON serialization also +represents every schema field and must not be used for client configuration. | Constraint | Meaning | | ------------------- | --------------------------------------------- | diff --git a/docs/configuration.md b/docs/configuration.md index eb57da33..0c472d84 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -22,20 +22,20 @@ await using client = new Client({ ``` Call `client.Close(ctx)` in Go, or `await client.close()` or `await using` in -TypeScript. That closes your connection to Daytona or Modal. The sandboxes keep +TypeScript. That closes your connection to Daytona, Modal, or Blaxel. The sandboxes keep running. You can keep using a handle. The next call returns the provider's result. Closing one handle does not close the client. ## Configuration fields -| Go / TypeScript | Purpose | -| ----------------------- | ------------------------------------------------------------------- | -| Provider / `provider` | `daytona.New()` / `daytona()`, or `modal.New()` / `modal()` | -| `Auth` / `auth` | One credential shape. Omit it to use the provider's own credentials | -| `Scope` / `scope` | App, environment, organization, or project | -| `Endpoint` / `endpoint` | Provider API URL, where the provider supports one | -| `Region` / `region` | Provider target or default placement | -| `Timeout` / `timeout` | Default deadline. Go: `sandbox.Duration`. TypeScript: milliseconds | +| Go / TypeScript | Purpose | +| ----------------------- | ---------------------------------------------------------------------------------------- | +| Provider / `provider` | `daytona.New()` / `daytona()`, `modal.New()` / `modal()`, or `blaxel.New()` / `blaxel()` | +| `Auth` / `auth` | One credential shape. Omit it to use the provider's own credentials | +| `Scope` / `scope` | App, environment, organization, project, or workspace | +| `Endpoint` / `endpoint` | Provider API URL, where the provider supports one | +| `Region` / `region` | Provider target or default placement | +| `Timeout` / `timeout` | Default deadline. Go: `sandbox.Duration`. TypeScript: milliseconds | TypeScript auth is one nested shape: `auth: { apiKey: { key } }`, `tokenPair`, `bearerToken`, or `oauthRefresh`. @@ -46,26 +46,31 @@ does not change the handle. ## Provider support -The bindings use Modal v0.11.1 and Daytona v0.225.0. - -| Setting | Modal | Daytona | -| ---------------- | ---------------------------------------------------------- | --------------------------------- | -| API key | Unsupported | `APIKey` | -| Token pair | `TokenID` and `TokenSecret` | Unsupported | -| Bearer/JWT token | Unsupported | `JWTToken`; organization required | -| OAuth refresh | Refresh token, client ID, and one client secret or JWT key | Unsupported | -| Endpoint | Unsupported | API URL | -| Region | Default sandbox region preference | Target | -| App name | Required for app lookup | Unsupported | -| Environment | Client, app, and secret lookup environment | Unsupported | -| Organization ID | Unsupported | Organization | -| Project ID | Unsupported | Unsupported | +The bindings target Modal, Daytona, and Blaxel. Pinned dependency versions live +in each provider package. + +| Setting | Modal | Daytona | Blaxel | +| ---------------- | ---------------------------------------------------------- | --------------------------------- | ------------------------------ | +| API key | Unsupported | `APIKey` | `APIKey` | +| Token pair | `TokenID` and `TokenSecret` | Unsupported | Unsupported | +| Bearer/JWT token | Unsupported | `JWTToken`; organization required | Unsupported | +| OAuth refresh | Refresh token, client ID, and one client secret or JWT key | Unsupported | Unsupported | +| Endpoint | Unsupported | API URL | Unsupported | +| Region | Default sandbox region preference | Target | Required for create | +| App name | Required for app lookup | Unsupported | Unsupported | +| Environment | Client, app, and secret lookup environment | Unsupported | Unsupported | +| Organization ID | Unsupported | Organization | Unsupported | +| Project ID | Unsupported | Unsupported | Unsupported | +| Workspace | Unsupported | Unsupported | Blaxel workspace (`BL_WORKSPACE` when omitted) | A setting the provider does not support fails before the provider client is created. Omit `auth` when the provider should use credentials it already finds in the environment. Daytona can take the organization from that environment when you use a bearer token and leave organization unset. +In TypeScript, use one Blaxel client (or one API key) per process. A second +client with different credentials replaces the first. + A remote endpoint uses HTTPS. HTTP is accepted for `localhost` or a loopback address. A URL with a username, password, or fragment is rejected. Daytona uses `endpoint` when you set it, and otherwise uses `https://app.daytona.io/api`. @@ -85,7 +90,10 @@ deadline or abort signal that arrives earlier still applies. Daytona create keeps its 60-second default when the timeout is omitted and when it is explicit zero. Daytona start, stop, pause, and delete use that 60-second default when the timeout is omitted, and treat explicit zero as no -operation timeout. +operation timeout. On Blaxel, your client/create `timeout` is how long create +may take. An edge HTTP 504 during create can leave TypeScript waiting until +the sandbox is ready or that timeout ends; Go returns a timeout when the 504 +arrives. ## Credentials diff --git a/docs/filesystem.md b/docs/filesystem.md index b95f3ebe..80ae8d57 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -1,7 +1,7 @@ # Filesystem -File calls live on the sandbox handle, next to lifecycle. Daytona and Modal -implement the same methods. Open the handle with `Create`, Daytona `Get`, or +File calls live on the sandbox handle, next to lifecycle. Daytona, Modal, and Blaxel +implement the same methods. Open the handle with `Create`, Daytona or Blaxel `Get`, or Modal `Attach`, then call `Filesystem` / `sandbox.filesystem`. | You want to | Go | TypeScript | @@ -23,8 +23,8 @@ provider call settles so the next call does not overlap it. ## Paths A path inside the sandbox is absolute: `/tmp/note.txt`. A relative path, a -blank path, or a path that contains a NUL byte is rejected before the provider -is called. The error field is `path`. +blank path, or a path that contains a NUL byte is rejected. The error field +is `path`. A path on your machine, used by copy, may be relative to the process, such as `./note.txt`. It must be non-blank and must not contain a NUL byte. A rejected @@ -56,8 +56,9 @@ omitted, the abort signal carries the client deadline. `makeDirectory`, `listDirectory`, `stat`, and `remove` use the abort signal. `1500` ends the call at 1.5 seconds. Every Go file call uses the context as the deadline, so `1500 * time.Millisecond` ends the call at 1.5 seconds. Modal -file calls use the context or the abort signal. `1500` milliseconds ends the -call at 1.5 seconds. +and Blaxel file calls use the context or the abort signal. `1500` milliseconds +ends the call at 1.5 seconds. Blaxel does not support `parents: false` +(field `parents`). ```ts await sandbox.filesystem.readFile("/tmp/note.txt", { timeout: 2_000 }); @@ -87,7 +88,9 @@ data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", nil) [Go Daytona](../examples/go/daytona/filesystem/read-daytona-file/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/read-daytona-file/README.md) · [Go Modal](../examples/go/modal/filesystem/read-modal-file/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/read-modal-file/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/read-modal-file/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/read-blaxel-file/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/read-blaxel-file/README.md). ## Write a file @@ -106,7 +109,9 @@ err := instance.Filesystem().WriteFile(ctx, "/tmp/kit-files/note.txt", []byte("h [Go Daytona](../examples/go/daytona/filesystem/write-daytona-file/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/write-daytona-file/README.md) · [Go Modal](../examples/go/modal/filesystem/write-modal-file/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/write-modal-file/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/write-modal-file/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/write-blaxel-file/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/write-blaxel-file/README.md). ## Copy a file @@ -140,34 +145,47 @@ Into the sandbox: [Go Daytona](../examples/go/daytona/filesystem/copy-file-into-daytona/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/copy-file-into-daytona/README.md) · [Go Modal](../examples/go/modal/filesystem/copy-file-into-modal/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/copy-file-into-modal/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/copy-file-into-modal/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md). Out of the sandbox: [Go Daytona](../examples/go/daytona/filesystem/copy-file-from-daytona/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/copy-file-from-daytona/README.md) · [Go Modal](../examples/go/modal/filesystem/copy-file-from-modal/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/copy-file-from-modal/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/copy-file-from-modal/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md). ## Create a directory `parents` defaults to true, so the call creates missing parents. Modal accepts a directory that already exists. Daytona returns the provider error when it -already exists. +already exists. Blaxel creates missing parents when `parents` is omitted or +true. Set `parents` to false when the parent directory must already exist. Modal then requires that parent, and returns an error if the new directory is -already there. Daytona rejects `false` before the call. The error field is +already there. Daytona and Blaxel do not support `false`. The error field is `parents`. ```ts await sandbox.filesystem.makeDirectory("/tmp/kit-files"); +``` + +```go +err := instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files", nil) +``` + +Modal accepts `parents: false`: + +```ts await sandbox.filesystem.makeDirectory("/tmp/kit-files/nested", { parents: false, }); ``` ```go -err := instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files", nil) err = instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files/nested", &sandbox.MakeDirectoryOptions{ Parents: sandbox.Value(false), }) @@ -176,7 +194,9 @@ err = instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files/nested", &sandbox [Go Daytona](../examples/go/daytona/filesystem/make-daytona-directory/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/make-daytona-directory/README.md) · [Go Modal](../examples/go/modal/filesystem/make-modal-directory/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/make-modal-directory/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/make-modal-directory/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/make-blaxel-directory/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md). ## List a directory @@ -197,15 +217,18 @@ entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp/kit-files", nil) [Go Daytona](../examples/go/daytona/filesystem/list-daytona-directory/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/list-daytona-directory/README.md) · [Go Modal](../examples/go/modal/filesystem/list-modal-directory/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/list-modal-directory/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/list-modal-directory/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/list-blaxel-directory/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md). ## 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. +`Stat` returns one path. A symlink result describes the symlink. + +On Blaxel, you get `type` (file or directory), `size`, and usually +`permissions`, `owner`, `group`, and `modifiedAt`. `name` and `path` are +whatever Blaxel reports. Modal can report a symlink. Daytona reports a file +or a directory. `mode` is an octal string when present, such as `0644`. ```ts const info = await sandbox.filesystem.stat("/tmp/note.txt"); @@ -219,7 +242,9 @@ info, err := instance.Filesystem().Stat(ctx, "/tmp/note.txt", nil) [Go Daytona](../examples/go/daytona/filesystem/stat-daytona-file/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/stat-daytona-file/README.md) · [Go Modal](../examples/go/modal/filesystem/stat-modal-file/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/stat-modal-file/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/stat-modal-file/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/stat-blaxel-file/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md). ## Delete a file or directory @@ -248,4 +273,6 @@ if err := files.Remove(ctx, "/tmp/kit-files", &sandbox.RemoveOptions{ [Go Daytona](../examples/go/daytona/filesystem/remove-daytona-file/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/remove-daytona-file/README.md) · [Go Modal](../examples/go/modal/filesystem/remove-modal-file/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/remove-modal-file/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/remove-modal-file/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/remove-blaxel-file/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md). diff --git a/docs/naming.md b/docs/naming.md index 28688488..f9ba6cde 100644 --- a/docs/naming.md +++ b/docs/naming.md @@ -39,6 +39,7 @@ SDK client remain private. Construction uses sdks/go/sandbox/ Public SDK module sdks/go/providers/modal/ Optional Modal module sdks/go/providers/daytona/ Optional Daytona module +sdks/go/providers/blaxel/ Optional Blaxel module sdks/go/internal/contracts/ Generated Go wire types; only packages under sdks/go can import them sdks/go/internal/sdk/ Shared SDK error type and provider helpers proto/kit/sandbox/v1/ Shared contracts @@ -57,7 +58,7 @@ sdks/typescript/sandbox/ Public TypeScript SDK (@sandbox-kit/sandbox) src/contracts/ Public type aliases over protobuf messages src/metadata/ Metadata map helpers src/internal/ Package-private duration/oneof/strip helpers -sdks/typescript/providers/ Optional providers (@sandbox-kit/daytona, @sandbox-kit/modal) +sdks/typescript/providers/ Optional providers (@sandbox-kit/daytona, @sandbox-kit/modal, @sandbox-kit/blaxel) src/mappings/ Create/info field mappings src/checks/ Provider create checks src/config/ Native SDK client configuration diff --git a/docs/process.md b/docs/process.md index f8fe2571..37d91d77 100644 --- a/docs/process.md +++ b/docs/process.md @@ -1,13 +1,13 @@ # Process Run a command inside an open sandbox and wait for it to finish. Use the same -options on Daytona and Modal. +options on Daytona, Modal, and Blaxel. | You want to | Go | TypeScript | | ------------------------------------- | ----- | ---------- | | Run a command and wait for the result | `Run` | `run` | -Open a handle with `Create`, Daytona `Get`, or Modal `Attach`, then call +Open a handle with `Create`, Daytona or Blaxel `Get`, or Modal `Attach`, then call `Process()` / `sandbox.process`. Calls on one handle run one at a time (including filesystem and lifecycle). A @@ -25,10 +25,10 @@ Setting both or neither fails with field `argv`. A blank or whitespace-only How providers run the command: -| You pass | Daytona | Modal | -| -------- | ------------------------- | ------------------------ | -| `argv` | POSIX-quoted shell string | argument list as-is | -| `shell` | shell string as-is | `bash -c` with that text | +| You pass | Daytona | Modal | Blaxel | +| -------- | ------------------------- | ------------------------ | ------------------------- | +| `argv` | One quoted shell command string | argument list as-is | One quoted shell command string | +| `shell` | shell string as-is | `bash -c` with that text | shell string as-is | ## Working directory and environment @@ -56,6 +56,7 @@ code. | -------- | ----------------------------------------------------------------- | | Daytona | stdout only; `stderr` is empty (Go returns a non-nil empty slice) | | Modal | both stdout and stderr | +| Blaxel | both stdout and stderr | ## Timeouts and cancel @@ -65,23 +66,28 @@ code. | `timeout: 0` / `Duration(0)` | Do not add an extra client deadline for this call | | Positive timeout | Cap this call; must be a whole number of seconds (`5000` ms or `5 * time.Second` is fine; `1500` ms is rejected, field `timeout`) | -Omit or zero also means **no separate provider command limit**. Provider -quirks: +Omit or zero also means **no separate provider process time limit** (see +provider quirks). Provider quirks: - **Daytona** — if you omit a native timeout, Daytona defaults to 10 seconds. This client therefore uses timeout `0` for omit/zero so the command is not capped at 10 seconds. - **Modal** — omit/zero adds no Modal command limit. In TypeScript the client omits `timeoutMs` (sending `0` is rejected by Modal). +- **Blaxel** — omit or zero waits until the command ends (or you cancel), with + no Blaxel process time limit. Positive timeouts must be whole seconds and + cap how long the command may run. Canceling stops waiting; the remote + command may keep running. If the wait times out, you get a timeout error. Cancel behavior: -- **Go** — cancel or deadline on the context ends the wait and returns an +- **Go** — cancel or a deadline on the context ends the wait and returns an error. The handle stays busy until that provider call returns. - **TypeScript** — abort rejects the `await`. The remote command may keep - running; Daytona and Modal exec do not take an `AbortSignal`. The handle - still stays busy until that provider call settles, so the next call on the - same handle does not overlap it. + running. Daytona and Modal do not observe the signal inside exec. Blaxel + waits stop when the signal aborts. The handle stays busy until the + provider call settles, so the next call on the same handle does not + overlap it. ```ts const result = await sandbox.process.run({ @@ -113,17 +119,19 @@ result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ [Go Daytona](../examples/go/daytona/process/run-daytona-command/README.md) · [TypeScript Daytona](../examples/typescript/daytona/process/run-daytona-command/README.md) · [Go Modal](../examples/go/modal/process/run-modal-command/README.md) · -[TypeScript Modal](../examples/typescript/modal/process/run-modal-command/README.md). +[TypeScript Modal](../examples/typescript/modal/process/run-modal-command/README.md) · +[Go Blaxel](../examples/go/blaxel/process/run-blaxel-command/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/process/run-blaxel-command/README.md). ## More examples -| Topic | Go Daytona | TypeScript Daytona | Go Modal | TypeScript Modal | -| ------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| Shell string | [run-daytona-shell-command](../examples/go/daytona/process/run-daytona-shell-command/README.md) | [run-daytona-shell-command](../examples/typescript/daytona/process/run-daytona-shell-command/README.md) | [run-modal-shell-command](../examples/go/modal/process/run-modal-shell-command/README.md) | [run-modal-shell-command](../examples/typescript/modal/process/run-modal-shell-command/README.md) | -| Working directory | [run-daytona-with-cwd](../examples/go/daytona/process/run-daytona-with-cwd/README.md) | [run-daytona-with-cwd](../examples/typescript/daytona/process/run-daytona-with-cwd/README.md) | [run-modal-with-cwd](../examples/go/modal/process/run-modal-with-cwd/README.md) | [run-modal-with-cwd](../examples/typescript/modal/process/run-modal-with-cwd/README.md) | -| Environment | [run-daytona-with-env](../examples/go/daytona/process/run-daytona-with-env/README.md) | [run-daytona-with-env](../examples/typescript/daytona/process/run-daytona-with-env/README.md) | [run-modal-with-env](../examples/go/modal/process/run-modal-with-env/README.md) | [run-modal-with-env](../examples/typescript/modal/process/run-modal-with-env/README.md) | -| Timeout | [run-daytona-with-timeout](../examples/go/daytona/process/run-daytona-with-timeout/README.md) | [run-daytona-with-timeout](../examples/typescript/daytona/process/run-daytona-with-timeout/README.md) | [run-modal-with-timeout](../examples/go/modal/process/run-modal-with-timeout/README.md) | [run-modal-with-timeout](../examples/typescript/modal/process/run-modal-with-timeout/README.md) | -| Non-zero exit code | [run-daytona-check-exit](../examples/go/daytona/process/run-daytona-check-exit/README.md) | [run-daytona-check-exit](../examples/typescript/daytona/process/run-daytona-check-exit/README.md) | [run-modal-check-exit](../examples/go/modal/process/run-modal-check-exit/README.md) | [run-modal-check-exit](../examples/typescript/modal/process/run-modal-check-exit/README.md) | +| Topic | Go Daytona | TypeScript Daytona | Go Modal | TypeScript Modal | Go Blaxel | TypeScript Blaxel | +| ------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| Shell string | [run-daytona-shell-command](../examples/go/daytona/process/run-daytona-shell-command/README.md) | [run-daytona-shell-command](../examples/typescript/daytona/process/run-daytona-shell-command/README.md) | [run-modal-shell-command](../examples/go/modal/process/run-modal-shell-command/README.md) | [run-modal-shell-command](../examples/typescript/modal/process/run-modal-shell-command/README.md) | [run-blaxel-shell-command](../examples/go/blaxel/process/run-blaxel-shell-command/README.md) | [run-blaxel-shell-command](../examples/typescript/blaxel/process/run-blaxel-shell-command/README.md) | +| Working directory | [run-daytona-with-cwd](../examples/go/daytona/process/run-daytona-with-cwd/README.md) | [run-daytona-with-cwd](../examples/typescript/daytona/process/run-daytona-with-cwd/README.md) | [run-modal-with-cwd](../examples/go/modal/process/run-modal-with-cwd/README.md) | [run-modal-with-cwd](../examples/typescript/modal/process/run-modal-with-cwd/README.md) | [run-blaxel-with-cwd](../examples/go/blaxel/process/run-blaxel-with-cwd/README.md) | [run-blaxel-with-cwd](../examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md) | +| Environment | [run-daytona-with-env](../examples/go/daytona/process/run-daytona-with-env/README.md) | [run-daytona-with-env](../examples/typescript/daytona/process/run-daytona-with-env/README.md) | [run-modal-with-env](../examples/go/modal/process/run-modal-with-env/README.md) | [run-modal-with-env](../examples/typescript/modal/process/run-modal-with-env/README.md) | [run-blaxel-with-env](../examples/go/blaxel/process/run-blaxel-with-env/README.md) | [run-blaxel-with-env](../examples/typescript/blaxel/process/run-blaxel-with-env/README.md) | +| Timeout | [run-daytona-with-timeout](../examples/go/daytona/process/run-daytona-with-timeout/README.md) | [run-daytona-with-timeout](../examples/typescript/daytona/process/run-daytona-with-timeout/README.md) | [run-modal-with-timeout](../examples/go/modal/process/run-modal-with-timeout/README.md) | [run-modal-with-timeout](../examples/typescript/modal/process/run-modal-with-timeout/README.md) | [run-blaxel-with-timeout](../examples/go/blaxel/process/run-blaxel-with-timeout/README.md) | [run-blaxel-with-timeout](../examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md) | +| Non-zero exit code | [run-daytona-check-exit](../examples/go/daytona/process/run-daytona-check-exit/README.md) | [run-daytona-check-exit](../examples/typescript/daytona/process/run-daytona-check-exit/README.md) | [run-modal-check-exit](../examples/go/modal/process/run-modal-check-exit/README.md) | [run-modal-check-exit](../examples/typescript/modal/process/run-modal-check-exit/README.md) | [run-blaxel-check-exit](../examples/go/blaxel/process/run-blaxel-check-exit/README.md) | [run-blaxel-check-exit](../examples/typescript/blaxel/process/run-blaxel-check-exit/README.md) | ## Limits diff --git a/docs/provider-verification.md b/docs/provider-verification.md index c7deb3a0..2a364d98 100644 --- a/docs/provider-verification.md +++ b/docs/provider-verification.md @@ -1,6 +1,7 @@ # Provider reference verification -Go and TypeScript bindings target Modal v0.11.1 and Daytona v0.225.0 +Go and TypeScript bindings target Modal v0.11.1, Daytona v0.225.0, and Blaxel +`@blaxel/core@0.3.36` / `github.com/blaxel-ai/sdk-go@v0.31.0` (`modal@0.11.1`, `@daytona/sdk@0.225.0`). Latest documentation helps discover APIs; pinned SDK source and compilation determine the signatures and serialization used by this checkout. @@ -14,11 +15,13 @@ resolution, and error translation without provisioning cloud resources. Filesystem tests cover path validation, local copy classification, timeouts, and provider file APIs (Modal `readBytes` / `writeBytes` / `copyFromLocal` / `listFiles`; Daytona high-level `FileSystem` plus toolbox list/stat, and -`downloadFiles` with timeout `0` when Kit adds no transfer limit). Process -tests cover argv/shell exclusivity, working-directory checks, whole-second -command timeouts, and command mapping (Daytona POSIX-quoted argv; Modal -`bash -c` for shell). Provider tests use the pinned SDK's transport/service -seams; they do not expose those native clients through the public SDK. +`downloadFiles` with timeout `0` when Kit adds no transfer limit; Blaxel +filesystem and process adapters against the pinned `@blaxel/core` / +`sdk-go` seams). Process tests cover argv/shell exclusivity, +working-directory checks, whole-second command timeouts, and command mapping +(Daytona and Blaxel POSIX-quoted argv; Modal `bash -c` for shell). Provider +tests use the pinned SDK's transport/service seams; they do not expose those +native clients through the public SDK. ## Reference sources @@ -26,6 +29,7 @@ seams; they do not expose those native clients through the public SDK. | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Modal | [Go client](https://modal.com/docs/sdk/go/latest/Client), [Go sandbox](https://modal.com/docs/sdk/go/latest/Sandbox), [JavaScript/Go SDK guide](https://modal.com/docs/guide/sdk-javascript-go) | [Resources](https://modal.com/docs/guide/resources), [Sandbox resources](https://modal.com/docs/guide/sandbox-resources), [Sandboxes](https://modal.com/docs/guide/sandboxes), [Regions](https://modal.com/docs/guide/region-selection) | | Daytona | [Go client/sandbox](https://www.daytona.io/docs/en/go-sdk/daytona/), [Go types](https://www.daytona.io/docs/en/go-sdk/types/), [TypeScript SDK](https://www.daytona.io/docs/en/typescript-sdk/) | [Sandboxes](https://www.daytona.io/docs/en/sandboxes/), [Limits](https://www.daytona.io/docs/limits) | +| Blaxel | [TypeScript SDK](https://docs.blaxel.ai/sdk-reference/sdk-ts), [Go SDK](https://docs.blaxel.ai/sdk-reference/sdk-go) | [Sandboxes](https://docs.blaxel.ai/Sandboxes/Overview), [Regions](https://docs.blaxel.ai/Infrastructure/Regions) | Modal creation dependencies also use the [Secret](https://modal.com/docs/sdk/go/latest/Secret), [Probe](https://modal.com/docs/sdk/go/latest/Probe), @@ -46,11 +50,16 @@ retain native behavior. Handwritten error classification exposes shared `ErrorKind` values while preserving native classification through the original cause. -The TypeScript adapters construct `ModalClient` and `Daytona` directly. Modal -looks up the app, image, secrets, and attached sandboxes through the pinned -client services. Daytona uses its asynchronous create/get and lifecycle methods. -Handwritten mappings and error classification preserve the same portable -semantics in both languages. +The TypeScript adapters construct `ModalClient`, `Daytona`, and Blaxel +`SandboxInstance` / `settings` directly. Modal looks up the app, image, +secrets, and attached sandboxes through the pinned client services. Daytona +uses its asynchronous create/get and lifecycle methods. Blaxel create uses +`SandboxInstance.create` (not create-if-not-exists); get/refresh look up by +sandbox name; delete removes the resource. Handwritten mappings and error +classification preserve the same portable semantics in both languages. +Blaxel memory converts Kit MiB to Blaxel's decimal MB field, rounding up; Kit +rejects CPU/disk overrides and has no portable volume fields. TypeScript +Blaxel credentials go through process-global `@blaxel/core` `settings`. ## Conflicting Daytona policy documentation @@ -73,6 +82,7 @@ See the [policy matrix](sandbox-creation.md#policy-semantics). | Modal | 0.125 physical CPU cores and 128 MiB | Five-minute maximum lifetime | | Daytona image creation | 1 vCPU, 1 GiB memory, 3 GiB disk | Provider/class defaults | | Daytona snapshot creation | Snapshot resource allocation | Provider/class defaults | +| Blaxel | Provider default when memory omitted | Provider defaults; TTL ≥5m, idle ≥1m when set | Kit leaves omitted fields to the provider. It does not inject these documented defaults into requests. diff --git a/docs/providers.md b/docs/providers.md index f648d5d7..398cd64a 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -2,10 +2,10 @@ Install the core package and the provider you use. -| | Daytona | Modal | -| ---------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | -| TypeScript | `@sandbox-kit/sandbox` and `@sandbox-kit/daytona` | `@sandbox-kit/sandbox` and `@sandbox-kit/modal` | -| Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/daytona` | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/modal` | +| | Daytona | Modal | Blaxel | +| ---------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| TypeScript | `@sandbox-kit/sandbox` and `@sandbox-kit/daytona` | `@sandbox-kit/sandbox` and `@sandbox-kit/modal` | `@sandbox-kit/sandbox` and `@sandbox-kit/blaxel` | +| Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/daytona` | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/modal` | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/blaxel` | ```go client, err := sandbox.NewClient(modal.New(), &sandbox.Config{ @@ -26,16 +26,38 @@ await using client = new Client({ }); ``` +```go +client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: apiKey}, + }, + }, + Scope: &sandbox.Scope{Workspace: sandbox.Value("my-workspace")}, + Region: sandbox.Value("us-pdx-1"), +}) +``` + +```ts +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: apiKey } }, + scope: { workspace: "my-workspace" }, + region: "us-pdx-1", +}); +``` + ## What each provider can do -| Operation | Modal | Daytona | -| ----------------------------------- | ---------------------------------- | ------------------------------------------ | -| Create | Registry image and an existing app | Default snapshot, named snapshot, or image | -| Open an existing sandbox | `attach` (ID only) | `get` (provider lookup) | -| Start, stop, pause, archive, delete | Unsupported | Supported | -| Terminate | Supported | Unsupported | -| Filesystem | Same eight shared methods | Same eight shared methods | -| Process | Same blocking `Run` | Same blocking `Run` | +| Operation | Modal | Daytona | Blaxel | +| --------------------------- | ---------------------------------- | ------------------------------------------ | -------------------------------------- | +| Create | Registry image and an existing app | Default snapshot, named snapshot, or image | Named image create (Blaxel name rules) | +| Open an existing sandbox | `attach` (ID only) | `get` (provider lookup) | `get` / `refresh` by name | +| Start, stop, pause, archive | Unsupported | Supported | Unsupported | +| Delete | Unsupported | Supported | Supported | +| Terminate | Supported | Unsupported | Unsupported | +| Filesystem | Same eight shared methods | Same eight shared methods | Same eight shared methods | +| Process | Same blocking `Run` | Same blocking `Run` | Same blocking `Run` | `supports` reports whether the provider implements that operation. A missing operation fails before the provider call. Daytona still accepts or rejects a @@ -43,16 +65,20 @@ supported call for a particular sandbox, and Kit returns that error. Set Daytona's target with `region` on the client. A placement block on the create request is rejected. Modal accepts cloud and region on the create -request, and rejects a placement target. +request, and rejects a placement target. Blaxel create requires +`Config.Region` / `region` on the client. Per-request placement is rejected. Closing the client closes your connection to the provider. The sandboxes keep running. Closing a handle leaves the sandbox as it is. You can keep using the handle. The next call returns the provider's result. The public API covers create, lifecycle, files, and blocking command execution -inside the sandbox. GPU requests, resizing, storage volumes, browsers, -streaming process handles, sessions, PTY, and macOS or Windows creation are -outside it. +inside the sandbox. GPU requests, resizing, storage volumes (no portable +volume fields; Blaxel, Daytona, and Modal volumes stay out of create), +browsers, streaming process handles, sessions, PTY, and macOS or Windows +creation are outside it. + +In TypeScript, use one Blaxel client (or one API key) per process. See [configuration](configuration.md), [creation](sandbox-creation.md), [lifecycle](sandbox-lifecycle.md), [filesystem](filesystem.md), and diff --git a/docs/responses-and-errors.md b/docs/responses-and-errors.md index b6a0160d..f388f7c6 100644 --- a/docs/responses-and-errors.md +++ b/docs/responses-and-errors.md @@ -19,9 +19,7 @@ if info.Resources != nil { ``` A present optional value keeps explicit zero, false, and empty strings. A -present map or slice keeps an empty collection. A create or lookup fails in -the client when the provider returns no sandbox ID. When the provider sets -`provider`, it must match the provider you selected. +present map or slice keeps an empty collection. ## Read an error @@ -39,14 +37,19 @@ if errors.As(err, &detail) { fmt.Println("Check configuration:", detail.Field) case sandbox.ErrorKindAuthentication: fmt.Println("Check provider credentials") + case sandbox.ErrorKindAlreadyExists: + fmt.Println("Named resource already exists") + case sandbox.ErrorKindConflict: + fmt.Println("Current state rejects this operation") } } ``` -The same switch works for either provider. Runnable examples: -[Modal unsupported settings](../examples/go/modal/errors/handle-modal-errors/README.md) +The same switch works for any provider. Runnable examples: +[Modal unsupported settings](../examples/go/modal/errors/handle-modal-errors/README.md), +[Daytona invalid configuration](../examples/go/daytona/errors/handle-daytona-errors/README.md), and -[Daytona invalid configuration](../examples/go/daytona/errors/handle-daytona-errors/README.md). +[Blaxel invalid configuration](../examples/go/blaxel/errors/handle-blaxel-errors/README.md). Go and TypeScript use the same field string. A rule about one setting names that setting, such as `resources.cpu_limit_cores`. A rule about several @@ -78,9 +81,10 @@ try { ``` TypeScript examples: -[handle-daytona-errors](../examples/typescript/daytona/errors/handle-daytona-errors/README.md) +[handle-daytona-errors](../examples/typescript/daytona/errors/handle-daytona-errors/README.md), +[handle-modal-errors](../examples/typescript/modal/errors/handle-modal-errors/README.md), and -[handle-modal-errors](../examples/typescript/modal/errors/handle-modal-errors/README.md). +[handle-blaxel-errors](../examples/typescript/blaxel/errors/handle-blaxel-errors/README.md). ## Provider errors @@ -103,9 +107,16 @@ branch on `Kind` without importing that package. TypeScript keeps the provider error on the standard `cause` property of `SandboxError`. -An error that Kit could not classify keeps an unknown kind and the original -cause. A kind is a category. It is not a signal that retrying is safe, and -Kit does not retry for you. +An unclassified error keeps an unknown kind and the original cause. A kind is +a category. It is not a signal that retrying is safe, and Kit does not retry +for you. + +On Blaxel, creating a sandbox whose name already exists returns +`AlreadyExists`. If a delete is still finishing or another conflict occurs, +you get `Conflict`. Lifetime below Blaxel’s floors (≥5m max lifetime, ≥1m idle) +or a non-whole-second duration is `InvalidArgument`. Unsupported create fields +are `Unsupported`. When Blaxel returns a control-plane code, it is on +`ProviderCode` / `providerCode`. ## After a failure diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 916366fe..7bca837e 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -6,22 +6,25 @@ from lifecycle operations on an existing sandbox. ## Supported creation settings -Go and TypeScript integrations target Modal v0.11.1 and Daytona v0.225.0 -(`modal@0.11.1`, `@daytona/sdk@0.225.0`). - -| Setting | Modal | Daytona | -| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | -| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | -| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | -| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | -| Placement | Cloud and region preferences | Client target; request placement unsupported | -| Environment and labels | Environment map and tags | Environment map and labels | -| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | -| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | -| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | -| Observability | Verbose output | Telemetry endpoint | +Go and TypeScript integrations target Modal, Daytona, and Blaxel. Pinned +dependency versions live in each provider package. + +| Setting | Modal | Daytona | Blaxel | +| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | +| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | +| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | +| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory (`memory_mib` / `memoryMib`); CPU/disk overrides rejected | +| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | +| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | +| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | +| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | +| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Create `timeout`; 1–50s frees the name on timeout; waitFor Default/Scheduled only; Ready/Started/Submitted unsupported; edge HTTP 504: TypeScript may keep waiting, Go returns timeout (get by name before recreate) | +| Observability | Verbose output | Telemetry endpoint | Unsupported | + +Kit has no portable volume fields; Blaxel volumes stay out of create the same +way Daytona and Modal volumes do. Modal default, snapshot, and warm-pool sources are unavailable. Daytona warm pools and explicit snapshot kinds are unavailable. A field the provider does @@ -46,22 +49,24 @@ field was omitted. Invalid combinations fail in the client before the provider call. A field the selected provider does not support fails there too. -| Resource | Modal | Daytona | -| ---------------- | ----------------------------------------------- | -------------------------------------------- | -| CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | -| Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | -| Disk | Creation override unsupported | Whole GiB expressed as MiB | -| Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | +| Resource | Modal | Daytona | Blaxel | +| ---------------- | ----------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------- | +| CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | Derived by Blaxel from memory; Kit rejects CPU overrides | +| Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | `memory_mib` / `memoryMib` (Kit converts to Blaxel decimal MB) | +| Disk | Creation override unsupported | Whole GiB expressed as MiB | Creation override unsupported | +| Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | At least 5 minutes when set | Daytona request conversion divides MiB exactly by 1024. Allocated metadata converts GiB back to MiB. Snapshot creation inherits snapshot resources. Positive CPU or memory limits require a corresponding request and cannot be -lower than that request. +lower than that request. On Blaxel, pass memory as an integer; Kit does not +convert units. Local checks cover syntax, known enums, documented bounds, precision, and supported combinations. Account quotas, class-specific permissions, image existence, regions, and capacity remain provider-enforced. Metadata availability -varies; resource metadata represents SDK-reported allocation, not echoed requests. +varies; resource and label fields on the handle are what the provider reported, +not a copy of the create request. ## Policy semantics @@ -94,6 +99,11 @@ before provider calls unless `lifetime.ephemeral` is explicitly true. Modal idle termination supports disabled or positive whole-second delays. Its maximum lifetime and idle delay remain separate settings. +Blaxel idle terminate supports only `PolicyModeAfter` with a delay of at least +1m, or provider default. `PolicyModeDisabled` on idle terminate is rejected. +Maximum lifetime, when set, must be at least 5m. Ephemeral, idle stop/pause, +stopped archive/delete, runtime, and isolation are rejected. + Image references support lowercase repository paths, optional registry hostnames and ports, tags up to 128 characters, and SHA-256 digests, within 512 characters. IPv6 registry literals and other digest algorithms are unsupported. @@ -115,6 +125,10 @@ deadline. A caller-context deadline still applies. A Daytona creation timeout must be a whole number of seconds. An omitted Daytona creation timeout and an explicit zero both leave Daytona's 60-second create default in place. Modal ready waiting also requires a readiness probe and a positive creation timeout. +On Blaxel, an edge HTTP 504 during create can leave TypeScript waiting until +the sandbox is ready or the create timeout ends, while Go returns a timeout as +soon as that response arrives. A Go timeout does not always mean the sandbox +was never created — call `Get` by name before recreating. ## Results and cleanup @@ -131,10 +145,10 @@ handle. The next call returns the provider's result. Lifecycle operations are described in the [lifecycle guide](sandbox-lifecycle.md). Files inside a sandbox are described in the [filesystem guide](filesystem.md). -Creation covers the sandbox itself. GPU requests, resizing, storage volumes, -browsers, streaming process handles, sessions, PTY, and macOS or Windows -creation are outside it. Blocking command execution is covered in -[process](process.md). +Creation covers the sandbox itself. GPU requests, resizing, storage volumes +(including Blaxel volumes), browsers, streaming process handles, sessions, +PTY, and macOS or Windows creation are outside it. Blocking command execution +is covered in [process](process.md). See [configuration](configuration.md), [lifecycle](sandbox-lifecycle.md), and [filesystem](filesystem.md). diff --git a/docs/sandbox-lifecycle.md b/docs/sandbox-lifecycle.md index 9df3e1ba..b0e8eefa 100644 --- a/docs/sandbox-lifecycle.md +++ b/docs/sandbox-lifecycle.md @@ -27,7 +27,7 @@ treat zero as no operation timeout. A timeout that is not a whole number of seconds is rejected before the native call. A terminate timeout is how long Kit waits. Modal does not take that duration as its own terminate timeout. With wait, Modal polls until the sandbox exits or the deadline fires. -`Client.Close` / `client.close()` closes your connection to Daytona or Modal +`Client.Close` / `client.close()` closes your connection to Daytona, Modal, or Blaxel and returns that result. It does not stop or delete the sandbox. A later call uses that connection and returns the provider's result. `Sandbox.Close` / `sandbox.close()` does not change the sandbox. You can keep @@ -45,7 +45,7 @@ const sandbox = await client.create(request); await sandbox.stop({ timeout: 60_000 }); ``` -Disposing a client closes your connection to Daytona or Modal. Disposing a +Disposing a client closes your connection to Daytona, Modal, or Blaxel. Disposing a handle does not stop, terminate, or delete the sandbox. You can keep using the handle. @@ -86,16 +86,16 @@ canceled before it starts never reaches the provider. ## Provider support -| Operation | Modal | Daytona | -| ----------- | -------------------------------------- | ----------- | -| Attach | Supported; ID only, no provider lookup | Unsupported | -| Refresh/Get | Unsupported | Supported | -| Start | Unsupported | Supported | -| Stop | Unsupported | Supported | -| Pause | Unsupported | Supported | -| Terminate | Supported | Unsupported | -| Archive | Unsupported | Supported | -| Delete | Unsupported | Supported | +| Operation | Modal | Daytona | Blaxel | +| ----------- | -------------------------------------- | ----------- | ---------------------------------- | +| Attach | Supported; ID only, no provider lookup | Unsupported | Unsupported | +| Refresh/Get | Unsupported | Supported | Supported (lookup by sandbox name) | +| Start | Unsupported | Supported | Unsupported | +| Stop | Unsupported | Supported | Unsupported | +| Pause | Unsupported | Supported | Unsupported | +| Terminate | Supported | Unsupported | Unsupported | +| Archive | Unsupported | Supported | Unsupported | +| Delete | Unsupported | Supported | Supported | Daytona reports its resolved sandbox class after creation. Kit maps container and Linux VM to shared isolation kinds and preserves Windows as a provider-specific @@ -108,6 +108,14 @@ Modal gVisor and Linux VM sandboxes share Modal's terminal execution lifecycle. `Stop` or `Delete`. Modal's native `FromID` performs no remote lookup, so Kit exposes it as `Client.Attach`; Modal does not advertise `Get` or `Refresh`. +Blaxel looks up sandboxes by name for `Get` and `Refresh`. After a successful +`Delete`, the handle reports `Deleted` and `terminal`. Omit `Wait` to return +when Blaxel accepts the delete; recreating the same name too soon may return +`Conflict`. Pass `Wait: true` to wait until removal finishes. +`Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` are unsupported. +Closing a Blaxel handle does not contact Blaxel. On `Get` / `Refresh`, an idle +sandbox shows as paused and an active one shows as running. + File calls on the same handle are documented in [filesystem](filesystem.md). -Both providers implement the same eight methods; each handle serializes those -calls with its lifecycle transitions. +Daytona, Modal, and Blaxel implement the same eight methods; each handle +serializes those calls with its lifecycle transitions. diff --git a/examples/README.md b/examples/README.md index 46f9a9f7..00a7398c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,7 +4,7 @@ Independent Go and TypeScript projects. Each selects one provider, loads its own `.env`, and demonstrates one operation. Projects for a provider sit in `filesystem`, `process`, `configuration`, `lifecycle`, or `errors`. Every project README explains what the example does for that provider, with edge -cases drawn from Daytona and Modal behavior. +cases drawn from Daytona, Modal, and Blaxel behavior. ## Run one @@ -24,13 +24,19 @@ Modal needs a token pair and an existing app: [Go](go/modal/configuration/create-modal-sandbox/README.md) · [TypeScript](typescript/modal/configuration/create-modal-sandbox/README.md). +Blaxel needs an API key, a workspace, and a client region (examples use +`BL_REGION`, default `us-pdx-1`): +[Go](go/blaxel/configuration/create-blaxel-sandbox/README.md) · +[TypeScript](typescript/blaxel/configuration/create-blaxel-sandbox/README.md). + Creation examples provision real resources. Filesystem, process, and lifecycle -examples use the sandbox ID in `.env`. A filesystem example runs one file -operation (read, write, copy, mkdir, list, stat, or remove) on the path you -set. A process example runs one command. Stop, +examples use the sandbox id in `.env` (`DAYTONA_SANDBOX_ID`, `MODAL_SANDBOX_ID`, +or Blaxel `BL_SANDBOX_NAME` — Blaxel id is the create name). A filesystem +example runs one file operation (read, write, copy, mkdir, list, stat, or +remove) on the path you set. A process example runs one command. Stop, terminate, archive, and delete can destroy the sandbox. Error-handling examples fail locally. `Client.Close` closes your connection to -Daytona or Modal. `Sandbox.Close` does not change the sandbox, and you can keep +Daytona, Modal, or Blaxel. `Sandbox.Close` does not change the sandbox, and you can keep using the handle. Indexes: [Go](go/README.md) · [TypeScript](typescript/README.md). @@ -114,6 +120,33 @@ Indexes: [Go](go/README.md) · [TypeScript](typescript/README.md). | Modal termination request | [go](go/modal/lifecycle/request-modal-termination/README.md) | [typescript](typescript/modal/lifecycle/request-modal-termination/README.md) | | Modal close handle | [go](go/modal/lifecycle/close-modal-handle/README.md) | [typescript](typescript/modal/lifecycle/close-modal-handle/README.md) | | Modal errors | [go](go/modal/errors/handle-modal-errors/README.md) | [typescript](typescript/modal/errors/handle-modal-errors/README.md) | +| Blaxel named image create | [go](go/blaxel/configuration/create-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-sandbox/README.md) | +| Blaxel memory MiB | [go](go/blaxel/configuration/create-blaxel-with-resources/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-resources/README.md) | +| Blaxel labels and environment | [go](go/blaxel/configuration/create-blaxel-with-labels/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-labels/README.md) | +| Blaxel maximum lifetime TTL | [go](go/blaxel/configuration/create-blaxel-with-ttl/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-ttl/README.md) | +| Blaxel idle delete policy | [go](go/blaxel/configuration/create-blaxel-idle/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-idle/README.md) | +| Blaxel client timeout | [go](go/blaxel/configuration/create-blaxel-client-timeout/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-client-timeout/README.md) | +| Blaxel published ports | [go](go/blaxel/configuration/create-blaxel-with-ports/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-ports/README.md) | +| Blaxel outbound domains | [go](go/blaxel/configuration/create-blaxel-with-allowlists/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md) | +| Blaxel run one command | [go](go/blaxel/process/run-blaxel-command/README.md) | [typescript](typescript/blaxel/process/run-blaxel-command/README.md) | +| Blaxel run one shell string | [go](go/blaxel/process/run-blaxel-shell-command/README.md) | [typescript](typescript/blaxel/process/run-blaxel-shell-command/README.md) | +| Blaxel run with working directory | [go](go/blaxel/process/run-blaxel-with-cwd/README.md) | [typescript](typescript/blaxel/process/run-blaxel-with-cwd/README.md) | +| Blaxel run with environment variables | [go](go/blaxel/process/run-blaxel-with-env/README.md) | [typescript](typescript/blaxel/process/run-blaxel-with-env/README.md) | +| Blaxel run with a whole-second timeout | [go](go/blaxel/process/run-blaxel-with-timeout/README.md) | [typescript](typescript/blaxel/process/run-blaxel-with-timeout/README.md) | +| Blaxel non-zero exit in the result | [go](go/blaxel/process/run-blaxel-check-exit/README.md) | [typescript](typescript/blaxel/process/run-blaxel-check-exit/README.md) | +| Blaxel read one file | [go](go/blaxel/filesystem/read-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/read-blaxel-file/README.md) | +| Blaxel write one file | [go](go/blaxel/filesystem/write-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/write-blaxel-file/README.md) | +| Blaxel copy a local file in | [go](go/blaxel/filesystem/copy-file-into-blaxel/README.md) | [typescript](typescript/blaxel/filesystem/copy-file-into-blaxel/README.md) | +| Blaxel copy a sandbox file out | [go](go/blaxel/filesystem/copy-file-from-blaxel/README.md) | [typescript](typescript/blaxel/filesystem/copy-file-from-blaxel/README.md) | +| Blaxel create a directory | [go](go/blaxel/filesystem/make-blaxel-directory/README.md) | [typescript](typescript/blaxel/filesystem/make-blaxel-directory/README.md) | +| Blaxel list one directory | [go](go/blaxel/filesystem/list-blaxel-directory/README.md) | [typescript](typescript/blaxel/filesystem/list-blaxel-directory/README.md) | +| Blaxel stat one path | [go](go/blaxel/filesystem/stat-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/stat-blaxel-file/README.md) | +| Blaxel remove one path | [go](go/blaxel/filesystem/remove-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/remove-blaxel-file/README.md) | +| Blaxel remote retrieval and metadata | [go](go/blaxel/lifecycle/get-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md) | +| Blaxel metadata refresh | [go](go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | +| Blaxel delete by name | [go](go/blaxel/lifecycle/delete-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md) | +| Blaxel close the handle; sandbox keeps running | [go](go/blaxel/lifecycle/close-blaxel-handle/README.md) | [typescript](typescript/blaxel/lifecycle/close-blaxel-handle/README.md) | +| Blaxel typed validation errors | [go](go/blaxel/errors/handle-blaxel-errors/README.md) | [typescript](typescript/blaxel/errors/handle-blaxel-errors/README.md) | ## Verify locally diff --git a/examples/go/README.md b/examples/go/README.md index 81a92c9d..328a59fa 100644 --- a/examples/go/README.md +++ b/examples/go/README.md @@ -13,16 +13,20 @@ cp .env.example .env go run . ``` -Skip copying if `.env` already exists. Programs load `.env` with `godotenv`; -shell variables win. The SDK does not load files. Modal app setup: +Skip copying if `.env` already exists. Programs load only that project’s +`.env` with `godotenv` (it does not override existing shell env). There is no +provider-level `.env`. The SDK does not load files. Modal app setup: [create-modal-sandbox](modal/configuration/create-modal-sandbox/README.md). +Blaxel needs an API key, workspace, and client region: +[create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). Creation programs provision real sandboxes (linked Daytona creates a parent and -child). Filesystem, process, and lifecycle programs use the configured sandbox -ID. Read the guide before a destructive stop, terminate, archive, delete, or -remove. +child). Filesystem, process, and lifecycle programs use the sandbox id in +`.env` (`DAYTONA_SANDBOX_ID`, `MODAL_SANDBOX_ID`, or Blaxel `BL_SANDBOX_NAME` — +Blaxel id is the create name). Read the guide before a destructive stop, +terminate, archive, delete, or remove. Error-handling programs fail locally. Closing a handle leaves the sandbox as it is. Closing the client -closes your connection to Daytona or Modal. +closes your connection to Daytona, Modal, or Blaxel. ## Catalog @@ -111,6 +115,38 @@ closes your connection to Daytona or Modal. | [request-modal-termination](modal/lifecycle/request-modal-termination/README.md) | non-waited termination | | [close-modal-handle](modal/lifecycle/close-modal-handle/README.md) | close the handle; sandbox keeps running | +### Blaxel + +| Project | Demonstrates | +| -------------------------------------------------------------------------------------------- | --------------------------------------- | +| [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md) | named image create | +| [create-blaxel-with-resources](blaxel/configuration/create-blaxel-with-resources/README.md) | memory MiB | +| [create-blaxel-with-labels](blaxel/configuration/create-blaxel-with-labels/README.md) | labels and environment | +| [create-blaxel-with-ttl](blaxel/configuration/create-blaxel-with-ttl/README.md) | maximum lifetime TTL | +| [create-blaxel-idle](blaxel/configuration/create-blaxel-idle/README.md) | idle delete policy | +| [create-blaxel-client-timeout](blaxel/configuration/create-blaxel-client-timeout/README.md) | client timeout | +| [create-blaxel-with-ports](blaxel/configuration/create-blaxel-with-ports/README.md) | published ports | +| [create-blaxel-with-allowlists](blaxel/configuration/create-blaxel-with-allowlists/README.md) | outbound domains | +| [run-blaxel-command](blaxel/process/run-blaxel-command/README.md) | run one command | +| [run-blaxel-shell-command](blaxel/process/run-blaxel-shell-command/README.md) | run one shell string | +| [run-blaxel-with-cwd](blaxel/process/run-blaxel-with-cwd/README.md) | run with working directory | +| [run-blaxel-with-env](blaxel/process/run-blaxel-with-env/README.md) | run with environment variables | +| [run-blaxel-with-timeout](blaxel/process/run-blaxel-with-timeout/README.md) | run with a whole-second timeout | +| [run-blaxel-check-exit](blaxel/process/run-blaxel-check-exit/README.md) | non-zero exit in the result | +| [read-blaxel-file](blaxel/filesystem/read-blaxel-file/README.md) | read one file | +| [write-blaxel-file](blaxel/filesystem/write-blaxel-file/README.md) | write one file | +| [copy-file-into-blaxel](blaxel/filesystem/copy-file-into-blaxel/README.md) | copy a local file in | +| [copy-file-from-blaxel](blaxel/filesystem/copy-file-from-blaxel/README.md) | copy a sandbox file out | +| [make-blaxel-directory](blaxel/filesystem/make-blaxel-directory/README.md) | create a directory | +| [list-blaxel-directory](blaxel/filesystem/list-blaxel-directory/README.md) | list one directory | +| [stat-blaxel-file](blaxel/filesystem/stat-blaxel-file/README.md) | stat one path | +| [remove-blaxel-file](blaxel/filesystem/remove-blaxel-file/README.md) | remove one path | +| [get-blaxel-sandbox](blaxel/lifecycle/get-blaxel-sandbox/README.md) | remote retrieval and metadata | +| [refresh-blaxel-sandbox](blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | metadata refresh | +| [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | delete by name | +| [close-blaxel-handle](blaxel/lifecycle/close-blaxel-handle/README.md) | close the handle; sandbox keeps running | +| [handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) | typed validation errors | + ## Verify without credentials ```sh @@ -126,9 +162,11 @@ Repository-wide: `sandbox-kit test go` from `tooling/`. ## Error handling -[handle-modal-errors](modal/errors/handle-modal-errors/README.md) and -[handle-daytona-errors](daytona/errors/handle-daytona-errors/README.md) switch on +[handle-modal-errors](modal/errors/handle-modal-errors/README.md), +[handle-daytona-errors](daytona/errors/handle-daytona-errors/README.md), and +[handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) switch on `*sandbox.Error` `Kind` in `main.go`. Other create projects print and exit. Cleanup failures do not replace creation failures. -Modal errors: unsupported disk. Daytona errors: invalid CPU. +Modal errors: unsupported disk. Daytona errors: invalid CPU. Blaxel errors: +invalid create configuration. diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/.env.example b/examples/go/blaxel/configuration/create-blaxel-client-timeout/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md b/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md new file mode 100644 index 00000000..05820772 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md @@ -0,0 +1,42 @@ +# Create a Blaxel sandbox with a client timeout + +Create one Blaxel sandbox using a shorter client-wide timeout. + +## What this means + +Sets `Config.Timeout` / `timeout` on the shared client. This bounds local waiting for SDK calls, including create. + +## Edge cases + +- Client `Timeout` is how long create may take. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-client-timeout +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-client-timeout/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod new file mode 100644 index 00000000..ed86310a --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-client-timeout + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.sum b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go b/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go new file mode 100644 index 00000000..10daf333 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(90 * time.Second), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-client-timeout"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with client timeout: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/.env.example b/examples/go/blaxel/configuration/create-blaxel-idle/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/README.md b/examples/go/blaxel/configuration/create-blaxel-idle/README.md new file mode 100644 index 00000000..115c4f0c --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with idle delete + +Create one Blaxel sandbox that deletes after an idle period. + +## What this means + +Set `lifetime.idleTerminate` with `after` and a delay (≥1 minute). The sandbox deletes after that idle period. You cannot disable idle terminate. + +## Edge cases + +- Disabling idle terminate is not representable and is rejected. +- Minimum idle duration follows Blaxel rules (1 minute). + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-idle +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-idle/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/go.mod b/examples/go/blaxel/configuration/create-blaxel-idle/go.mod new file mode 100644 index 00000000..7d86cb10 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-idle + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/go.sum b/examples/go/blaxel/configuration/create-blaxel-idle/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/main.go b/examples/go/blaxel/configuration/create-blaxel-idle/main.go new file mode 100644 index 00000000..351c0f55 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-idle"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Lifetime: &sandbox.LifetimePolicy{ + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(60 * time.Second), + }, + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with 60s idle delete: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/.env.example b/examples/go/blaxel/configuration/create-blaxel-sandbox/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md new file mode 100644 index 00000000..3ada61c5 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md @@ -0,0 +1,47 @@ +# Create a Blaxel sandbox + +Create one Blaxel sandbox from a registry image with an explicit name. Prints the sandbox ID and leaves the sandbox running. + +## What this means + +Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, +at most 49 characters), an image, and client `region` (`BL_REGION`, default +`us-pdx-1`). If the name already exists, you get `AlreadyExists`. Closing the +client does not delete the sandbox. + +## Edge cases + +- Omitting `name` or `source.image`, or using an invalid name, fails before Blaxel is called. +- A create error is not proof that no sandbox was provisioned. +- Reclaim sandboxes with TTL / idle policies or `delete`. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-sandbox/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod new file mode 100644 index 00000000..1788fee2 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-sandbox + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/go.sum b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go b/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go new file mode 100644 index 00000000..e5722875 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-sandbox"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md new file mode 100644 index 00000000..f29f6e1c --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with outbound domains + +Create one Blaxel sandbox that allows selected outbound domains. + +## What this means + +Set `network.outboundDomains` to allow those hosts. Other egress controls are unsupported on Blaxel. + +## Edge cases + +- Restricted egress modes and CIDR allowlists are rejected. +- Domain entries follow Blaxel validation. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-allowlists +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod new file mode 100644 index 00000000..167e3790 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-allowlists + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go new file mode 100644 index 00000000..46c23633 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-allowlists"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Network: &sandbox.NetworkConfig{ + OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"example.com"}}, + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with outbound domains: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-labels/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md b/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md new file mode 100644 index 00000000..ef147bab --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md @@ -0,0 +1,44 @@ +# Create a Blaxel sandbox with labels and env + +Create one Blaxel sandbox with labels and environment variables. + +## What this means + +Set `labels` and create-time environment variables. Environment variables are +available inside the sandbox. `Info().Labels` is set only when Blaxel reports +labels back — Kit does not copy them from the create request. + +## Edge cases + +- Invalid environment names are rejected before Blaxel is called. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-labels +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-labels/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod new file mode 100644 index 00000000..85c807e9 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-labels + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go b/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go new file mode 100644 index 00000000..5963603e --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-labels"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Labels: map[string]string{ + "team": "platform", + "purpose": "kit-example", + }, + Environment: map[string]string{ + "NODE_ENV": "development", + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with labels and environment: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-ports/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md new file mode 100644 index 00000000..8130b787 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with ports + +Create one Blaxel sandbox that publishes selected ports. + +## What this means + +Publish ports with `network.ports`. Default protocol is HTTP; `plain` is TCP; +`tls` is TLS. HTTP/2 over TLS is not supported. + +## Edge cases + +- Other network settings (egress mode, CIDRs, private network, proxies) are rejected. +- Port numbers must be in range. +- Blaxel reserves ports 80, 443, and 8080; this example uses 3000 and 3001. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-ports +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-ports/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod new file mode 100644 index 00000000..b10eb25c --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-ports + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go b/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go new file mode 100644 index 00000000..bd8c7d51 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-ports"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{ + {Port: 3000, Transport: sandbox.PortTransportTLS}, + {Port: 3001, Transport: sandbox.PortTransportPlain}, + }, + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with ports: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-resources/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md b/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md new file mode 100644 index 00000000..74bc69fe --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md @@ -0,0 +1,42 @@ +# Create a Blaxel sandbox with memory + +Create one Blaxel sandbox with an explicit memory size in MiB. Blaxel derives CPU from memory. + +## What this means + +You can set `resources.memory_mib` only. CPU cores, limits, and disk overrides are rejected. Omit memory to use Blaxel's default. + +## Edge cases + +- Memory values that Blaxel rejects are returned as provider errors. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-resources +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-resources/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod new file mode 100644 index 00000000..16e6d5b8 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-resources + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go b/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go new file mode 100644 index 00000000..f06738af --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-resources"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Resources: &sandbox.Resources{MemoryMib: sandbox.Value(uint64(4096))}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with 4096 MiB: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-ttl/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md new file mode 100644 index 00000000..a5bc4649 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with maximum lifetime + +Create one Blaxel sandbox that deletes after a maximum lifetime TTL. + +## What this means + +Set `lifetime.maximumLifetime` (whole seconds, at least 5 minutes). The sandbox deletes when that max age elapses. + +## Edge cases + +- Fractional-second durations are rejected. +- Idle cleanup uses `idleTerminate`, not maximum lifetime. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-ttl +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-ttl/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod new file mode 100644 index 00000000..43bb177e --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-ttl + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go b/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go new file mode 100644 index 00000000..98f535df --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-ttl"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(time.Hour), + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with 60-minute maximum lifetime: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/.env.example b/examples/go/blaxel/errors/handle-blaxel-errors/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/README.md b/examples/go/blaxel/errors/handle-blaxel-errors/README.md new file mode 100644 index 00000000..5837e996 --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/README.md @@ -0,0 +1,43 @@ +# Handle Blaxel validation errors + +Demonstrate typed validation errors from the Blaxel provider without creating a sandbox. + +## What this means + +Requests an unsupported CPU override so validation fails before any cloud call. Inspect `SandboxError` `kind` and `field`. + +## Edge cases + +- Does not provision cloud resources when validation fails as expected. +- Authentication still requires a configured client. + +## Run + +```sh +cd examples/go/blaxel/errors/handle-blaxel-errors +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Validation fails locally for the unsupported CPU field; no sandbox is created. +- Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/errors/handle-blaxel-errors/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/go.mod b/examples/go/blaxel/errors/handle-blaxel-errors/go.mod new file mode 100644 index 00000000..674480a3 --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/handle-blaxel-errors + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/go.sum b/examples/go/blaxel/errors/handle-blaxel-errors/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/main.go b/examples/go/blaxel/errors/handle-blaxel-errors/main.go new file mode 100644 index 00000000..4dda7b6a --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + _, err = client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-handle-blaxel-errors"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Resources: &sandbox.Resources{CpuCores: sandbox.Value(2.0)}, + }) + if err != nil { + var sandboxError *sandbox.Error + if !errors.As(err, &sandboxError) { + fmt.Fprintln(os.Stdout, err) + return + } + switch sandboxError.Kind { + case sandbox.ErrorKindInvalidArgument, sandbox.ErrorKindUnsupported: + fmt.Fprintf(os.Stdout, "Check %s: %s\n", sandboxError.Field, sandboxError.Message) + case sandbox.ErrorKindAuthentication: + fmt.Fprintln(os.Stdout, "Check your provider credentials.") + default: + fmt.Fprintln(os.Stdout, err) + } + return + } + fmt.Fprintln(os.Stdout, "Sandbox created.") +} diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example b/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example new file mode 100644 index 00000000..d6ad3fe0 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md b/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md new file mode 100644 index 00000000..2978db1d --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md @@ -0,0 +1,48 @@ +# Copy a file from Blaxel + +Copy a sandbox file out to a local path. + +## What this means + +`copyToLocal` creates a missing local parent directory. + +## Edge cases + +- Remote path must exist. + +## Run + +```sh +cd examples/go/blaxel/filesystem/copy-file-from-blaxel +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/copy-file-from-blaxel/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod new file mode 100644 index 00000000..33faa96c --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/copy-file-from-blaxel + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.sum b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go b/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go new file mode 100644 index 00000000..795eb6a2 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + local := os.Getenv("BL_LOCAL_PATH") + if err := files.CopyToLocal(context.Background(), remote, local, nil); err != nil { + log.Fatal(err) + } + fmt.Printf("copied %s to %s\n", remote, local) +} diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example b/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example new file mode 100644 index 00000000..d6ad3fe0 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md b/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md new file mode 100644 index 00000000..150f5d1c --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md @@ -0,0 +1,48 @@ +# Copy a file into Blaxel + +Copy a local file into a Blaxel sandbox path. + +## What this means + +A missing local file is not found before Blaxel is called (`local_path`). A local directory is an invalid argument. + +## Edge cases + +- Create the local file before running. + +## Run + +```sh +cd examples/go/blaxel/filesystem/copy-file-into-blaxel +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/copy-file-into-blaxel/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod new file mode 100644 index 00000000..8a61bf3d --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/copy-file-into-blaxel + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.sum b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go b/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go new file mode 100644 index 00000000..433f599c --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + local := os.Getenv("BL_LOCAL_PATH") + if err := files.CopyFromLocal(context.Background(), local, remote, nil); err != nil { + log.Fatal(err) + } + fmt.Printf("copied %s to %s\n", local, remote) +} diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example b/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example new file mode 100644 index 00000000..f6619de3 --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/README.md b/examples/go/blaxel/filesystem/list-blaxel-directory/README.md new file mode 100644 index 00000000..a423f98c --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/README.md @@ -0,0 +1,46 @@ +# List a Blaxel directory + +List entries in one absolute directory path. + +## What this means + +Returns shared directory entries with names and types. + +## Edge cases + +- Missing directories return not found. + +## Run + +```sh +cd examples/go/blaxel/filesystem/list-blaxel-directory +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/list-blaxel-directory/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod b/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod new file mode 100644 index 00000000..9ce05095 --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/list-blaxel-directory + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/go.sum b/examples/go/blaxel/filesystem/list-blaxel-directory/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/main.go b/examples/go/blaxel/filesystem/list-blaxel-directory/main.go new file mode 100644 index 00000000..6b9ba0fd --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + entries, err := files.ListDirectory(context.Background(), remote, nil) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s (%d entries)\n", remote, len(entries)) + for _, entry := range entries { + fmt.Println(entry.GetName()) + } +} diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example b/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example new file mode 100644 index 00000000..3ec608ba --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example-dir diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/README.md b/examples/go/blaxel/filesystem/make-blaxel-directory/README.md new file mode 100644 index 00000000..ed946c39 --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/README.md @@ -0,0 +1,46 @@ +# Make a Blaxel directory + +Create a directory at an absolute path in a Blaxel sandbox. + +## What this means + +`parents` defaults to true. + +## Edge cases + +- Absolute paths only. + +## Run + +```sh +cd examples/go/blaxel/filesystem/make-blaxel-directory +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example-dir +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/make-blaxel-directory/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod b/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod new file mode 100644 index 00000000..72221bf4 --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/make-blaxel-directory + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/go.sum b/examples/go/blaxel/filesystem/make-blaxel-directory/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/main.go b/examples/go/blaxel/filesystem/make-blaxel-directory/main.go new file mode 100644 index 00000000..07d54c63 --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + if err := files.MakeDirectory(context.Background(), remote, nil); err != nil { + log.Fatal(err) + } + fmt.Printf("created %s\n", remote) +} diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/.env.example b/examples/go/blaxel/filesystem/read-blaxel-file/.env.example new file mode 100644 index 00000000..f7524e0d --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/README.md b/examples/go/blaxel/filesystem/read-blaxel-file/README.md new file mode 100644 index 00000000..613de447 --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/README.md @@ -0,0 +1,46 @@ +# Read a Blaxel file + +Read one absolute path from a Blaxel sandbox. + +## What this means + +Uses `filesystem.readFile` / `ReadFile`. Paths in the sandbox are absolute. + +## Edge cases + +- Missing files return not found from Blaxel. + +## Run + +```sh +cd examples/go/blaxel/filesystem/read-blaxel-file +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/read-blaxel-file/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/go.mod b/examples/go/blaxel/filesystem/read-blaxel-file/go.mod new file mode 100644 index 00000000..2ef1eebe --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/read-blaxel-file + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/go.sum b/examples/go/blaxel/filesystem/read-blaxel-file/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/main.go b/examples/go/blaxel/filesystem/read-blaxel-file/main.go new file mode 100644 index 00000000..e5ffb068 --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + data, err := files.ReadFile(context.Background(), remote, nil) + if err != nil { + log.Fatal(err) + } + fmt.Printf("read %s (%d bytes)\n", remote, len(data)) +} diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example b/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example new file mode 100644 index 00000000..de34add4 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Set true for recursive remove. +BL_RECURSIVE= diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/README.md b/examples/go/blaxel/filesystem/remove-blaxel-file/README.md new file mode 100644 index 00000000..a4c29c64 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/README.md @@ -0,0 +1,48 @@ +# Remove a Blaxel path + +Remove one absolute path in a Blaxel sandbox. + +## What this means + +Set `BL_RECURSIVE=true` to remove recursively when supported by options. + +## Edge cases + +- Removing a missing path returns not found. + +## Run + +```sh +cd examples/go/blaxel/filesystem/remove-blaxel-file +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Set true for recursive remove. +BL_RECURSIVE= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/remove-blaxel-file/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod b/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod new file mode 100644 index 00000000..b207b60a --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/remove-blaxel-file + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/go.sum b/examples/go/blaxel/filesystem/remove-blaxel-file/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/main.go b/examples/go/blaxel/filesystem/remove-blaxel-file/main.go new file mode 100644 index 00000000..4644ee30 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + var options *sandbox.RemoveOptions + if os.Getenv("BL_RECURSIVE") == "true" { + options = &sandbox.RemoveOptions{Recursive: sandbox.Value(true)} + } + if err := files.Remove(context.Background(), remote, options); err != nil { + log.Fatal(err) + } + fmt.Printf("removed %s\n", remote) +} diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example b/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example new file mode 100644 index 00000000..f7524e0d --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md new file mode 100644 index 00000000..2e8853d2 --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md @@ -0,0 +1,48 @@ +# Stat a Blaxel path + +Stat one absolute path in a Blaxel sandbox. + +## What this means + +`Stat` returns `Type` (file or directory), `Size`, and usually `Permissions`, +`Owner`, `Group`, and `ModifiedAt`. `Name` and `Path` are whatever Blaxel +reports. Size may be zero when Blaxel does not return it. + +## Edge cases + +- Missing paths return not found. + +## Run + +```sh +cd examples/go/blaxel/filesystem/stat-blaxel-file +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/stat-blaxel-file/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod b/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod new file mode 100644 index 00000000..e99814d8 --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/stat-blaxel-file + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/go.sum b/examples/go/blaxel/filesystem/stat-blaxel-file/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/main.go b/examples/go/blaxel/filesystem/stat-blaxel-file/main.go new file mode 100644 index 00000000..1b103a20 --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + info, err := files.Stat(context.Background(), remote, nil) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s type=%s size=%d\n", info.GetPath(), info.GetType(), info.GetSize()) +} diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/.env.example b/examples/go/blaxel/filesystem/write-blaxel-file/.env.example new file mode 100644 index 00000000..f7524e0d --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/README.md b/examples/go/blaxel/filesystem/write-blaxel-file/README.md new file mode 100644 index 00000000..ee180bda --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/README.md @@ -0,0 +1,46 @@ +# Write a Blaxel file + +Write one absolute path in a Blaxel sandbox. + +## What this means + +`parents` defaults to true. `parents: false` is rejected. + +## Edge cases + +- Absolute remote paths only. + +## Run + +```sh +cd examples/go/blaxel/filesystem/write-blaxel-file +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/write-blaxel-file/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/go.mod b/examples/go/blaxel/filesystem/write-blaxel-file/go.mod new file mode 100644 index 00000000..138ea09f --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/write-blaxel-file + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/go.sum b/examples/go/blaxel/filesystem/write-blaxel-file/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/main.go b/examples/go/blaxel/filesystem/write-blaxel-file/main.go new file mode 100644 index 00000000..d4e9cf67 --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + body := []byte("hello from sandbox-kit\n") + if err := files.WriteFile(context.Background(), remote, body, nil); err != nil { + log.Fatal(err) + } + fmt.Printf("wrote %s (%d bytes)\n", remote, len(body)) +} diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example b/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md b/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md new file mode 100644 index 00000000..b4f390c9 --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md @@ -0,0 +1,45 @@ +# Close a Blaxel sandbox handle + +Close one Blaxel sandbox handle without contacting Blaxel or changing the sandbox. + +## What this means + +`Close` releases local handle resources only. The sandbox keeps running until TTL, idle policy, or delete. + +## Edge cases + +- After close, further handle calls fail locally. +- Client close also does not delete sandboxes. + +## Run + +```sh +cd examples/go/blaxel/lifecycle/close-blaxel-handle +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/lifecycle/close-blaxel-handle/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod new file mode 100644 index 00000000..c32880b5 --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/close-blaxel-handle + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/go.sum b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go b/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go new file mode 100644 index 00000000..8750b650 --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + if err := instance.Close(context.Background()); err != nil { + log.Fatal(err) + } + fmt.Printf("Closed the handle for Blaxel sandbox %s. The sandbox was not changed.\n", instance.ID()) +} diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md new file mode 100644 index 00000000..2b1b1865 --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -0,0 +1,49 @@ +# Delete a Blaxel sandbox + +Delete an existing Blaxel sandbox by name. + +## What this means + +`Delete` permanently removes the Blaxel sandbox. After success, `Info().State` +is `Deleted` and `Terminal` is true. This example uses `Wait: true` so removal +finishes before the call returns. Omit `Wait` to return when Blaxel accepts +the delete — recreating the same name too soon may return `Conflict`. Start, +stop, pause, archive, and terminate are not available. + +## Edge cases + +- Deletion destroys the sandbox and its filesystem. +- Closing the handle is not deletion. + +## Run + +```sh +cd examples/go/blaxel/lifecycle/delete-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod new file mode 100644 index 00000000..b221a716 --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/delete-blaxel-sandbox + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.sum b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go new file mode 100644 index 00000000..bb0c04b0 --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + if !instance.Supports(sandbox.LifecycleCapabilityDelete) { + log.Fatalf("sandbox %s does not support delete", instance.ID()) + } + if err := instance.Delete(context.Background(), &sandbox.DeleteOptions{Wait: sandbox.Value(true)}); err != nil { + log.Fatal(err) + } + info := instance.Info() + fmt.Printf("Blaxel sandbox deleted: %s (state=%s, terminal=%t)\n", instance.ID(), info.State, info.GetTerminal()) +} diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md new file mode 100644 index 00000000..9c20ab2c --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md @@ -0,0 +1,45 @@ +# Get a Blaxel sandbox + +Look up an existing Blaxel sandbox by name and print metadata. + +## What this means + +`client.Get` loads the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_NAME`. + +## Edge cases + +- Unknown names return not found. +- Attach is unsupported; use get. + +## Run + +```sh +cd examples/go/blaxel/lifecycle/get-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod new file mode 100644 index 00000000..b8c6d9bc --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/get-blaxel-sandbox + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.sum b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go new file mode 100644 index 00000000..1afe0a65 --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + info := instance.Info() + fmt.Printf("Blaxel sandbox: id=%s state=%s terminal=%v capabilities=%v\n", info.Id, info.State, info.GetTerminal(), instance.Capabilities()) +} diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md new file mode 100644 index 00000000..e77aa5ca --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md @@ -0,0 +1,44 @@ +# Refresh a Blaxel sandbox + +Refresh metadata for an existing Blaxel sandbox by name. + +## What this means + +`Refresh` updates `Info()` on this handle (state, labels, and related metadata). + +## Edge cases + +- If this handle does not support refresh, you get `Unsupported`. + +## Run + +```sh +cd examples/go/blaxel/lifecycle/refresh-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod new file mode 100644 index 00000000..813266ea --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/refresh-blaxel-sandbox + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.sum b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go new file mode 100644 index 00000000..1a64936d --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + if !instance.Supports(sandbox.LifecycleCapabilityRefresh) { + log.Fatalf("sandbox %s does not support refresh", instance.ID()) + } + if err := instance.Refresh(context.Background()); err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel metadata refreshed: %s (%s)\n", instance.ID(), instance.Info().State) +} diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/.env.example b/examples/go/blaxel/process/run-blaxel-check-exit/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/README.md b/examples/go/blaxel/process/run-blaxel-check-exit/README.md new file mode 100644 index 00000000..d522867b --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/README.md @@ -0,0 +1,44 @@ +# Check a Blaxel non-zero exit + +Run a command that exits non-zero and read the exit code from the result. + +## What this means + +A non-zero exit is not an SDK error. Check `GetExitCode` on the result. + +## Edge cases + +- Transport and validation failures still raise errors. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-check-exit +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-check-exit/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/go.mod b/examples/go/blaxel/process/run-blaxel-check-exit/go.mod new file mode 100644 index 00000000..0e4377fa --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-check-exit + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/go.sum b/examples/go/blaxel/process/run-blaxel-check-exit/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/main.go b/examples/go/blaxel/process/run-blaxel-check-exit/main.go new file mode 100644 index 00000000..0ff4bc7f --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Shell: sandbox.Value("exit 7"), + }) + if err != nil { + log.Fatal(err) + } + if result.GetExitCode() != 7 { + log.Fatalf("want exit 7, got %d", result.GetExitCode()) + } + fmt.Printf("command finished with exit=%d\n", result.GetExitCode()) +} diff --git a/examples/go/blaxel/process/run-blaxel-command/.env.example b/examples/go/blaxel/process/run-blaxel-command/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-command/README.md b/examples/go/blaxel/process/run-blaxel-command/README.md new file mode 100644 index 00000000..b6589b83 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/README.md @@ -0,0 +1,45 @@ +# Run a Blaxel command + +Run one argv command in a Blaxel sandbox and print exit code plus streams. + +## What this means + +`Process().Run` waits until the command finishes. Set exactly one of `Argv` or `Shell`. `Argv` is sent as one quoted shell command string. + +## Edge cases + +- Non-zero exit codes are success for the SDK—read `exitCode`. +- Blaxel returns stdout and stderr. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-command +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-command/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-command/go.mod b/examples/go/blaxel/process/run-blaxel-command/go.mod new file mode 100644 index 00000000..b0f30281 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-command + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-command/go.sum b/examples/go/blaxel/process/run-blaxel-command/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-command/main.go b/examples/go/blaxel/process/run-blaxel-command/main.go new file mode 100644 index 00000000..1744518b --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"echo", "hello from kit"}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d stdout=%q stderr=%q\n", result.GetExitCode(), string(result.GetStdout()), string(result.GetStderr())) +} diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/.env.example b/examples/go/blaxel/process/run-blaxel-shell-command/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/README.md b/examples/go/blaxel/process/run-blaxel-shell-command/README.md new file mode 100644 index 00000000..057bf252 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/README.md @@ -0,0 +1,44 @@ +# Run a Blaxel shell command + +Run one shell string in a Blaxel sandbox. + +## What this means + +Uses `shell` instead of `argv`. Exactly one of the two must be set. + +## Edge cases + +- Setting both argv and shell is rejected. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-shell-command +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-shell-command/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/go.mod b/examples/go/blaxel/process/run-blaxel-shell-command/go.mod new file mode 100644 index 00000000..03c4b15d --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-shell-command + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/go.sum b/examples/go/blaxel/process/run-blaxel-shell-command/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/main.go b/examples/go/blaxel/process/run-blaxel-shell-command/main.go new file mode 100644 index 00000000..66d543b1 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Shell: sandbox.Value(`echo "hello from shell"`), + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d stdout=%q\n", result.GetExitCode(), string(result.GetStdout())) +} diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example b/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example new file mode 100644 index 00000000..21c42a00 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Working directory inside the sandbox. +BL_WORKING_DIRECTORY=/tmp diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/README.md b/examples/go/blaxel/process/run-blaxel-with-cwd/README.md new file mode 100644 index 00000000..470363d1 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/README.md @@ -0,0 +1,46 @@ +# Run a Blaxel command with cwd + +Run a command with an explicit working directory. + +## What this means + +Sets `workingDirectory` / `WorkingDirectory` for the process. + +## Edge cases + +- Paths in the sandbox are absolute. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-with-cwd +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Working directory inside the sandbox. +BL_WORKING_DIRECTORY=/tmp +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-with-cwd/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod b/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod new file mode 100644 index 00000000..b7cd2e64 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-with-cwd + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/go.sum b/examples/go/blaxel/process/run-blaxel-with-cwd/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/main.go b/examples/go/blaxel/process/run-blaxel-with-cwd/main.go new file mode 100644 index 00000000..7ffe6c29 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + cwd := os.Getenv("BL_WORKING_DIRECTORY") + if cwd == "" { + cwd = "/tmp" + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"pwd"}, + WorkingDirectory: sandbox.Value(cwd), + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d cwd_out=%q\n", result.GetExitCode(), string(result.GetStdout())) +} diff --git a/examples/go/blaxel/process/run-blaxel-with-env/.env.example b/examples/go/blaxel/process/run-blaxel-with-env/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-with-env/README.md b/examples/go/blaxel/process/run-blaxel-with-env/README.md new file mode 100644 index 00000000..fbddcf20 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/README.md @@ -0,0 +1,44 @@ +# Run a Blaxel command with env + +Run a command with environment variables for that process. + +## What this means + +Process environment is separate from create-time sandbox environment. + +## Edge cases + +- Invalid names follow the same env-name rules as create. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-with-env +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-with-env/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-with-env/go.mod b/examples/go/blaxel/process/run-blaxel-with-env/go.mod new file mode 100644 index 00000000..45d02ac6 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-with-env + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-with-env/go.sum b/examples/go/blaxel/process/run-blaxel-with-env/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-with-env/main.go b/examples/go/blaxel/process/run-blaxel-with-env/main.go new file mode 100644 index 00000000..c0c12d75 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"printenv", "KIT_DEMO"}, + Environment: map[string]string{"KIT_DEMO": "from-kit"}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d stdout=%q\n", result.GetExitCode(), string(result.GetStdout())) +} diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example b/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md new file mode 100644 index 00000000..fe03fe1b --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md @@ -0,0 +1,47 @@ +# Run a Blaxel command with timeout + +Cap a Blaxel `Process().Run` with a whole-second timeout. + +## What this means + +Command timeouts must be whole seconds. This run uses 5 seconds and caps how +long the command may run. Omit or zero waits until the command ends (or you +cancel), with no Blaxel process time limit. + +## Edge cases + +- `1500` ms is rejected; `2000` is two seconds. +- Canceling the local wait does not guarantee the remote command stops. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-with-timeout +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-with-timeout/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod b/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod new file mode 100644 index 00000000..b5a90420 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-with-timeout + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/go.sum b/examples/go/blaxel/process/run-blaxel-with-timeout/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/main.go b/examples/go/blaxel/process/run-blaxel-with-timeout/main.go new file mode 100644 index 00000000..b6bd9ca8 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"echo", "hello with timeout"}, + Timeout: sandbox.Duration(5 * time.Second), + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d stdout=%q stderr=%q\n", result.GetExitCode(), string(result.GetStdout()), string(result.GetStderr())) +} diff --git a/examples/typescript/README.md b/examples/typescript/README.md index 2e3e90b1..03daac26 100644 --- a/examples/typescript/README.md +++ b/examples/typescript/README.md @@ -20,10 +20,16 @@ npm run build npm start ``` -Creation examples provision real cloud resources. Filesystem, process, and lifecycle -examples act on the sandbox ID in `.env`. Closing a handle leaves the sandbox as it is. Closing -the client closes your connection to Daytona or Modal. Modal setup: +`npm start` loads only that project’s `.env` via `--env-file-if-exists` (it +does not override existing shell env). There is no provider-level `.env`. +Creation examples provision real cloud resources. Filesystem, process, and +lifecycle examples use the sandbox id in `.env` (`DAYTONA_SANDBOX_ID`, +`MODAL_SANDBOX_ID`, or Blaxel `BL_SANDBOX_NAME` — Blaxel id is the create name). +Closing a handle leaves the sandbox as it is. Closing the client closes your +connection to Daytona, Modal, or Blaxel. Modal setup: [create-modal-sandbox](modal/configuration/create-modal-sandbox/README.md). +Blaxel needs an API key, workspace, and client region: +[create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). ## Catalog @@ -112,6 +118,38 @@ the client closes your connection to Daytona or Modal. Modal setup: | [request-modal-termination](modal/lifecycle/request-modal-termination/README.md) | non-waited termination | | [close-modal-handle](modal/lifecycle/close-modal-handle/README.md) | close the handle; sandbox keeps running | +### Blaxel + +| Project | Demonstrates | +| -------------------------------------------------------------------------------------------- | --------------------------------------- | +| [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md) | named image create | +| [create-blaxel-with-resources](blaxel/configuration/create-blaxel-with-resources/README.md) | memory MiB | +| [create-blaxel-with-labels](blaxel/configuration/create-blaxel-with-labels/README.md) | labels and environment | +| [create-blaxel-with-ttl](blaxel/configuration/create-blaxel-with-ttl/README.md) | maximum lifetime TTL | +| [create-blaxel-idle](blaxel/configuration/create-blaxel-idle/README.md) | idle delete policy | +| [create-blaxel-client-timeout](blaxel/configuration/create-blaxel-client-timeout/README.md) | client timeout | +| [create-blaxel-with-ports](blaxel/configuration/create-blaxel-with-ports/README.md) | published ports | +| [create-blaxel-with-allowlists](blaxel/configuration/create-blaxel-with-allowlists/README.md) | outbound domains | +| [run-blaxel-command](blaxel/process/run-blaxel-command/README.md) | run one command | +| [run-blaxel-shell-command](blaxel/process/run-blaxel-shell-command/README.md) | run one shell string | +| [run-blaxel-with-cwd](blaxel/process/run-blaxel-with-cwd/README.md) | run with working directory | +| [run-blaxel-with-env](blaxel/process/run-blaxel-with-env/README.md) | run with environment variables | +| [run-blaxel-with-timeout](blaxel/process/run-blaxel-with-timeout/README.md) | run with a whole-second timeout | +| [run-blaxel-check-exit](blaxel/process/run-blaxel-check-exit/README.md) | non-zero exit in the result | +| [read-blaxel-file](blaxel/filesystem/read-blaxel-file/README.md) | read one file | +| [write-blaxel-file](blaxel/filesystem/write-blaxel-file/README.md) | write one file | +| [copy-file-into-blaxel](blaxel/filesystem/copy-file-into-blaxel/README.md) | copy a local file in | +| [copy-file-from-blaxel](blaxel/filesystem/copy-file-from-blaxel/README.md) | copy a sandbox file out | +| [make-blaxel-directory](blaxel/filesystem/make-blaxel-directory/README.md) | create a directory | +| [list-blaxel-directory](blaxel/filesystem/list-blaxel-directory/README.md) | list one directory | +| [stat-blaxel-file](blaxel/filesystem/stat-blaxel-file/README.md) | stat one path | +| [remove-blaxel-file](blaxel/filesystem/remove-blaxel-file/README.md) | remove one path | +| [get-blaxel-sandbox](blaxel/lifecycle/get-blaxel-sandbox/README.md) | remote retrieval and metadata | +| [refresh-blaxel-sandbox](blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | metadata refresh | +| [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | delete by name | +| [close-blaxel-handle](blaxel/lifecycle/close-blaxel-handle/README.md) | close the handle; sandbox keeps running | +| [handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) | typed validation errors | + ## Verify all TypeScript projects From `tooling/`: diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md new file mode 100644 index 00000000..97cc74ce --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md @@ -0,0 +1,44 @@ +# Create a Blaxel sandbox with a client timeout + +Create one Blaxel sandbox using a shorter client-wide timeout. + +## What this means + +Sets `Config.Timeout` / `timeout` on the shared client. This bounds local waiting for SDK calls, including create. + +## Edge cases + +- Client `timeout` is how long create may take. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-client-timeout +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-client-timeout/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json new file mode 100644 index 00000000..f3bf6417 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-client-timeout", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-client-timeout", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package.json b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package.json new file mode 100644 index 00000000..eb5b69d4 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-client-timeout", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts new file mode 100644 index 00000000..2d2852c7 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 90000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-client-timeout", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(`Blaxel sandbox created with client timeout: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-idle/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-idle/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md b/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md new file mode 100644 index 00000000..ff9b7d40 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with idle delete + +Create one Blaxel sandbox that deletes after an idle period. + +## What this means + +Set `lifetime.idleTerminate` with `after` and a delay (≥1 minute). The sandbox deletes after that idle period. You cannot disable idle terminate. + +## Edge cases + +- Disabling idle terminate is not representable and is rejected. +- Minimum idle duration follows Blaxel rules (1 minute). + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-idle +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-idle/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json new file mode 100644 index 00000000..d1f6872c --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-idle", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-idle", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/package.json b/examples/typescript/blaxel/configuration/create-blaxel-idle/package.json new file mode 100644 index 00000000..40cbe33f --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-idle", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts new file mode 100644 index 00000000..4a008396 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client, PolicyMode } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-idle", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { idleTerminate: { mode: PolicyMode.After, after: 60_000 } }, +}); +console.log(`Blaxel sandbox created with 60s idle delete: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-idle/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md new file mode 100644 index 00000000..f0fdc4d4 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md @@ -0,0 +1,50 @@ +# Create a Blaxel sandbox + +Create one Blaxel sandbox from a registry image with an explicit name. Prints the sandbox ID and leaves the sandbox running. + +## What this means + +Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, +at most 49 characters), an image, and client `region` (examples read +`BL_REGION`, default `us-pdx-1`). If the name already exists, you get +`AlreadyExists`. Closing the client does not delete the sandbox. + +## Edge cases + +- Omitting `name` or `source.image`, or using an invalid name, fails before + Blaxel is called. +- A create error is not proof that no sandbox was provisioned. +- Reclaim sandboxes with TTL / idle policies or `delete`. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-sandbox/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json new file mode 100644 index 00000000..86f24529 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-sandbox", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-sandbox", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package.json b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package.json new file mode 100644 index 00000000..377374ac --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-sandbox", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts new file mode 100644 index 00000000..9fc61cf8 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-sandbox", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(`Blaxel sandbox created: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md new file mode 100644 index 00000000..58aacdef --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with outbound domains + +Create one Blaxel sandbox that allows selected outbound domains. + +## What this means + +Set `network.outboundDomains` to allow those hosts. Other egress controls are unsupported on Blaxel. + +## Edge cases + +- Restricted egress modes and CIDR allowlists are rejected. +- Domain entries follow Blaxel validation. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-allowlists +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-allowlists/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json new file mode 100644 index 00000000..588555c2 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-allowlists", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-allowlists", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package.json new file mode 100644 index 00000000..633d5941 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-allowlists", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts new file mode 100644 index 00000000..3bb81fef --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-allowlists", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { outboundDomains: ["example.com"] }, +}); +console.log(`Blaxel sandbox created with outbound domains: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md new file mode 100644 index 00000000..f52e161b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md @@ -0,0 +1,46 @@ +# Create a Blaxel sandbox with labels and env + +Create one Blaxel sandbox with labels and environment variables. + +## What this means + +Set `labels` and create-time environment variables. Environment variables are +available inside the sandbox. `info().labels` is set only when Blaxel reports +labels back — Kit does not copy them from the create request. + +## Edge cases + +- Invalid environment names are rejected before Blaxel is called. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-labels +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-labels/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json new file mode 100644 index 00000000..69288462 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-labels", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-labels", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package.json new file mode 100644 index 00000000..769ad97e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-labels", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts new file mode 100644 index 00000000..dc5f6ba4 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts @@ -0,0 +1,21 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-labels", + source: { image: { reference: "blaxel/base-image:latest" } }, + labels: { team: "platform", purpose: "kit-example" }, + environment: { NODE_ENV: "development" }, +}); +console.log(`Blaxel sandbox created with labels and environment: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md new file mode 100644 index 00000000..b03a2b12 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md @@ -0,0 +1,47 @@ +# Create a Blaxel sandbox with ports + +Create one Blaxel sandbox that publishes selected ports. + +## What this means + +Publish ports with `network.ports`. Default protocol is HTTP; `plain` is TCP; +`tls` is TLS. HTTP/2 over TLS is not supported. + +## Edge cases + +- Other network settings (egress mode, CIDRs, private network, proxies) are rejected. +- Port numbers must be in range. +- Blaxel reserves ports 80, 443, and 8080; this example uses 3000 and 3001. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-ports +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-ports/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json new file mode 100644 index 00000000..8a6653ba --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-ports", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-ports", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package.json new file mode 100644 index 00000000..0d9ec92c --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-ports", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts new file mode 100644 index 00000000..7ddf0d00 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts @@ -0,0 +1,25 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client, PortTransport } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-ports", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { + ports: [ + { port: 3000, transport: PortTransport.Tls }, + { port: 3001, transport: PortTransport.Plain }, + ], + }, +}); +console.log(`Blaxel sandbox created with ports: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md new file mode 100644 index 00000000..6cf1ed43 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md @@ -0,0 +1,44 @@ +# Create a Blaxel sandbox with memory + +Create one Blaxel sandbox with an explicit memory size in MiB. Blaxel derives CPU from memory. + +## What this means + +You can set `resources.memoryMib` only. CPU cores, limits, and disk overrides are rejected. Omit memory to use Blaxel's default. + +## Edge cases + +- Memory values that Blaxel rejects are returned as provider errors. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-resources +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-resources/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json new file mode 100644 index 00000000..879556fc --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-resources", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-resources", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package.json new file mode 100644 index 00000000..d25ac342 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-resources", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts new file mode 100644 index 00000000..8f26b1f8 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-resources", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, +}); +console.log(`Blaxel sandbox created with 4096 MiB: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md new file mode 100644 index 00000000..681637f8 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with maximum lifetime + +Create one Blaxel sandbox that deletes after a maximum lifetime TTL. + +## What this means + +Set `lifetime.maximumLifetime` (whole seconds, at least 5 minutes). The sandbox deletes when that max age elapses. + +## Edge cases + +- Fractional-second durations are rejected. +- Idle cleanup uses `idleTerminate`, not maximum lifetime. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-ttl +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-ttl/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json new file mode 100644 index 00000000..085c15c9 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-ttl", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-ttl", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package.json new file mode 100644 index 00000000..9ce978e9 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-ttl", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts new file mode 100644 index 00000000..4b21c1af --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-ttl", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { maximumLifetime: 60 * 60_000 }, +}); +console.log(`Blaxel sandbox created with 60-minute maximum lifetime: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/.env.example b/examples/typescript/blaxel/errors/handle-blaxel-errors/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/.gitignore b/examples/typescript/blaxel/errors/handle-blaxel-errors/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md b/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md new file mode 100644 index 00000000..0bc9b30c --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md @@ -0,0 +1,45 @@ +# Handle Blaxel validation errors + +Demonstrate typed validation errors from the Blaxel provider without creating a sandbox. + +## What this means + +Requests an unsupported CPU override so validation fails before any cloud call. Inspect `SandboxError` `kind` and `field`. + +## Edge cases + +- Does not provision cloud resources when validation fails as expected. +- Authentication still requires a configured client. + +## Run + +```sh +cd examples/typescript/blaxel/errors/handle-blaxel-errors +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Validation fails locally for the unsupported CPU field; no sandbox is created. +- Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/errors/handle-blaxel-errors/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json b/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json new file mode 100644 index 00000000..1cece427 --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-handle-blaxel-errors", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-handle-blaxel-errors", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/package.json b/examples/typescript/blaxel/errors/handle-blaxel-errors/package.json new file mode 100644 index 00000000..1b2cb966 --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-handle-blaxel-errors", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts b/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts new file mode 100644 index 00000000..ef5f693a --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts @@ -0,0 +1,30 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client, SandboxError } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +try { + await client.create({ + name: "kit-handle-blaxel-errors", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { cpuCores: 2 }, + }); + console.log("Sandbox created."); +} catch (error) { + if (error instanceof SandboxError) { + console.log( + `${error.kind}: ${error.message} (field=${error.field || "none"}, operation=${error.operation})`, + ); + } else { + throw error; + } +} diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/tsconfig.json b/examples/typescript/blaxel/errors/handle-blaxel-errors/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example new file mode 100644 index 00000000..d6ad3fe0 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.gitignore b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md new file mode 100644 index 00000000..2f350f3b --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md @@ -0,0 +1,50 @@ +# Copy a file from Blaxel + +Copy a sandbox file out to a local path. + +## What this means + +`copyToLocal` creates a missing local parent directory. + +## Edge cases + +- Remote path must exist. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/copy-file-from-blaxel +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/copy-file-from-blaxel/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json new file mode 100644 index 00000000..7e8566d0 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-copy-file-from-blaxel", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-copy-file-from-blaxel", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package.json b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package.json new file mode 100644 index 00000000..02e0c2c4 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-copy-file-from-blaxel", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts new file mode 100644 index 00000000..8697f796 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const remote = process.env.BL_REMOTE_PATH!; +const local = process.env.BL_LOCAL_PATH!; +await sandbox.filesystem.copyToLocal(remote, local); +console.log(`copied ${remote} to ${local}`); diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/tsconfig.json b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example new file mode 100644 index 00000000..d6ad3fe0 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.gitignore b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md new file mode 100644 index 00000000..13569e8e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md @@ -0,0 +1,50 @@ +# Copy a file into Blaxel + +Copy a local file into a Blaxel sandbox path. + +## What this means + +A missing local file is not found before Blaxel is called (`local_path`). A local directory is an invalid argument. + +## Edge cases + +- Create the local file before running. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/copy-file-into-blaxel +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/copy-file-into-blaxel/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json new file mode 100644 index 00000000..2b440fea --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-copy-file-into-blaxel", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-copy-file-into-blaxel", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package.json b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package.json new file mode 100644 index 00000000..a01a2a74 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-copy-file-into-blaxel", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts new file mode 100644 index 00000000..2be83ebc --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const remote = process.env.BL_REMOTE_PATH!; +const local = process.env.BL_LOCAL_PATH!; +await sandbox.filesystem.copyFromLocal(local, remote); +console.log(`copied ${local} to ${remote}`); diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/tsconfig.json b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example new file mode 100644 index 00000000..f6619de3 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/.gitignore b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md b/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md new file mode 100644 index 00000000..54cacc13 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md @@ -0,0 +1,48 @@ +# List a Blaxel directory + +List entries in one absolute directory path. + +## What this means + +Returns shared directory entries with names and types. + +## Edge cases + +- Missing directories return not found. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/list-blaxel-directory +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/list-blaxel-directory/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json new file mode 100644 index 00000000..51e4a86b --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-list-blaxel-directory", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-list-blaxel-directory", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/package.json b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package.json new file mode 100644 index 00000000..ac2b11cb --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-list-blaxel-directory", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts b/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts new file mode 100644 index 00000000..295c5c53 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts @@ -0,0 +1,21 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const remote = process.env.BL_REMOTE_PATH!; +const entries = await sandbox.filesystem.listDirectory(remote); +console.log(`${remote} (${entries.length} entries)`); +for (const entry of entries) { + console.log(entry.name); +} diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/tsconfig.json b/examples/typescript/blaxel/filesystem/list-blaxel-directory/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example new file mode 100644 index 00000000..3ec608ba --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example-dir diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/.gitignore b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md b/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md new file mode 100644 index 00000000..a732cda2 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md @@ -0,0 +1,48 @@ +# Make a Blaxel directory + +Create a directory at an absolute path in a Blaxel sandbox. + +## What this means + +`parents` defaults to true. + +## Edge cases + +- Absolute paths only. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/make-blaxel-directory +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example-dir +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/make-blaxel-directory/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json new file mode 100644 index 00000000..8eb08d74 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-make-blaxel-directory", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-make-blaxel-directory", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/package.json b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package.json new file mode 100644 index 00000000..c30302b9 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-make-blaxel-directory", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts b/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts new file mode 100644 index 00000000..ecf95dce --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const remote = process.env.BL_REMOTE_PATH!; +await sandbox.filesystem.makeDirectory(remote); +console.log(`created ${remote}`); diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/tsconfig.json b/examples/typescript/blaxel/filesystem/make-blaxel-directory/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example new file mode 100644 index 00000000..f7524e0d --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/.gitignore b/examples/typescript/blaxel/filesystem/read-blaxel-file/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md new file mode 100644 index 00000000..bf3fee26 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md @@ -0,0 +1,48 @@ +# Read a Blaxel file + +Read one absolute path from a Blaxel sandbox. + +## What this means + +Uses `filesystem.readFile` / `ReadFile`. Paths in the sandbox are absolute. + +## Edge cases + +- Missing files return not found from Blaxel. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/read-blaxel-file +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/read-blaxel-file/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json new file mode 100644 index 00000000..202ad343 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-read-blaxel-file", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-read-blaxel-file", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/package.json b/examples/typescript/blaxel/filesystem/read-blaxel-file/package.json new file mode 100644 index 00000000..cf353020 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-read-blaxel-file", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts new file mode 100644 index 00000000..814e4550 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const remote = process.env.BL_REMOTE_PATH!; +const data = await sandbox.filesystem.readFile(remote); +console.log(`read ${remote} (${data.byteLength} bytes)`); diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/tsconfig.json b/examples/typescript/blaxel/filesystem/read-blaxel-file/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example new file mode 100644 index 00000000..de34add4 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Set true for recursive remove. +BL_RECURSIVE= diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/.gitignore b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md new file mode 100644 index 00000000..84c85513 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md @@ -0,0 +1,50 @@ +# Remove a Blaxel path + +Remove one absolute path in a Blaxel sandbox. + +## What this means + +Set `BL_RECURSIVE=true` to remove recursively when supported by options. + +## Edge cases + +- Removing a missing path returns not found. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/remove-blaxel-file +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Set true for recursive remove. +BL_RECURSIVE= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/remove-blaxel-file/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json new file mode 100644 index 00000000..e49aa9c1 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-remove-blaxel-file", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-remove-blaxel-file", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/package.json b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package.json new file mode 100644 index 00000000..ef817ced --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-remove-blaxel-file", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts new file mode 100644 index 00000000..de743181 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120_000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const remote = process.env.BL_REMOTE_PATH!; +const recursive = process.env.BL_RECURSIVE === "true"; +await sandbox.filesystem.remove(remote, recursive ? { recursive: true } : {}); +console.log(`removed ${remote}`); diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/tsconfig.json b/examples/typescript/blaxel/filesystem/remove-blaxel-file/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example new file mode 100644 index 00000000..f7524e0d --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/.gitignore b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md new file mode 100644 index 00000000..e0ac96e9 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md @@ -0,0 +1,50 @@ +# Stat a Blaxel path + +Stat one absolute path in a Blaxel sandbox. + +## What this means + +`stat` returns `type` (file or directory), `size`, and usually `permissions`, +`owner`, `group`, and `modifiedAt`. `name` and `path` are whatever Blaxel +reports. Size may be zero when Blaxel does not return it. + +## Edge cases + +- Missing paths return not found. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/stat-blaxel-file +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/stat-blaxel-file/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json new file mode 100644 index 00000000..eaad3cfb --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-stat-blaxel-file", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-stat-blaxel-file", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/package.json b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package.json new file mode 100644 index 00000000..8e3925bd --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-stat-blaxel-file", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts new file mode 100644 index 00000000..afe0d2d8 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const remote = process.env.BL_REMOTE_PATH!; +const info = await sandbox.filesystem.stat(remote); +console.log(`${info.path} type=${info.type} size=${info.size}`); diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/tsconfig.json b/examples/typescript/blaxel/filesystem/stat-blaxel-file/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example new file mode 100644 index 00000000..f7524e0d --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/.gitignore b/examples/typescript/blaxel/filesystem/write-blaxel-file/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md new file mode 100644 index 00000000..0acbe27a --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md @@ -0,0 +1,48 @@ +# Write a Blaxel file + +Write one absolute path in a Blaxel sandbox. + +## What this means + +`parents` defaults to true. `parents: false` is rejected. + +## Edge cases + +- Absolute remote paths only. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/write-blaxel-file +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/write-blaxel-file/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json new file mode 100644 index 00000000..1d5836e7 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-write-blaxel-file", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-write-blaxel-file", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/package.json b/examples/typescript/blaxel/filesystem/write-blaxel-file/package.json new file mode 100644 index 00000000..a354570e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-write-blaxel-file", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts new file mode 100644 index 00000000..175905cd --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const remote = process.env.BL_REMOTE_PATH!; +const body = new TextEncoder().encode("hello from sandbox-kit\n"); +await sandbox.filesystem.writeFile(remote, body); +console.log(`wrote ${remote} (${body.byteLength} bytes)`); diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/tsconfig.json b/examples/typescript/blaxel/filesystem/write-blaxel-file/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.gitignore b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md new file mode 100644 index 00000000..64cc8e85 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md @@ -0,0 +1,47 @@ +# Close a Blaxel sandbox handle + +Close one Blaxel sandbox handle without contacting Blaxel or changing the sandbox. + +## What this means + +`Close` releases local handle resources only. The sandbox keeps running until TTL, idle policy, or delete. + +## Edge cases + +- After close, further handle calls fail locally. +- Client close also does not delete sandboxes. + +## Run + +```sh +cd examples/typescript/blaxel/lifecycle/close-blaxel-handle +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/lifecycle/close-blaxel-handle/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json new file mode 100644 index 00000000..61dfdf22 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-close-blaxel-handle", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-close-blaxel-handle", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package.json b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package.json new file mode 100644 index 00000000..17a18c8f --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-close-blaxel-handle", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts new file mode 100644 index 00000000..49a03bb3 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +await sandbox.close(); +console.log( + `Closed the handle for Blaxel sandbox ${sandbox.id}. The sandbox was not changed.`, +); diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/tsconfig.json b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.gitignore b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md new file mode 100644 index 00000000..c9762a32 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -0,0 +1,52 @@ +# Delete a Blaxel sandbox + +Delete an existing Blaxel sandbox by name. + +## What this means + +`delete` permanently removes the Blaxel sandbox and returns `SandboxInfo`. +On success, `state` is `deleted` and `terminal` is true. This example uses +`wait: true` so removal finishes before the call returns. Omit `wait` to +return when Blaxel accepts the delete — recreating the same name too soon may +return `Conflict`. Start, stop, pause, archive, and terminate are not +available. + +## Edge cases + +- Deletion destroys the sandbox and its filesystem. +- Closing the handle is not deletion. + +## Run + +```sh +cd examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/lifecycle/delete-blaxel-sandbox/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json new file mode 100644 index 00000000..10d8a487 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-delete-blaxel-sandbox", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-delete-blaxel-sandbox", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package.json b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package.json new file mode 100644 index 00000000..11cd3414 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-delete-blaxel-sandbox", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts new file mode 100644 index 00000000..0fee1d1d --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts @@ -0,0 +1,33 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { + Client, + LifecycleCapability, + type Sandbox, +} from "@sandbox-kit/sandbox"; + +function assertCapability( + sandbox: Sandbox, + capability: LifecycleCapability, +): void { + if (!sandbox.supports(capability)) { + throw new Error(`sandbox ${sandbox.id} does not support ${capability}`); + } +} + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120_000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +assertCapability(sandbox, LifecycleCapability.Delete); +const info = await sandbox.delete({ wait: true }); +console.log( + `Blaxel sandbox deleted: ${sandbox.id} (state=${info.state}, terminal=${info.terminal ?? false})`, +); diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/tsconfig.json b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.gitignore b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md new file mode 100644 index 00000000..c5dadaa5 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md @@ -0,0 +1,47 @@ +# Get a Blaxel sandbox + +Look up an existing Blaxel sandbox by name and print metadata. + +## What this means + +`client.get` loads the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_NAME`. + +## Edge cases + +- Unknown names return not found. +- Attach is unsupported; use get. + +## Run + +```sh +cd examples/typescript/blaxel/lifecycle/get-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/lifecycle/get-blaxel-sandbox/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json new file mode 100644 index 00000000..7e7beaf1 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-get-blaxel-sandbox", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-get-blaxel-sandbox", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package.json b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package.json new file mode 100644 index 00000000..fdf65730 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-get-blaxel-sandbox", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts new file mode 100644 index 00000000..da1c533b --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120_000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const info = sandbox.info(); +console.log( + `Blaxel sandbox: id=${info.id} state=${info.state} terminal=${info.terminal ?? false} capabilities=${sandbox.capabilities().join(",")}`, +); diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/tsconfig.json b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.gitignore b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md new file mode 100644 index 00000000..fc8fcfac --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md @@ -0,0 +1,46 @@ +# Refresh a Blaxel sandbox + +Refresh metadata for an existing Blaxel sandbox by name. + +## What this means + +`sandbox.refresh` updates `info()` on this handle (state, labels, and related metadata). + +## Edge cases + +- If this handle does not support refresh, you get `Unsupported`. + +## Run + +```sh +cd examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json new file mode 100644 index 00000000..e6ca5b65 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-refresh-blaxel-sandbox", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-refresh-blaxel-sandbox", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package.json b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package.json new file mode 100644 index 00000000..e617e356 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-refresh-blaxel-sandbox", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts new file mode 100644 index 00000000..ae956e10 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts @@ -0,0 +1,31 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { + Client, + LifecycleCapability, + type Sandbox, +} from "@sandbox-kit/sandbox"; + +function assertCapability( + sandbox: Sandbox, + capability: LifecycleCapability, +): void { + if (!sandbox.supports(capability)) { + throw new Error(`sandbox ${sandbox.id} does not support ${capability}`); + } +} + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120_000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +assertCapability(sandbox, LifecycleCapability.Refresh); +const info = await sandbox.refresh(); +console.log(`Blaxel metadata refreshed: ${sandbox.id} (${info.state})`); diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/tsconfig.json b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example b/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/.gitignore b/examples/typescript/blaxel/process/run-blaxel-check-exit/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md b/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md new file mode 100644 index 00000000..d6519273 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md @@ -0,0 +1,46 @@ +# Check a Blaxel non-zero exit + +Run a command that exits non-zero and read the exit code from the result. + +## What this means + +A non-zero exit is not an SDK error. Check `exitCode` on the result. + +## Edge cases + +- Transport and validation failures still raise errors. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-check-exit +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-check-exit/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json new file mode 100644 index 00000000..419ce498 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-check-exit", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-check-exit", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/package.json b/examples/typescript/blaxel/process/run-blaxel-check-exit/package.json new file mode 100644 index 00000000..55cfeeef --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-check-exit", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts new file mode 100644 index 00000000..74fd4914 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const result = await sandbox.process.run({ shell: "exit 7" }); +if (result.exitCode !== 7) { + throw new Error(`want exit 7, got ${result.exitCode}`); +} +console.log(`command finished with exit=${result.exitCode}`); diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-check-exit/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-command/.env.example b/examples/typescript/blaxel/process/run-blaxel-command/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-command/.gitignore b/examples/typescript/blaxel/process/run-blaxel-command/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-command/README.md b/examples/typescript/blaxel/process/run-blaxel-command/README.md new file mode 100644 index 00000000..bd77ba12 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/README.md @@ -0,0 +1,47 @@ +# Run a Blaxel command + +Run one argv command in a Blaxel sandbox and print exit code plus streams. + +## What this means + +`sandbox.process.run` waits until the command finishes. Set exactly one of `argv` or `shell`. `argv` is sent as one quoted shell command string. + +## Edge cases + +- Non-zero exit codes are success for the SDK—read `exitCode`. +- Blaxel returns stdout and stderr. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-command +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-command/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json new file mode 100644 index 00000000..14fb2063 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-command", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-command", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-command/package.json b/examples/typescript/blaxel/process/run-blaxel-command/package.json new file mode 100644 index 00000000..9da6cb8c --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-command", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts new file mode 100644 index 00000000..524c167c --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts @@ -0,0 +1,21 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const result = await sandbox.process.run({ + argv: ["echo", "hello from kit"], +}); +console.log( + `exit=${result.exitCode} stdout=${JSON.stringify(new TextDecoder().decode(result.stdout))} stderr=${JSON.stringify(new TextDecoder().decode(result.stderr))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-command/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-command/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example b/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/.gitignore b/examples/typescript/blaxel/process/run-blaxel-shell-command/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md b/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md new file mode 100644 index 00000000..478bb9a4 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md @@ -0,0 +1,46 @@ +# Run a Blaxel shell command + +Run one shell string in a Blaxel sandbox. + +## What this means + +Uses `shell` instead of `argv`. Exactly one of the two must be set. + +## Edge cases + +- Setting both argv and shell is rejected. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-shell-command +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-shell-command/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json new file mode 100644 index 00000000..ac26f8bd --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-shell-command", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-shell-command", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/package.json b/examples/typescript/blaxel/process/run-blaxel-shell-command/package.json new file mode 100644 index 00000000..08817058 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-shell-command", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts new file mode 100644 index 00000000..d3777166 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts @@ -0,0 +1,21 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const result = await sandbox.process.run({ + shell: `echo "hello from shell"`, +}); +console.log( + `exit=${result.exitCode} stdout=${JSON.stringify(new TextDecoder().decode(result.stdout))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-shell-command/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example new file mode 100644 index 00000000..21c42a00 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Working directory inside the sandbox. +BL_WORKING_DIRECTORY=/tmp diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/.gitignore b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md b/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md new file mode 100644 index 00000000..25dd2509 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md @@ -0,0 +1,48 @@ +# Run a Blaxel command with cwd + +Run a command with an explicit working directory. + +## What this means + +Sets `workingDirectory` / `WorkingDirectory` for the process. + +## Edge cases + +- Paths in the sandbox are absolute. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-with-cwd +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +# Working directory inside the sandbox. +BL_WORKING_DIRECTORY=/tmp +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-with-cwd/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json new file mode 100644 index 00000000..6626b93d --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-cwd", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-with-cwd", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/package.json b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package.json new file mode 100644 index 00000000..7337ae61 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-cwd", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts new file mode 100644 index 00000000..c01a8d88 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts @@ -0,0 +1,23 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const cwd = process.env.BL_WORKING_DIRECTORY || "/tmp"; +const result = await sandbox.process.run({ + argv: ["pwd"], + workingDirectory: cwd, +}); +console.log( + `exit=${result.exitCode} cwd_out=${JSON.stringify(new TextDecoder().decode(result.stdout))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-with-cwd/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/.gitignore b/examples/typescript/blaxel/process/run-blaxel-with-env/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/README.md b/examples/typescript/blaxel/process/run-blaxel-with-env/README.md new file mode 100644 index 00000000..066ac151 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/README.md @@ -0,0 +1,46 @@ +# Run a Blaxel command with env + +Run a command with environment variables for that process. + +## What this means + +Process environment is separate from create-time sandbox environment. + +## Edge cases + +- Invalid names follow the same env-name rules as create. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-with-env +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-with-env/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json new file mode 100644 index 00000000..2ffcb55d --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-env", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-with-env", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/package.json b/examples/typescript/blaxel/process/run-blaxel-with-env/package.json new file mode 100644 index 00000000..fe2b4c19 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-env", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts new file mode 100644 index 00000000..5bf5675c --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts @@ -0,0 +1,22 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const result = await sandbox.process.run({ + argv: ["printenv", "KIT_DEMO"], + environment: { KIT_DEMO: "from-kit" }, +}); +console.log( + `exit=${result.exitCode} stdout=${JSON.stringify(new TextDecoder().decode(result.stdout))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-with-env/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example new file mode 100644 index 00000000..3470ef8a --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/.gitignore b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md new file mode 100644 index 00000000..440b2ea1 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md @@ -0,0 +1,49 @@ +# Run a Blaxel command with timeout + +Cap a Blaxel `sandbox.process.run` with a whole-second timeout. + +## What this means + +Command timeouts must be whole seconds. This run uses 5 seconds and caps how +long the command may run. Omit or zero waits until the command ends (or you +cancel), with no Blaxel process time limit. + +## Edge cases + +- `1500` ms is rejected; `2000` is two seconds. +- Canceling the local wait does not guarantee the remote command stops. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-with-timeout +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_NAME= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-with-timeout/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json new file mode 100644 index 00000000..bd73fb1f --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-timeout", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-with-timeout", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/package.json b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package.json new file mode 100644 index 00000000..1e5941b5 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-timeout", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts new file mode 100644 index 00000000..a08fc7c6 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts @@ -0,0 +1,22 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const result = await sandbox.process.run({ + argv: ["echo", "hello with timeout"], + timeout: 5_000, +}); +console.log( + `exit=${result.exitCode} stdout=${JSON.stringify(new TextDecoder().decode(result.stdout))} stderr=${JSON.stringify(new TextDecoder().decode(result.stderr))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-with-timeout/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/go.work b/go.work index 23e1fbab..aa6d76c9 100644 --- a/go.work +++ b/go.work @@ -1,8 +1,33 @@ go 1.26.0 use ( - ./examples/go/daytona/lifecycle/archive-daytona-sandbox - ./examples/go/daytona/lifecycle/close-daytona-handle + ./examples/go/blaxel/configuration/create-blaxel-client-timeout + ./examples/go/blaxel/configuration/create-blaxel-idle + ./examples/go/blaxel/configuration/create-blaxel-sandbox + ./examples/go/blaxel/configuration/create-blaxel-with-allowlists + ./examples/go/blaxel/configuration/create-blaxel-with-labels + ./examples/go/blaxel/configuration/create-blaxel-with-ports + ./examples/go/blaxel/configuration/create-blaxel-with-resources + ./examples/go/blaxel/configuration/create-blaxel-with-ttl + ./examples/go/blaxel/errors/handle-blaxel-errors + ./examples/go/blaxel/filesystem/copy-file-from-blaxel + ./examples/go/blaxel/filesystem/copy-file-into-blaxel + ./examples/go/blaxel/filesystem/list-blaxel-directory + ./examples/go/blaxel/filesystem/make-blaxel-directory + ./examples/go/blaxel/filesystem/read-blaxel-file + ./examples/go/blaxel/filesystem/remove-blaxel-file + ./examples/go/blaxel/filesystem/stat-blaxel-file + ./examples/go/blaxel/filesystem/write-blaxel-file + ./examples/go/blaxel/lifecycle/close-blaxel-handle + ./examples/go/blaxel/lifecycle/delete-blaxel-sandbox + ./examples/go/blaxel/lifecycle/get-blaxel-sandbox + ./examples/go/blaxel/lifecycle/refresh-blaxel-sandbox + ./examples/go/blaxel/process/run-blaxel-check-exit + ./examples/go/blaxel/process/run-blaxel-command + ./examples/go/blaxel/process/run-blaxel-shell-command + ./examples/go/blaxel/process/run-blaxel-with-cwd + ./examples/go/blaxel/process/run-blaxel-with-env + ./examples/go/blaxel/process/run-blaxel-with-timeout ./examples/go/daytona/configuration/create-daytona-ephemeral ./examples/go/daytona/configuration/create-daytona-from-image ./examples/go/daytona/configuration/create-daytona-from-snapshot @@ -15,18 +40,32 @@ use ( ./examples/go/daytona/configuration/create-daytona-with-runtime ./examples/go/daytona/configuration/create-daytona-with-secrets ./examples/go/daytona/configuration/create-daytona-with-ttl + ./examples/go/daytona/errors/handle-daytona-errors + ./examples/go/daytona/filesystem/copy-file-from-daytona + ./examples/go/daytona/filesystem/copy-file-into-daytona + ./examples/go/daytona/filesystem/list-daytona-directory + ./examples/go/daytona/filesystem/make-daytona-directory + ./examples/go/daytona/filesystem/read-daytona-file + ./examples/go/daytona/filesystem/remove-daytona-file + ./examples/go/daytona/filesystem/stat-daytona-file + ./examples/go/daytona/filesystem/write-daytona-file + ./examples/go/daytona/lifecycle/archive-daytona-sandbox + ./examples/go/daytona/lifecycle/close-daytona-handle ./examples/go/daytona/lifecycle/delete-daytona-sandbox ./examples/go/daytona/lifecycle/force-stop-daytona-sandbox ./examples/go/daytona/lifecycle/get-daytona-sandbox - ./examples/go/daytona/errors/handle-daytona-errors ./examples/go/daytona/lifecycle/pause-daytona-sandbox ./examples/go/daytona/lifecycle/refresh-daytona-sandbox ./examples/go/daytona/lifecycle/request-daytona-deletion ./examples/go/daytona/lifecycle/start-daytona-sandbox ./examples/go/daytona/lifecycle/start-daytona-with-timeout ./examples/go/daytona/lifecycle/stop-daytona-sandbox - ./examples/go/modal/lifecycle/attach-modal-sandbox - ./examples/go/modal/lifecycle/close-modal-handle + ./examples/go/daytona/process/run-daytona-check-exit + ./examples/go/daytona/process/run-daytona-command + ./examples/go/daytona/process/run-daytona-shell-command + ./examples/go/daytona/process/run-daytona-with-cwd + ./examples/go/daytona/process/run-daytona-with-env + ./examples/go/daytona/process/run-daytona-with-timeout ./examples/go/modal/configuration/create-modal-client-timeout ./examples/go/modal/configuration/create-modal-gvisor ./examples/go/modal/configuration/create-modal-idle @@ -45,23 +84,6 @@ use ( ./examples/go/modal/configuration/create-modal-with-runtime ./examples/go/modal/configuration/create-modal-with-secrets ./examples/go/modal/errors/handle-modal-errors - ./examples/go/modal/lifecycle/request-modal-termination - ./examples/go/modal/lifecycle/terminate-modal-sandbox - ./examples/go/modal/lifecycle/terminate-modal-with-timeout - ./examples/go/daytona/filesystem/copy-file-from-daytona - ./examples/go/daytona/filesystem/copy-file-into-daytona - ./examples/go/daytona/filesystem/list-daytona-directory - ./examples/go/daytona/filesystem/make-daytona-directory - ./examples/go/daytona/filesystem/read-daytona-file - ./examples/go/daytona/filesystem/remove-daytona-file - ./examples/go/daytona/filesystem/stat-daytona-file - ./examples/go/daytona/filesystem/write-daytona-file - ./examples/go/daytona/process/run-daytona-check-exit - ./examples/go/daytona/process/run-daytona-command - ./examples/go/daytona/process/run-daytona-shell-command - ./examples/go/daytona/process/run-daytona-with-cwd - ./examples/go/daytona/process/run-daytona-with-env - ./examples/go/daytona/process/run-daytona-with-timeout ./examples/go/modal/filesystem/copy-file-from-modal ./examples/go/modal/filesystem/copy-file-into-modal ./examples/go/modal/filesystem/list-modal-directory @@ -70,6 +92,11 @@ use ( ./examples/go/modal/filesystem/remove-modal-file ./examples/go/modal/filesystem/stat-modal-file ./examples/go/modal/filesystem/write-modal-file + ./examples/go/modal/lifecycle/attach-modal-sandbox + ./examples/go/modal/lifecycle/close-modal-handle + ./examples/go/modal/lifecycle/request-modal-termination + ./examples/go/modal/lifecycle/terminate-modal-sandbox + ./examples/go/modal/lifecycle/terminate-modal-with-timeout ./examples/go/modal/process/run-modal-check-exit ./examples/go/modal/process/run-modal-command ./examples/go/modal/process/run-modal-shell-command @@ -78,6 +105,7 @@ use ( ./examples/go/modal/process/run-modal-with-timeout ./sdks/go/internal/contracts ./sdks/go/internal/sdk + ./sdks/go/providers/blaxel ./sdks/go/providers/daytona ./sdks/go/providers/modal ./sdks/go/sandbox diff --git a/go.work.sum b/go.work.sum index 967c9e08..018e4e34 100644 --- a/go.work.sum +++ b/go.work.sum @@ -40,7 +40,6 @@ golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= diff --git a/proto/kit/sandbox/v1/client.proto b/proto/kit/sandbox/v1/client.proto index a5bf540d..3d0db28e 100644 --- a/proto/kit/sandbox/v1/client.proto +++ b/proto/kit/sandbox/v1/client.proto @@ -54,7 +54,7 @@ message AuthConfig { } // APIKeyCredentials is an API-key credential. Daytona uses Key. message APIKeyCredentials { - // The secret API key. It is excluded from generated JSON. + // The secret API key. Go encoding/json output excludes it when marshaling a pointer. string key = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -63,13 +63,13 @@ message APIKeyCredentials { } // TokenPairCredentials is a token-id and token-secret pair. Modal uses both fields. message TokenPairCredentials { - // The token identifier. + // The token identifier. Go encoding/json output excludes it when marshaling a pointer. string id = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" expression: "this.trim() != ''" }]; - // The token secret. It is excluded from generated JSON. + // The token secret. Go encoding/json output excludes it when marshaling a pointer. string secret = 2 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -78,7 +78,7 @@ message TokenPairCredentials { } // BearerTokenCredentials is a bearer or JWT credential. Daytona also requires an organization. message BearerTokenCredentials { - // The bearer token. It is excluded from generated JSON. + // The bearer token. Go encoding/json output excludes it when marshaling a pointer. string token = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -96,7 +96,7 @@ message OAuthCredentials { } ] }; - // The refresh token. It is excluded from generated JSON. + // The refresh token. Go encoding/json output excludes it when marshaling a pointer. string refresh_token = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -108,13 +108,13 @@ message OAuthCredentials { message: "must not be blank" expression: "this.trim() != ''" }]; - // One client secret. Do not set this together with JWTKey. + // One client secret. Do not set this together with JWTKey. Go encoding/json excludes it when marshaling a pointer. optional string client_secret = 3 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" expression: "this.trim() != ''" }]; - // One JWT signing key. Do not set this together with ClientSecret. + // One JWT signing key. Do not set this together with ClientSecret. Go encoding/json excludes it when marshaling a pointer. optional string jwt_key = 4 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -147,4 +147,10 @@ message Scope { message: "must not be blank" expression: "this.trim() != ''" }]; + // The Blaxel workspace name. + optional string workspace = 5 [(buf.validate.field).cel = { + id: "nonblank" + message: "must not be blank" + expression: "this.trim() != ''" + }]; } diff --git a/sdks/go/internal/contracts/README.md b/sdks/go/internal/contracts/README.md index 1b6ca420..4d9b0cc8 100644 --- a/sdks/go/internal/contracts/README.md +++ b/sdks/go/internal/contracts/README.md @@ -12,6 +12,8 @@ go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest go get github.com/sandbox-kit/kit/sdks/go/providers/daytona@latest # or go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest +# or +go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ``` ## License diff --git a/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go b/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go index 0aaf667e..1c4b9bc9 100644 --- a/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go +++ b/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go @@ -230,7 +230,7 @@ func (*AuthConfig_OauthRefresh) isAuthConfig_Credentials() {} // APIKeyCredentials is an API-key credential. Daytona uses Key. type APIKeyCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The secret API key. It is excluded from generated JSON. + // The secret API key. Go encoding/json output excludes it when marshaling a pointer. Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -276,9 +276,9 @@ func (x *APIKeyCredentials) GetKey() string { // TokenPairCredentials is a token-id and token-secret pair. Modal uses both fields. type TokenPairCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The token identifier. + // The token identifier. Go encoding/json output excludes it when marshaling a pointer. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // The token secret. It is excluded from generated JSON. + // The token secret. Go encoding/json output excludes it when marshaling a pointer. Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -331,7 +331,7 @@ func (x *TokenPairCredentials) GetSecret() string { // BearerTokenCredentials is a bearer or JWT credential. Daytona also requires an organization. type BearerTokenCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The bearer token. It is excluded from generated JSON. + // The bearer token. Go encoding/json output excludes it when marshaling a pointer. Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -377,13 +377,13 @@ func (x *BearerTokenCredentials) GetToken() string { // OAuthCredentials is an OAuth refresh credential. Supply ClientID and exactly one of ClientSecret or JWTKey. type OAuthCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The refresh token. It is excluded from generated JSON. + // The refresh token. Go encoding/json output excludes it when marshaling a pointer. RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` // The OAuth client identifier. ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` - // One client secret. Do not set this together with JWTKey. + // One client secret. Do not set this together with JWTKey. Go encoding/json excludes it when marshaling a pointer. ClientSecret *string `protobuf:"bytes,3,opt,name=client_secret,json=clientSecret,proto3,oneof" json:"client_secret,omitempty"` - // One JWT signing key. Do not set this together with ClientSecret. + // One JWT signing key. Do not set this together with ClientSecret. Go encoding/json excludes it when marshaling a pointer. JwtKey *string `protobuf:"bytes,4,opt,name=jwt_key,json=jwtKey,proto3,oneof" json:"jwt_key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -457,7 +457,9 @@ type Scope struct { // The Daytona organization. Bearer auth requires it when the SDK cannot resolve one. OrganizationId *string `protobuf:"bytes,3,opt,name=organization_id,json=organizationId,proto3,oneof" json:"organization_id,omitempty"` // Reserved for providers that scope work to a project. Current providers reject it when supplied. - ProjectId *string `protobuf:"bytes,4,opt,name=project_id,json=projectId,proto3,oneof" json:"project_id,omitempty"` + ProjectId *string `protobuf:"bytes,4,opt,name=project_id,json=projectId,proto3,oneof" json:"project_id,omitempty"` + // The Blaxel workspace name. + Workspace *string `protobuf:"bytes,5,opt,name=workspace,proto3,oneof" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -520,6 +522,13 @@ func (x *Scope) GetProjectId() string { return "" } +func (x *Scope) GetWorkspace() string { + if x != nil && x.Workspace != nil { + return *x.Workspace + } + return "" +} + var File_kit_sandbox_v1_client_proto protoreflect.FileDescriptor const file_kit_sandbox_v1_client_proto_rawDesc = "" + @@ -567,7 +576,7 @@ const file_kit_sandbox_v1_client_proto_rawDesc = "" + "\vexactly_one\x12.requires exactly one of client_secret, jwt_key\x1a,has(this.client_secret) != has(this.jwt_key)B\x10\n" + "\x0e_client_secretB\n" + "\n" + - "\b_jwt_key\"\xc0\x03\n" + + "\b_jwt_key\"\xa9\x04\n" + "\x05Scope\x12V\n" + "\bapp_name\x18\x01 \x01(\tB6\xbaH3\xba\x010\n" + "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x00R\aappName\x88\x01\x01\x12]\n" + @@ -577,11 +586,15 @@ const file_kit_sandbox_v1_client_proto_rawDesc = "" + "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x02R\x0eorganizationId\x88\x01\x01\x12Z\n" + "\n" + "project_id\x18\x04 \x01(\tB6\xbaH3\xba\x010\n" + - "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x03R\tprojectId\x88\x01\x01B\v\n" + + "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x03R\tprojectId\x88\x01\x01\x12Y\n" + + "\tworkspace\x18\x05 \x01(\tB6\xbaH3\xba\x010\n" + + "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x04R\tworkspace\x88\x01\x01B\v\n" + "\t_app_nameB\x0e\n" + "\f_environmentB\x12\n" + "\x10_organization_idB\r\n" + - "\v_project_idBPZNgithub.com/sandbox-kit/kit/sdks/go/internal/contracts/kit/sandbox/v1;sandboxv1b\x06proto3" + "\v_project_idB\f\n" + + "\n" + + "_workspaceBPZNgithub.com/sandbox-kit/kit/sdks/go/internal/contracts/kit/sandbox/v1;sandboxv1b\x06proto3" var ( file_kit_sandbox_v1_client_proto_rawDescOnce sync.Once diff --git a/sdks/go/internal/contracts/kit/sandbox/v1/client_json.go b/sdks/go/internal/contracts/kit/sandbox/v1/client_json.go new file mode 100644 index 00000000..f04f2264 --- /dev/null +++ b/sdks/go/internal/contracts/kit/sandbox/v1/client_json.go @@ -0,0 +1,32 @@ +package sandboxv1 + +import "encoding/json" + +// MarshalJSON prevents the API key from entering ordinary Go JSON output. +func (*APIKeyCredentials) MarshalJSON() ([]byte, error) { + return []byte("{}"), nil +} + +// MarshalJSON prevents the token identifier and secret from entering ordinary +// Go JSON output. +func (*TokenPairCredentials) MarshalJSON() ([]byte, error) { + return []byte("{}"), nil +} + +// MarshalJSON prevents the bearer token from entering ordinary Go JSON output. +func (*BearerTokenCredentials) MarshalJSON() ([]byte, error) { + return []byte("{}"), nil +} + +// MarshalJSON retains the non-sensitive OAuth client identifier and prevents +// the refresh token, client secret, and JWT key from entering ordinary Go JSON +// output. +func (credentials *OAuthCredentials) MarshalJSON() ([]byte, error) { + type publicOAuthCredentials struct { + ClientID string `json:"client_id,omitempty"` + } + if credentials == nil { + return []byte("null"), nil + } + return json.Marshal(publicOAuthCredentials{ClientID: credentials.ClientId}) +} diff --git a/sdks/go/internal/contracts/kit/sandbox/v1/client_json_test.go b/sdks/go/internal/contracts/kit/sandbox/v1/client_json_test.go new file mode 100644 index 00000000..a5c65e74 --- /dev/null +++ b/sdks/go/internal/contracts/kit/sandbox/v1/client_json_test.go @@ -0,0 +1,51 @@ +package sandboxv1_test + +import ( + "encoding/json" + "strings" + "testing" + + sandboxv1 "github.com/sandbox-kit/kit/sdks/go/internal/contracts/kit/sandbox/v1" +) + +func TestCredentialJSONRedactsSensitiveFields(t *testing.T) { + tests := []struct { + name string + credential any + want string + }{ + {name: "api key", credential: &sandboxv1.APIKeyCredentials{Key: "api-key-secret"}, want: `{}`}, + {name: "token pair", credential: &sandboxv1.TokenPairCredentials{Id: "token-id-secret", Secret: "token-secret"}, want: `{}`}, + {name: "bearer token", credential: &sandboxv1.BearerTokenCredentials{Token: "bearer-secret"}, want: `{}`}, + {name: "OAuth", credential: &sandboxv1.OAuthCredentials{RefreshToken: "refresh-secret", ClientId: "public-client", ClientSecret: sandboxv1String("client-secret"), JwtKey: sandboxv1String("jwt-secret")}, want: `{"client_id":"public-client"}`}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + encoded, err := json.Marshal(test.credential) + if err != nil { + t.Fatal(err) + } + if string(encoded) != test.want { + t.Fatalf("JSON = %s, want %s", encoded, test.want) + } + for _, secret := range []string{"api-key-secret", "token-id-secret", "token-secret", "bearer-secret", "refresh-secret", "client-secret", "jwt-secret"} { + if strings.Contains(string(encoded), secret) { + t.Fatalf("JSON contains sensitive value %q", secret) + } + } + }) + } +} + +func TestConfigJSONUsesCredentialRedaction(t *testing.T) { + config := &sandboxv1.Config{Auth: &sandboxv1.AuthConfig{Credentials: &sandboxv1.AuthConfig_ApiKey{ApiKey: &sandboxv1.APIKeyCredentials{Key: "nested-secret"}}}} + encoded, err := json.Marshal(config) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(encoded), "nested-secret") { + t.Fatalf("config JSON contains credential: %s", encoded) + } +} + +func sandboxv1String(value string) *string { return &value } diff --git a/sdks/go/internal/sdk/error.go b/sdks/go/internal/sdk/error.go index 0ec01f49..756f0121 100644 --- a/sdks/go/internal/sdk/error.go +++ b/sdks/go/internal/sdk/error.go @@ -12,7 +12,7 @@ import ( type Error struct { // Kind is the category, such as ErrorKindUnsupported or ErrorKindAuthentication. Kind sandboxv1.ErrorKind - // Provider is the provider name, such as "daytona" or "modal". It is empty when the failure happened before a provider was selected. + // Provider is the provider name, such as "daytona", "modal", or "blaxel". It is empty when the failure happened before a provider was selected. Provider string // Operation is the call that failed, such as "create", "get", or "initialize". Operation string diff --git a/sdks/go/providers/blaxel/LICENSE b/sdks/go/providers/blaxel/LICENSE new file mode 100644 index 00000000..d843376c --- /dev/null +++ b/sdks/go/providers/blaxel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 sandbox-kit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md new file mode 100644 index 00000000..82d45e22 --- /dev/null +++ b/sdks/go/providers/blaxel/README.md @@ -0,0 +1,160 @@ +# Blaxel + +Create and manage Blaxel sandboxes with the shared sandbox client. + +Requires Go 1.26.0 or later and a Blaxel API key. Set `Auth` to an API key and +`Scope.Workspace` to the workspace name. If either is omitted, the client uses +`BL_API_KEY` and `BL_WORKSPACE` from the environment when present. +`Config.Region` is required for create. + +## Install + +```sh +go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest +go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest +``` + +## Create a sandbox + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) +if err != nil { + log.Fatal(err) +} +defer client.Close(context.Background()) + +instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("my-sandbox"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, +}) +if err != nil { + log.Fatal(err) +} +fmt.Println(instance.ID()) +``` + +Create requires a name and an image. `ID()` is that name — use it with `Get` +and example env `BL_SANDBOX_NAME`. Closing the client does not delete the +sandbox. Call `Delete`, or set a lifetime policy. + +## Settings + +| Setting | How to set it | +| --------- | ------------------------------------- | +| API key | `Auth` with `APIKey` | +| Workspace | `Scope.Workspace` (or `BL_WORKSPACE`) | +| Region | `Config.Region` (required for create) | +| Timeout | `Config.Timeout` (create/op deadline) | + +`Config.Endpoint` is not supported. Token pair, OAuth, and bearer auth are +rejected. `Scope` fields other than `Workspace` are rejected. + +## What you can do + +### Create + +You need a name and an image. Name is required (blank or whitespace-only is +rejected). Names are lowercase alphanumeric with single hyphens (max 49 +characters). You can also set labels, environment variables, +memory, ports, outbound domains, maximum lifetime (≥5 minutes), and idle +terminate (≥1 minute). You cannot disable idle terminate. Runtime and +isolation are not supported. `Info().Labels` is set only when Blaxel reports +labels. Ports 80, 443, and 8080 are reserved. Default port transport is HTTP; +plain is TCP; TLS is TLS; HTTP/2 over TLS is not supported. + +Set client or create timeout for how long create may wait. With a 1–50s +timeout, a timed-out create frees the name. Ready/Started/Submitted wait and +readiness probes are not supported (`WaitFor` only Default or Scheduled). + +If Blaxel’s edge returns HTTP 504 during create, Go returns a timeout as soon +as that response arrives. TypeScript may keep waiting until the sandbox is +ready or your create timeout ends. A Go timeout does not always mean the +sandbox was never created — call `Get` by name before recreating. + +Set `Resources.MemoryMib` for memory size. Kit converts MiB to Blaxel's decimal +MB value, rounding up so a request is never under-provisioned. CPU follows +memory on Blaxel — do not set CPU or +disk overrides. If you omit memory, Blaxel applies its default. Volumes are not +part of create. + +If the name already exists, you get `AlreadyExists`. + +### Lifecycle + +You can `Get`, `Refresh`, `Delete`, and `Close`. Lookup is by name +(`ID()` equals the create name). Attach, start, stop, pause, archive, and +terminate are not available. `Close` only drops the local handle. + +After a successful `Delete`, `Info().State` is `Deleted` and `Terminal` is +true. Omit `Wait` to return as soon as Blaxel accepts the delete — recreating +the same name too soon may return `Conflict`. Pass `Wait: true` to wait until +removal finishes before the call returns. + +On `Get` / `Refresh` / `Info`, an idle sandbox shows as `Paused`; an active +one shows as `Running`. + +### Filesystem + +You get the usual file APIs: read, write, copy in/out, mkdir, list, stat, and +remove. Paths must be absolute. `Parents` defaults to true; Blaxel does not +support `Parents: false`. + +`Stat` returns `Type` (file or directory), `Size`, and usually `Permissions`, +`Owner`, `Group`, and `ModifiedAt`. `Name` and `Path` are whatever Blaxel +reports. Missing local files for `CopyFromLocal`, and local write failures from +`CopyToLocal`, use field `local_path`. + +### Process + +`Process().Run` runs one command and waits for it to finish. Set exactly one of +`Argv` or `Shell`. `Argv` is sent as one quoted shell command string. A +non-zero exit code is not an error — read `GetExitCode`. You get both stdout +and stderr. + +Omit or zero `Timeout` to wait without a Blaxel process time limit (your +context can still cancel). A positive timeout must be whole seconds and caps +how long the command may run. Canceling the context stops waiting; the remote +command may keep running. If the wait times out, you get a timeout error. + +### Errors you will see + +| Situation | Kind | +| ------------------------------------------ | ----------------- | +| Create when the name already exists | `AlreadyExists` | +| Conflict while a delete is still finishing | `Conflict` | +| Lifetime below floor or not whole seconds | `InvalidArgument` | +| Unsupported create field (runtime, …) | `Unsupported` | + +Blaxel control-plane codes appear on `ProviderCode` when present. + +## Examples + +| Operation | Program | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | +| Create | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) | +| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-command) | +| Shell / cwd / env / exit | [process examples](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process) | +| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/read-blaxel-file) | +| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem) | +| Look up | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | +| All | [Go examples](https://github.com/sandbox-kit/kit/tree/main/examples/go) | + +pkg.go.dev also shows `ExampleNew_*` samples in this package for create, +lifecycle, process, and filesystem. + +## License + +MIT diff --git a/sdks/go/providers/blaxel/client.go b/sdks/go/providers/blaxel/client.go new file mode 100644 index 00000000..1be1080c --- /dev/null +++ b/sdks/go/providers/blaxel/client.go @@ -0,0 +1,109 @@ +package blaxel + +import ( + "strings" + + "github.com/blaxel-ai/sdk-go/option" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// mapClientConfig maps shared client configuration into native SDK options. +// Endpoint is unsupported. Scope.Workspace maps to the native workspace +// option when set. Unsupported supplied settings fail before the SDK is +// constructed. Shared Config validation runs in sandbox.NewClient; this +// maps only. +type clientSettings struct { + source *sandbox.Config + opts []option.RequestOption +} + +func mapClientConfig(config *sandbox.Config) ([]option.RequestOption, error) { + if config == nil { + config = &sandbox.Config{} + } + settings := &clientSettings{source: config} + if err := settings.applyEndpoint(); err != nil { + return nil, err + } + if err := settings.applyScope(); err != nil { + return nil, err + } + if err := settings.applyAuth(); err != nil { + return nil, err + } + return settings.opts, nil +} + +func (s *clientSettings) applyEndpoint() error { + if s.source.Endpoint == nil { + return nil + } + return unsupportedClientField(sandbox.ConfigFieldEndpoint, "Endpoint cannot be configured through this SDK's public client constructor") +} + +func (s *clientSettings) applyScope() error { + if s.source.Scope == nil { + return nil + } + scope := s.source.Scope + for _, check := range []struct { + set bool + field string + }{ + {scope.AppName != nil, sandbox.ConfigFieldScopeAppName}, + {scope.Environment != nil, sandbox.ConfigFieldScopeEnvironment}, + {scope.OrganizationId != nil, sandbox.ConfigFieldScopeOrganizationId}, + {scope.ProjectId != nil, sandbox.ConfigFieldScopeProjectId}, + } { + if check.set { + return unsupportedClientField(check.field, "unsupported client configuration field") + } + } + if scope.Workspace == nil { + return nil + } + workspace := scope.GetWorkspace() + if strings.TrimSpace(workspace) == "" { + return invalidClientField(sandbox.ConfigFieldScopeWorkspace, "workspace requires a non-blank value") + } + s.opts = append(s.opts, option.WithWorkspace(workspace)) + return nil +} + +func (s *clientSettings) applyAuth() error { + auth := s.source.GetAuth() + if auth == nil { + return nil + } + switch credentials := auth.Credentials.(type) { + case *sandbox.APIKey: + if credentials.ApiKey == nil { + return invalidClientField(sandbox.ConfigFieldAuthApiKey, "api_key requires key") + } + if credentials.ApiKey.Key == "" { + return invalidClientField(sandbox.ConfigFieldAuthApiKeyKey, "api_key requires key") + } + s.opts = append(s.opts, option.WithAPIKey(credentials.ApiKey.Key)) + default: + return unsupportedClientField(sandbox.ConfigFieldAuth, "unsupported authentication mode") + } + return nil +} + +func invalidClientField(field, message string) error { + return kitError(kitFault{ + Kind: sandbox.ErrorKindInvalidArgument, + Operation: "initialize", + Field: field, + Message: message, + }) +} + +func unsupportedClientField(field, message string) error { + return kitError(kitFault{ + Kind: sandbox.ErrorKindUnsupported, + Operation: "initialize", + Field: field, + Message: message, + }) +} diff --git a/sdks/go/providers/blaxel/client_test.go b/sdks/go/providers/blaxel/client_test.go new file mode 100644 index 00000000..664be4aa --- /dev/null +++ b/sdks/go/providers/blaxel/client_test.go @@ -0,0 +1,114 @@ +package blaxel + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestCommonInitializationMappings(t *testing.T) { + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: "api-key"}, + }}, + Scope: &sandbox.Scope{Workspace: sandbox.Value("workspace")}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(time.Second), + } + opts, err := mapClientConfig(config) + if err != nil { + t.Fatal(err) + } + if len(opts) != 2 { + t.Fatalf("expected api key and workspace options, got %d", len(opts)) + } +} + +func TestUnsupportedClientSettingsRejectedBeforeSDKInitialization(t *testing.T) { + for _, config := range []*sandbox.Config{ + {Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{TokenPair: &sandbox.TokenPairCredentials{Id: "id", Secret: "secret"}}}}, + {Scope: &sandbox.Scope{AppName: sandbox.Value("app")}}, + {Endpoint: sandbox.Value("https://example.invalid")}, + } { + if provider, err := New().NewClient(config); err == nil || provider != nil { + t.Fatal("unsupported client setting initialized SDK") + } + } +} + +func TestUnsupportedScopeFieldsUseCanonicalPaths(t *testing.T) { + for _, tc := range []struct { + name string + scope *sandbox.Scope + field string + }{ + {"app", &sandbox.Scope{AppName: sandbox.Value("app")}, sandbox.ConfigFieldScopeAppName}, + {"environment", &sandbox.Scope{Environment: sandbox.Value("env")}, sandbox.ConfigFieldScopeEnvironment}, + {"organization", &sandbox.Scope{OrganizationId: sandbox.Value("org")}, sandbox.ConfigFieldScopeOrganizationId}, + {"project", &sandbox.Scope{ProjectId: sandbox.Value("project")}, sandbox.ConfigFieldScopeProjectId}, + } { + t.Run(tc.name, func(t *testing.T) { + _, err := mapClientConfig(&sandbox.Config{Scope: tc.scope}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != tc.field { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + } +} + +func TestProviderInitializesAndOwnsSDKWithoutRequests(t *testing.T) { + t.Setenv("BL_API_KEY", "") + t.Setenv("BL_WORKSPACE", "") + client, err := sandbox.NewClient(New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: "test-only"}, + }}, + Scope: &sandbox.Scope{Workspace: sandbox.Value("test-workspace")}, + Region: sandbox.Value("us-pdx-1"), + }) + if err != nil { + t.Fatal(err) + } + if client.ProviderName() != "blaxel" { + t.Fatal("wrong provider") + } + if err := client.Close(context.Background()); err != nil { + t.Fatal(err) + } +} + +func TestAuthReportsTheMissingCredential(t *testing.T) { + cases := []struct { + name string + config *sandbox.Config + field string + }{ + {"missing key", &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{}}}, sandbox.ConfigFieldAuthApiKey}, + {"blank key", &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ApiKey: &sandbox.APIKeyCredentials{}}}}, sandbox.ConfigFieldAuthApiKeyKey}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := mapClientConfig(tc.config) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != tc.field || detail.Provider != "blaxel" { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + } +} + +func TestBlankWorkspaceRejected(t *testing.T) { + for _, workspace := range []string{"", " "} { + _, err := mapClientConfig(&sandbox.Config{ + Scope: &sandbox.Scope{Workspace: sandbox.Value(workspace)}, + }) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.ConfigFieldScopeWorkspace { + t.Fatalf("workspace %q: error = %v detail = %#v", workspace, err, detail) + } + } +} diff --git a/sdks/go/providers/blaxel/clone_message.go b/sdks/go/providers/blaxel/clone_message.go new file mode 100644 index 00000000..5277c0c8 --- /dev/null +++ b/sdks/go/providers/blaxel/clone_message.go @@ -0,0 +1,56 @@ +package blaxel + +import ( + "reflect" + + "google.golang.org/protobuf/proto" +) + +// cloneMessage copies a protobuf message without copying its embedded mutex. +// proto.Clone drops empty slices and maps; those are restored so an explicit +// empty collection still counts as set. +func cloneMessage[T proto.Message](src T) T { + value := reflect.ValueOf(src) + if !value.IsValid() || (value.Kind() == reflect.Pointer && value.IsNil()) { + var zero T + return zero + } + dst := proto.Clone(src).(T) + preserveEmpty(value, reflect.ValueOf(dst)) + return dst +} + +func preserveEmpty(src, dst reflect.Value) { + if src.Kind() == reflect.Pointer { + if src.IsNil() || dst.Kind() != reflect.Pointer || dst.IsNil() { + return + } + src, dst = src.Elem(), dst.Elem() + } + if src.Kind() != reflect.Struct || dst.Kind() != reflect.Struct { + return + } + for i := 0; i < src.NumField(); i++ { + if src.Type().Field(i).PkgPath != "" { + continue + } + sv, dv := src.Field(i), dst.Field(i) + switch sv.Kind() { + case reflect.Slice: + if !sv.IsNil() && dv.IsNil() { + dv.Set(reflect.MakeSlice(sv.Type(), 0, 0)) + } + case reflect.Map: + if !sv.IsNil() && dv.IsNil() { + dv.Set(reflect.MakeMap(sv.Type())) + } + case reflect.Pointer: + if sv.IsNil() || dv.IsNil() || sv.Elem().Kind() != reflect.Struct { + continue + } + preserveEmpty(sv, dv) + case reflect.Struct: + preserveEmpty(sv, dv) + } + } +} diff --git a/sdks/go/providers/blaxel/clone_message_test.go b/sdks/go/providers/blaxel/clone_message_test.go new file mode 100644 index 00000000..04564e82 --- /dev/null +++ b/sdks/go/providers/blaxel/clone_message_test.go @@ -0,0 +1,25 @@ +package blaxel + +import ( + "testing" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestCloneMessageKeepsExplicitEmptyCollections(t *testing.T) { + src := &sandbox.CreateOptions{ + Environment: map[string]string{}, + Runtime: &sandbox.RuntimeConfig{Entrypoint: []string{}}, + } + dst := cloneMessage(src) + if dst == nil || dst == src || dst.Environment == nil || dst.Runtime == nil || dst.Runtime.Entrypoint == nil { + t.Fatalf("empty collections dropped: %#v", dst) + } + dst.Environment["MODE"] = "changed" + if len(src.Environment) != 0 { + t.Fatal("clone shares the source map") + } + if cloneMessage((*sandbox.CreateOptions)(nil)) != nil { + t.Fatal("nil source returned a message") + } +} diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go new file mode 100644 index 00000000..ad17835b --- /dev/null +++ b/sdks/go/providers/blaxel/creation.go @@ -0,0 +1,329 @@ +package blaxel + +import ( + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/internal/sdk" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// createPlan is the Blaxel SDK argument for one creation request. +type createPlan struct { + params blaxel.SandboxNewParams +} + +// creation translates one CreateOptions value into native Blaxel arguments. +type creation struct { + request *sandbox.CreateOptions + rest *sandbox.CreateOptions + params blaxel.SandboxNewParams + region string +} + +var reservedBlaxelPorts = map[uint32]struct{}{ + 80: {}, + 443: {}, + 8080: {}, +} + +// planCreate checks the request and builds SDK arguments without contacting Blaxel. +func planCreate(request *sandbox.CreateOptions, region string) (createPlan, error) { + if request == nil { + request = &sandbox.CreateOptions{} + } + if err := validateProviderCreate(request); err != nil { + return createPlan{}, err + } + if region == "" { + return createPlan{}, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.ConfigFieldRegion, "Config.Region is required") + } + params, rest, err := mapCommonCreateFields(request) + if err != nil { + return createPlan{}, err + } + planned := &creation{request: request, rest: rest, params: params, region: region} + planned.params.Sandbox.Spec.Region = blaxel.String(region) + for _, apply := range []func() error{ + planned.source, + planned.resources, + planned.lifetime, + planned.runtime, + planned.isolation, + planned.security, + planned.network, + planned.provisioning, + planned.readiness, + planned.observability, + } { + if err := apply(); err != nil { + return createPlan{}, err + } + } + if err := sdk.RejectUnmapped("blaxel", "", planned.rest); err != nil { + return createPlan{}, err + } + return createPlan{params: planned.params}, nil +} + +func (c *creation) source() error { + image := c.request.GetSource().GetImage() + c.params.Sandbox.Spec.Runtime.Image = blaxel.String(image.Reference) + c.rest.Source = nil + return nil +} + +func (c *creation) resources() error { + if c.request.Resources == nil { + return nil + } + mapped, remaining, err := mapResources(c.request.Resources) + if err != nil { + return err + } + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldResources, remaining); err != nil { + return err + } + if mapped.Memory.Valid() { + c.params.Sandbox.Spec.Runtime.Memory = mapped.Memory + } + c.rest.Resources = nil + return nil +} + +func (c *creation) lifetime() error { + if c.request.Lifetime == nil { + return nil + } + config := cloneMessage(c.request.Lifetime) + if config.MaximumLifetime != nil { + formatted, err := formatBlaxelDuration(config.MaximumLifetime.AsDuration(), 5*time.Minute) + if err != nil { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeMaximumLifetime, err.Error()) + } + c.params.Sandbox.Spec.Runtime.Ttl = blaxel.String(formatted) + } + config.MaximumLifetime = nil + if config.IdleTerminate != nil { + policy, err := mapIdleTerminate(config.IdleTerminate) + if err != nil { + return err + } + if policy != nil { + c.params.Sandbox.Spec.Lifecycle.ExpirationPolicies = append( + c.params.Sandbox.Spec.Lifecycle.ExpirationPolicies, + *policy, + ) + } + } + config.IdleTerminate = nil + if hasUnsupportedLifetimeFields(config) { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldLifetime, "one or more Blaxel lifetime fields are not mapped") + } + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldLifetime, config); err != nil { + return err + } + c.rest.Lifetime = nil + return nil +} + +func mapIdleTerminate(action *sandbox.AutomaticAction) (*blaxel.ExpirationPolicyParam, error) { + if action == nil { + return nil, nil + } + switch action.Mode { + case sandbox.PolicyModeDefault: + return nil, nil + case sandbox.PolicyModeDisabled: + return nil, providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldLifetimeIdleTerminate, "disabling idle terminate is not representable") + case sandbox.PolicyModeAfter: + if action.After == nil { + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminateAfter, "duration is required") + } + formatted, err := formatBlaxelDuration(action.After.AsDuration(), time.Minute) + if err != nil { + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminateAfter, err.Error()) + } + return &blaxel.ExpirationPolicyParam{ + Action: blaxel.ExpirationPolicyActionDelete, + Type: blaxel.ExpirationPolicyTypeTtlIdle, + Value: blaxel.String(formatted), + }, nil + default: + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminateMode, "unknown policy mode") + } +} + +func (c *creation) runtime() error { + if c.request.Runtime == nil { + return nil + } + config := cloneMessage(c.request.Runtime) + if config.WorkingDirectory != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldRuntimeWorkingDirectory, "Blaxel create-time working directory is not mapped") + } + if hasUnsupportedRuntimeFields(config) { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldRuntime, "runtime user, language, version, entrypoint, and PTY are not mapped by Blaxel") + } + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldRuntime, config); err != nil { + return err + } + c.rest.Runtime = nil + return nil +} + +func (c *creation) isolation() error { + if c.request.Isolation == nil { + return nil + } + config := cloneMessage(c.request.Isolation) + if hasUnsupportedIsolation(config) { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldIsolation, "Blaxel isolation class is not mapped") + } + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldIsolation, config); err != nil { + return err + } + c.rest.Isolation = nil + return nil +} + +func (c *creation) security() error { + return rejectIfSet(c.request.Security != nil, sandbox.CreateFieldSecurity, "Blaxel security fields are not mapped") +} + +func (c *creation) network() error { + if c.request.Network == nil { + return nil + } + config := cloneMessage(c.request.Network) + if hasUnsupportedNetwork(config) { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldNetwork, "requested network settings are unsupported by Blaxel") + } + if config.OutboundDomains != nil { + c.params.Sandbox.Spec.Network.AllowedDomains = append([]string(nil), config.OutboundDomains.Entries...) + config.OutboundDomains = nil + } + if len(c.request.Network.Ports) > 0 { + ports, err := mapPorts(c.request.Network.Ports) + if err != nil { + return err + } + c.params.Sandbox.Spec.Runtime.Ports = ports + config.Ports = nil + } + config.Egress = 0 + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldNetwork, config); err != nil { + return err + } + c.rest.Network = nil + return nil +} + +func mapPorts(bindings []*sandbox.PortBinding) ([]blaxel.PortParam, error) { + ports := make([]blaxel.PortParam, 0, len(bindings)) + for _, binding := range bindings { + if binding == nil { + return nil, providerCheckError( + sandbox.ErrorKindInvalidArgument, + sandbox.CreateFieldNetworkPorts, + "port binding is required", + ) + } + if isReservedPort(binding.Port) { + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsPort, "ports 80, 443, and 8080 are reserved by Blaxel") + } + protocol, err := blaxelPortProtocol(binding.Transport) + if err != nil { + return nil, err + } + ports = append(ports, blaxel.PortParam{ + Target: int64(binding.Port), + Protocol: protocol, + }) + } + return ports, nil +} + +func blaxelPortProtocol(transport sandbox.PortTransport) (blaxel.PortProtocol, error) { + switch transport { + case sandbox.PortTransportDefault: + return "", nil + case sandbox.PortTransportPlain: + return blaxel.PortProtocolTcp, nil + case sandbox.PortTransportTLS: + return blaxel.PortProtocolTls, nil + default: + // Blaxel PortProtocol is HTTP|TCP|UDP|TLS only (Kit Http2Tls included). + return "", providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldNetworkPortsTransport, "Http2Tls is not a Blaxel port protocol (HTTP|TCP|UDP|TLS)") + } +} + +func (c *creation) provisioning() error { + if c.request.Provisioning == nil { + return nil + } + config := cloneMessage(c.request.Provisioning) + // Client.Create applies Timeout as the context deadline. When that value + // is a whole number of seconds in 1..50, create also sends + // X-Blaxel-Creation-Timeout so Blaxel frees the name on CREATION_TIMEOUT. + config.Timeout = nil + if config.QueueTimeout != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningQueueTimeout, "Blaxel queue timeout is unsupported") + } + config.QueueTimeout = nil + switch config.WaitFor { + case sandbox.WaitConditionDefault, sandbox.WaitConditionScheduled: + default: + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningWaitFor, "requested wait condition is unsupported") + } + config.WaitFor = 0 + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldProvisioning, config); err != nil { + return err + } + c.rest.Provisioning = nil + return nil +} + +func (c *creation) readiness() error { + return rejectIfSet(c.request.Readiness != nil, sandbox.CreateFieldReadiness, "Blaxel readiness probes are not mapped") +} + +func (c *creation) observability() error { + return rejectIfSet(c.request.Observability != nil, sandbox.CreateFieldObservability, "Blaxel observability fields are not mapped") +} + +func rejectIfSet(set bool, field, message string) error { + if !set { + return nil + } + return providerCheckError(sandbox.ErrorKindUnsupported, field, message) +} + +func isReservedPort(port uint32) bool { + _, ok := reservedBlaxelPorts[port] + return ok +} + +func hasUnsupportedLifetimeFields(c *sandbox.LifetimePolicy) bool { + return c.Ephemeral != nil || c.IdleStop != nil || c.IdlePause != nil || + c.StoppedArchive != nil || c.StoppedDelete != nil +} + +func hasUnsupportedRuntimeFields(c *sandbox.RuntimeConfig) bool { + return c.Entrypoint != nil || c.Pty != nil || c.User != nil || c.Language != nil || c.Version != nil +} + +func hasUnsupportedIsolation(c *sandbox.IsolationConfig) bool { + return c.Kind != sandbox.IsolationKindDefault || c.ProviderKind != nil || c.NestedVirtualization != nil +} + +func hasUnsupportedNetwork(c *sandbox.NetworkConfig) bool { + return c.Egress != sandbox.EgressModeDefault || + c.OutboundCidrs != nil || + c.InboundCidrs != nil || + c.PrivateNetwork != nil || + c.LinkedSandbox != nil || + c.ProxyReference != nil || + c.OutboundProxyUrl != nil || + c.CustomDomain != nil +} diff --git a/sdks/go/providers/blaxel/doc.go b/sdks/go/providers/blaxel/doc.go new file mode 100644 index 00000000..46ecc05c --- /dev/null +++ b/sdks/go/providers/blaxel/doc.go @@ -0,0 +1,15 @@ +// Package blaxel connects the sandbox client to Blaxel. +// +// Pass [New] to sandbox.NewClient. Create requires a Blaxel-valid sandbox name +// (lowercase alphanumeric with single hyphens, at most 49 characters), an +// image source, and [sandbox.Config.Region]. Set Scope.Workspace and an API +// key, or omit them to use BL_API_KEY and BL_WORKSPACE from the environment. +// The backend implements shared filesystem operations through +// [sandbox.SandboxFiles] and blocking command execution through +// [sandbox.SandboxProcess]. Get and Refresh look up a sandbox by name. Delete +// removes the Blaxel resource. Closing the client releases local provider +// state and leaves the Blaxel sandbox running. Start, Stop, Pause, Archive, +// Attach, and Terminate are unsupported. Set resources.memory_mib for memory +// size; Blaxel documents that value as MB and Kit does not convert units. +// Volumes are not part of create. +package blaxel diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go new file mode 100644 index 00000000..fa302412 --- /dev/null +++ b/sdks/go/providers/blaxel/errors.go @@ -0,0 +1,204 @@ +package blaxel + +import ( + "context" + "errors" + "net/http" + + sdkblaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/internal/sdk" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +var providerCodeKinds = map[string]sandbox.ErrorKind{ + sdkblaxel.ErrAuthenticationRequired: sandbox.ErrorKindAuthentication, + sdkblaxel.ErrAuthenticationFailed: sandbox.ErrorKindAuthentication, + "TOKEN_REVOKED": sandbox.ErrorKindAuthentication, + + sdkblaxel.ErrForbidden: sandbox.ErrorKindPermissionDenied, + "UNAUTHORIZED": sandbox.ErrorKindPermissionDenied, + "UNAUTHORIZED_WORKSPACE": sandbox.ErrorKindPermissionDenied, + + sdkblaxel.ErrRouteNotFound: sandbox.ErrorKindNotFound, + sdkblaxel.ErrWorkloadNotFound: sandbox.ErrorKindNotFound, + sdkblaxel.ErrWorkspaceNotFound: sandbox.ErrorKindNotFound, + "IMAGE_NOT_FOUND": sandbox.ErrorKindNotFound, + "VOLUME_NOT_FOUND": sandbox.ErrorKindNotFound, + "ACCOUNT_NOT_FOUND": sandbox.ErrorKindNotFound, + "SANDBOX_DELETED": sandbox.ErrorKindNotFound, + "VOLUME_DELETED": sandbox.ErrorKindNotFound, + + "SANDBOX_ALREADY_EXISTS": sandbox.ErrorKindAlreadyExists, + "VOLUME_ALREADY_ATTACHED": sandbox.ErrorKindAlreadyExists, + + sdkblaxel.ErrBadRequest: sandbox.ErrorKindInvalidArgument, + sdkblaxel.ErrPolicyViolation: sandbox.ErrorKindInvalidArgument, + "INVALID_INPUT": sandbox.ErrorKindInvalidArgument, + "INVALID_IMAGE": sandbox.ErrorKindInvalidArgument, + "INVALID_PORTS": sandbox.ErrorKindInvalidArgument, + "INVALID_REGION": sandbox.ErrorKindInvalidArgument, + "INVALID_VOLUME": sandbox.ErrorKindInvalidArgument, + "INVALID_VOLUME_COUNT": sandbox.ErrorKindInvalidArgument, + "INVALID_VOLUME_REGION": sandbox.ErrorKindInvalidArgument, + "VALIDATION_FAILED": sandbox.ErrorKindInvalidArgument, + "VALIDATION_ERROR": sandbox.ErrorKindInvalidArgument, + "WORKSPACE_REQUIRED": sandbox.ErrorKindInvalidArgument, + "PARSE_FAILED": sandbox.ErrorKindInvalidArgument, + "REGION_MISMATCH": sandbox.ErrorKindInvalidArgument, + "VOLUME_PROVIDER_MISMATCH": sandbox.ErrorKindInvalidArgument, + "KERNEL_GENERATION_MISMATCH": sandbox.ErrorKindInvalidArgument, + "VALIDATED_DATA_ERROR": sandbox.ErrorKindInvalidArgument, + "WORKSPACE_DATA_MISSING": sandbox.ErrorKindInvalidArgument, + + "UPLOAD_NOT_SUPPORTED": sandbox.ErrorKindUnsupported, + "UNSUPPORTED_GENERATION": sandbox.ErrorKindUnsupported, + + "CREATION_TIMEOUT": sandbox.ErrorKindTimeout, + "UPSTREAM_CONNECT_TIMEOUT": sandbox.ErrorKindTimeout, + "UPSTREAM_TIMEOUT": sandbox.ErrorKindTimeout, + + sdkblaxel.ErrUsageLimitExceeded: sandbox.ErrorKindResourceExhausted, + "QUOTA_EXCEEDED": sandbox.ErrorKindResourceExhausted, + + "RATE_LIMIT_EXCEEDED": sandbox.ErrorKindRateLimited, + "WORKSPACE_RATE_LIMIT_EXCEEDED": sandbox.ErrorKindRateLimited, + + "SANDBOX_DELETION_IN_PROGRESS": sandbox.ErrorKindConflict, + + sdkblaxel.ErrWorkloadUnavailable: sandbox.ErrorKindUnavailable, + "ROUTING_UNAVAILABLE": sandbox.ErrorKindUnavailable, + "SERVICE_UNAVAILABLE": sandbox.ErrorKindUnavailable, + "AUTHENTICATION_UNAVAILABLE": sandbox.ErrorKindUnavailable, + "UPSTREAM_CONNECT_FAILED": sandbox.ErrorKindUnavailable, + "UPSTREAM_ERROR": sandbox.ErrorKindUnavailable, + "GATEWAY_INTERNAL_ERROR": sandbox.ErrorKindUnavailable, + "UPLOAD_UNAVAILABLE": sandbox.ErrorKindUnavailable, + "LOCK_ACQUISITION_FAILED": sandbox.ErrorKindUnavailable, + "VOLUME_LOOKUP_FAILED": sandbox.ErrorKindUnavailable, + "CLUSTER_GATEWAY_ERROR": sandbox.ErrorKindUnavailable, + + "WORKLOAD_FAILED": sandbox.ErrorKindInternal, + "INTERNAL_ERROR": sandbox.ErrorKindInternal, + "HANDLER_ERROR": sandbox.ErrorKindInternal, + "DATABASE_ERROR": sandbox.ErrorKindInternal, + "READ_FAILED": sandbox.ErrorKindInternal, + "BUILD_TRIGGER_ERROR": sandbox.ErrorKindInternal, + "PRESIGNED_URL_ERROR": sandbox.ErrorKindInternal, + "DEPLOYMENT_FAILED": sandbox.ErrorKindInternal, + "PROXY_CONFIG_ERROR": sandbox.ErrorKindInternal, + "CALLBACK_SEND_FAILED": sandbox.ErrorKindInternal, + "FORK_FAILED": sandbox.ErrorKindInternal, + "LOCKDOWN_ERROR": sandbox.ErrorKindInternal, + "UNKNOWN_ERROR": sandbox.ErrorKindInternal, +} + +var providerStatusKinds = map[int]sandbox.ErrorKind{ + 400: sandbox.ErrorKindInvalidArgument, + 422: sandbox.ErrorKindInvalidArgument, + 401: sandbox.ErrorKindAuthentication, + 402: sandbox.ErrorKindResourceExhausted, + 403: sandbox.ErrorKindPermissionDenied, + 404: sandbox.ErrorKindNotFound, + 410: sandbox.ErrorKindNotFound, + 408: sandbox.ErrorKindTimeout, + // Bare 409 is Conflict for non-create ops. Create maps it to AlreadyExists + // in statusKindFor (name collision). Coded conflicts still win above. + 409: sandbox.ErrorKindConflict, + 412: sandbox.ErrorKindFailedPrecondition, + 429: sandbox.ErrorKindRateLimited, + 500: sandbox.ErrorKindInternal, + 502: sandbox.ErrorKindUnavailable, + 503: sandbox.ErrorKindUnavailable, + 504: sandbox.ErrorKindTimeout, +} + +// statusKindFor maps an HTTP status. Bare 409 on create is treated as a name +// collision (AlreadyExists); other operations keep Conflict so portable code +// that branches on kind matches Daytona's conflict semantics outside create. +func statusKindFor(status int, operation string) (sandbox.ErrorKind, bool) { + if status == http.StatusConflict && operation == "create" { + return sandbox.ErrorKindAlreadyExists, true + } + kind, ok := providerStatusKinds[status] + return kind, ok +} + +func mapProviderError(err error, operation string) error { + if err == nil { + return nil + } + var local *sandbox.Error + if errors.As(err, &local) { + return sdk.WithErrorContext(err, "blaxel", operation) + } + + kind := sandbox.ErrorKindUnknown + var statusCode *uint32 + var providerCode, providerSource *string + switch { + case errors.Is(err, context.Canceled): + kind = sandbox.ErrorKindCanceled + case errors.Is(err, context.DeadlineExceeded): + kind = sandbox.ErrorKindTimeout + } + + var native *sdkblaxel.Error + if errors.As(err, &native) { + if native.StatusCode > 0 { + statusCode = sandbox.Value(uint32(native.StatusCode)) + } + if native.ErrorCode != "" { + providerCode = sandbox.Value(native.ErrorCode) + } + switch { + case native.BlaxelSource != "": + providerSource = sandbox.Value(native.BlaxelSource) + case native.Origin != "": + providerSource = sandbox.Value(native.Origin) + } + if kind == sandbox.ErrorKindUnknown { + if mapped, ok := providerCodeKinds[native.ErrorCode]; ok { + kind = mapped + } else if mapped, ok := statusKindFor(native.StatusCode, operation); ok { + kind = mapped + } + } + } + + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: kind, + Provider: "blaxel", + Operation: operation, + Message: err.Error(), + StatusCode: statusCode, + ProviderCode: providerCode, + ProviderSource: providerSource, + }, err) +} + +// kitFault is the internal shape for Blaxel-scoped validation / response errors. +type kitFault struct { + Kind sandbox.ErrorKind + Operation string + Field string + Message string +} + +func invalidResponse(operation, field, message string) error { + return kitError(kitFault{ + Kind: sandbox.ErrorKindInvalidResponse, + Operation: operation, + Field: field, + Message: message, + }) +} + +func kitError(fault kitFault) error { + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: fault.Kind, + Provider: "blaxel", + Operation: fault.Operation, + Field: fault.Field, + Message: "sandbox-kit blaxel: " + fault.Message, + }, nil) +} diff --git a/sdks/go/providers/blaxel/errors_test.go b/sdks/go/providers/blaxel/errors_test.go new file mode 100644 index 00000000..c68aca7a --- /dev/null +++ b/sdks/go/providers/blaxel/errors_test.go @@ -0,0 +1,167 @@ +package blaxel + +import ( + "context" + "errors" + "net/http" + "net/url" + "testing" + + sdkblaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestMapProviderErrorClassifiesAPIErrors(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusNotFound, + ErrorCode: sdkblaxel.ErrWorkloadNotFound, + Request: &http.Request{Method: http.MethodGet, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusNotFound}, + }, "refresh") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindNotFound || detail.Provider != "blaxel" || detail.Operation != "refresh" { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + +func TestMapProviderErrorPreservesContext(t *testing.T) { + err := mapProviderError(context.Canceled, "run") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindCanceled { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func TestMapProviderErrorSandboxAlreadyExists(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusConflict, + ErrorCode: "SANDBOX_ALREADY_EXISTS", + Request: &http.Request{Method: http.MethodPost, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusConflict}, + }, "create") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindAlreadyExists { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + +func TestMapProviderErrorBareConflictIsAlreadyExists(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusConflict, + Request: &http.Request{Method: http.MethodPost, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusConflict}, + }, "create") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindAlreadyExists { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + +func TestMapProviderErrorBareConflictOnDeleteIsConflict(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusConflict, + Request: &http.Request{Method: http.MethodDelete, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusConflict}, + }, "delete") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindConflict { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + +func TestMapProviderErrorDeletionInProgressStaysConflict(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusConflict, + ErrorCode: "SANDBOX_DELETION_IN_PROGRESS", + Request: &http.Request{Method: http.MethodDelete, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusConflict}, + }, "delete") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindConflict { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + +func TestMapProviderErrorGatewayTimeout(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo/process") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusGatewayTimeout, + Request: &http.Request{Method: http.MethodPost, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusGatewayTimeout}, + }, "run") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindTimeout { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + +func TestMapProviderErrorWorkloadFailedOverGatewayStatus(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusServiceUnavailable, + ErrorCode: "WORKLOAD_FAILED", + Request: &http.Request{Method: http.MethodGet, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusServiceUnavailable}, + }, "refresh") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindInternal { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } + if detail.GetProviderCode() != "WORKLOAD_FAILED" { + t.Fatalf("provider code = %q", detail.GetProviderCode()) + } +} + +func TestMapProviderErrorCreatePlaneCodes(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes") + if err != nil { + t.Fatal(err) + } + for _, tc := range []struct { + code string + kind sandbox.ErrorKind + }{ + {"DEPLOYMENT_FAILED", sandbox.ErrorKindInternal}, + {"REGION_MISMATCH", sandbox.ErrorKindInvalidArgument}, + {"UPLOAD_NOT_SUPPORTED", sandbox.ErrorKindUnsupported}, + {"UPLOAD_UNAVAILABLE", sandbox.ErrorKindUnavailable}, + {"LOCKDOWN_ERROR", sandbox.ErrorKindInternal}, + } { + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusBadRequest, + ErrorCode: tc.code, + Request: &http.Request{Method: http.MethodPost, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusBadRequest}, + }, "create") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != tc.kind { + t.Fatalf("%s: error = %v detail = %#v", tc.code, mapped, detail) + } + } +} diff --git a/sdks/go/providers/blaxel/example_test.go b/sdks/go/providers/blaxel/example_test.go new file mode 100644 index 00000000..9375d362 --- /dev/null +++ b/sdks/go/providers/blaxel/example_test.go @@ -0,0 +1,359 @@ +package blaxel_test + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func ExampleNew() { + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(instance.ProviderName(), instance.ID()) +} + +func ExampleNew_image() { + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Region: sandbox.Value(region), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example-memory"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(instance.ID()) +} + +func ExampleNew_get() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + if err = instance.Refresh(context.Background()); err != nil { + fmt.Println(err) + } +} + +func ExampleNew_delete() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + err = instance.Delete(context.Background(), &sandbox.DeleteOptions{Wait: sandbox.Value(true)}) + if err != nil { + fmt.Println(err) + } +} + +func ExampleNew_close() { + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Region: sandbox.Value(region), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example-close"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + }) + if err != nil { + fmt.Println(err) + return + } + // Close leaves the Blaxel sandbox running. + err = instance.Close(context.Background()) + if err != nil { + fmt.Println(err) + } +} + +func ExampleNew_errors() { + _, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Endpoint: sandbox.Value("https://example.com"), + }) + var detail *sandbox.Error + if errors.As(err, &detail) { + fmt.Println(detail.Field) + } + // Output: + // endpoint +} + +func ExampleNew_process() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"echo", "hello from kit"}, + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(result.GetExitCode(), string(result.GetStdout())) +} + +func ExampleNew_processShell() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Shell: sandbox.Value(`echo "hello from shell"`), + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(result.GetExitCode()) +} + +func ExampleNew_processEnv() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"printenv", "KIT_DEMO"}, + Environment: map[string]string{"KIT_DEMO": "from-kit"}, + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(result.GetStdout())) +} + +func ExampleNew_filesystem() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + files := instance.Filesystem() + if err := files.WriteFile(context.Background(), "/tmp/note.txt", []byte("hello\n"), nil); err != nil { + fmt.Println(err) + return + } + data, err := files.ReadFile(context.Background(), "/tmp/note.txt", nil) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(data)) +} + +func ExampleNew_makeDirectory() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + err = instance.Filesystem().MakeDirectory(context.Background(), "/tmp/kit-dir", nil) + if err != nil { + fmt.Println(err) + } +} + +func ExampleNew_listDirectory() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + entries, err := instance.Filesystem().ListDirectory(context.Background(), "/tmp", nil) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(len(entries)) +} + +func ExampleNew_stat() { + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) + if err != nil { + fmt.Println(err) + return + } + info, err := instance.Filesystem().Stat(context.Background(), "/tmp/note.txt", nil) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(info.GetPath(), info.GetSize()) +} diff --git a/sdks/go/providers/blaxel/filesystem.go b/sdks/go/providers/blaxel/filesystem.go new file mode 100644 index 00000000..76244991 --- /dev/null +++ b/sdks/go/providers/blaxel/filesystem.go @@ -0,0 +1,276 @@ +package blaxel + +import ( + "context" + "errors" + "os" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/blaxel-ai/sdk-go/option" + "github.com/sandbox-kit/kit/sdks/go/sandbox" + "google.golang.org/protobuf/types/known/timestamppb" +) + +var _ sandbox.SandboxFiles = (*backend)(nil) + +func (b *backend) ReadFile(ctx context.Context, id, path string, _ *sandbox.FileReadOptions) ([]byte, error) { + return withFS(b, ctx, fsCall{ID: id, Operation: "read_file"}, func(fs *blaxel.SandboxInstanceFSService) ([]byte, error) { + return fs.ReadBinary(ctx, path) + }) +} + +func (b *backend) WriteFile(ctx context.Context, id, path string, data []byte, _ *sandbox.FileWriteOptions) error { + if data == nil { + data = []byte{} + } + _, err := withFS(b, ctx, fsCall{ID: id, Operation: "write_file"}, func(fs *blaxel.SandboxInstanceFSService) (struct{}, error) { + _, err := fs.WriteBinary(ctx, path, data, "") + return struct{}{}, err + }) + return err +} + +func (b *backend) CopyFromLocal(ctx context.Context, id, localPath, remotePath string, _ *sandbox.FileCopyOptions) error { + if err := localSourceFile(localPath, "copy_from_local"); err != nil { + return err + } + data, err := os.ReadFile(localPath) + if err != nil { + return localPathError(err, "copy_from_local") + } + _, err = withFS(b, ctx, fsCall{ID: id, Operation: "copy_from_local"}, func(fs *blaxel.SandboxInstanceFSService) (struct{}, error) { + _, err := fs.WriteBinary(ctx, remotePath, data, "") + return struct{}{}, err + }) + return err +} + +func (b *backend) CopyToLocal(ctx context.Context, id, remotePath, localPath string, _ *sandbox.FileCopyOptions) error { + if err := localDestination(localPath, "copy_to_local"); err != nil { + return err + } + if err := ensureLocalParent(localPath, "copy_to_local"); err != nil { + return err + } + instance, err := b.fileSandbox(ctx, id, "copy_to_local") + if err != nil { + return err + } + if err := instance.FS.Download(ctx, remotePath, localPath); err != nil { + return mapCallerPathError(err, "copy_to_local", localPath) + } + return nil +} + +func (b *backend) MakeDirectory(ctx context.Context, id, path string, options *sandbox.MakeDirectoryOptions) error { + if err := rejectParents(options); err != nil { + return err + } + _, err := withFS(b, ctx, fsCall{ID: id, Operation: "make_directory"}, func(fs *blaxel.SandboxInstanceFSService) (struct{}, error) { + _, err := fs.Mkdir(ctx, path, "") + return struct{}{}, err + }) + return err +} + +func (b *backend) ListDirectory(ctx context.Context, id, path string, _ *sandbox.ListDirectoryOptions) ([]*sandbox.FileInfo, error) { + return withFS(b, ctx, fsCall{ID: id, Operation: "list_directory"}, func(fs *blaxel.SandboxInstanceFSService) ([]*sandbox.FileInfo, error) { + listing, err := fs.LS(ctx, path) + if err != nil { + return nil, err + } + result := make([]*sandbox.FileInfo, 0, len(listing.Files)+len(listing.Subdirectories)) + // Directories first, then files — matches TypeScript Blaxel listing order. + for _, entry := range listing.Subdirectories { + result = append(result, &sandbox.FileInfo{ + Name: entry.Name, + Path: entry.Path, + Type: sandbox.FileTypeDirectory, + }) + } + for _, entry := range listing.Files { + info, err := blaxelFileInfo(entry, sandbox.FileTypeFile, "list_directory") + if err != nil { + return nil, err + } + result = append(result, info) + } + return result, nil + }) +} + +func (b *backend) Stat(ctx context.Context, id, path string, _ *sandbox.StatOptions) (*sandbox.FileInfo, error) { + instance, err := b.fileSandbox(ctx, id, "stat") + if err != nil { + return nil, err + } + // SandboxInstanceFSService has Read/LS/… but no public Get/Stat. LS always + // calls AsDirectory(), so a file path would look like an empty directory. + // Rebuild the lower SandboxFilesystemService with the instance base URL + // (same construction NewInstance uses for instance.FS) and call Get. + fs := b.filesystemService(instance) + res, err := fs.Get(ctx, path, blaxel.SandboxFilesystemGetParams{}) + if err != nil { + return nil, mapFilesystemError(err, "stat") + } + if res == nil { + return nil, invalidResponse("stat", sandbox.FileFieldPath, "SDK returned no file metadata") + } + if res.JSON.Files.Valid() || res.JSON.Subdirectories.Valid() { + dir := res.AsDirectory() + return &sandbox.FileInfo{ + Name: dir.Name, + Path: dir.Path, + Type: sandbox.FileTypeDirectory, + }, nil + } + file := res.AsFilesystemReadWithContent() + return blaxelFileInfo(blaxel.FilesystemRead{ + Name: file.Name, + Path: file.Path, + Size: file.Size, + Permissions: file.Permissions, + Owner: file.Owner, + Group: file.Group, + LastModified: file.LastModified, + }, sandbox.FileTypeFile, "stat") +} + +func (b *backend) Remove(ctx context.Context, id, path string, options *sandbox.RemoveOptions) error { + recursive := options != nil && options.GetRecursive() + _, err := withFS(b, ctx, fsCall{ID: id, Operation: "remove"}, func(fs *blaxel.SandboxInstanceFSService) (struct{}, error) { + _, err := fs.RM(ctx, path, recursive) + return struct{}{}, err + }) + return err +} + +type fsCall struct { + ID string + Operation string +} + +func withFS[T any]( + b *backend, + ctx context.Context, + call fsCall, + fn func(*blaxel.SandboxInstanceFSService) (T, error), +) (T, error) { + var zero T + instance, err := b.fileSandbox(ctx, call.ID, call.Operation) + if err != nil { + return zero, err + } + result, err := fn(instance.FS) + if err != nil { + // Preserve SandboxErrors raised inside the callback (e.g. list mapping). + var local *sandbox.Error + if errors.As(err, &local) { + return zero, err + } + return zero, mapFilesystemError(err, call.Operation) + } + return result, nil +} + +// mapFilesystemError attaches Field=path on NotFound so portable callers can +// branch the same way as TypeScript Blaxel Stat/list failures. +func mapFilesystemError(err error, operation string) error { + mapped := mapProviderError(err, operation) + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindNotFound || detail.Field != "" { + return mapped + } + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: detail.Kind, + Provider: detail.Provider, + Operation: detail.Operation, + Field: sandbox.FileFieldPath, + Message: detail.Message, + StatusCode: detail.StatusCode, + ProviderCode: detail.ProviderCode, + ProviderSource: detail.ProviderSource, + }, err) +} + +func (b *backend) fileSandbox(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { + instance, err := b.getInstance(ctx, id, operation) + if err != nil { + return nil, err + } + if instance == nil || instance.FS == nil { + return nil, invalidResponse(operation, "", "SDK returned no filesystem") + } + return instance, nil +} + +func (b *backend) filesystemService(instance *blaxel.SandboxInstance) blaxel.SandboxFilesystemService { + opts := append([]option.RequestOption{}, b.client.Options...) + if instance != nil && instance.Metadata.URL != "" { + opts = append(opts, option.WithBaseURL(instance.Metadata.URL)) + } + return blaxel.NewSandboxFilesystemService(opts...) +} + +func rejectParents(options *sandbox.MakeDirectoryOptions) error { + if options == nil || options.Parents == nil || *options.Parents { + return nil + } + return kitError(kitFault{ + Kind: sandbox.ErrorKindUnsupported, + Operation: "make_directory", + Field: sandbox.FileFieldParents, + Message: "parents false is not available", + }) +} + +func blaxelFileInfo(entry blaxel.FilesystemRead, kind sandbox.FileType, operation string) (*sandbox.FileInfo, error) { + if entry.Path == "" && entry.Name == "" { + return nil, invalidResponse(operation, sandbox.FileFieldPath, "SDK returned no file metadata") + } + result := &sandbox.FileInfo{ + Name: entry.Name, + Path: entry.Path, + Type: kind, + Size: entry.Size, + } + if entry.Permissions != "" { + result.Permissions = sandbox.Value(entry.Permissions) + } + if entry.Owner != "" { + result.Owner = sandbox.Value(entry.Owner) + } + if entry.Group != "" { + result.Group = sandbox.Value(entry.Group) + } + if modified := parseOptionalTime(entry.LastModified); modified != nil { + result.ModifiedAt = modified + } + return result, nil +} + +func parseOptionalTime(raw string) *timestamppb.Timestamp { + if raw == "" { + return nil + } + parsed, err := parseBlaxelTime(raw) + if err != nil { + return nil + } + return timestamppb.New(parsed) +} + +func parseBlaxelTime(raw string) (time.Time, error) { + for _, layout := range []string{ + time.RFC3339Nano, + time.RFC3339, + time.RFC1123, + time.RFC1123Z, + } { + if parsed, err := time.Parse(layout, raw); err == nil { + return parsed, nil + } + } + return time.Time{}, errors.New("unrecognized timestamp") +} diff --git a/sdks/go/providers/blaxel/filesystem_test.go b/sdks/go/providers/blaxel/filesystem_test.go new file mode 100644 index 00000000..e07c2286 --- /dev/null +++ b/sdks/go/providers/blaxel/filesystem_test.go @@ -0,0 +1,46 @@ +package blaxel + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestLocalSourceFileValidation(t *testing.T) { + dir := t.TempDir() + missing := filepath.Join(dir, "missing.txt") + if err := localSourceFile(missing, "copy_from_local"); err == nil { + t.Fatal("expected missing file error") + } else { + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindNotFound || detail.Field != sandbox.FileFieldLocalPath { + t.Fatalf("error = %v detail = %#v", err, detail) + } + } + if err := localSourceFile(dir, "copy_from_local"); err == nil { + t.Fatal("expected directory error") + } else { + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.FileFieldLocalPath { + t.Fatalf("error = %v detail = %#v", err, detail) + } + } + file := filepath.Join(dir, "note.txt") + if err := os.WriteFile(file, []byte("hi"), 0o644); err != nil { + t.Fatal(err) + } + if err := localSourceFile(file, "copy_from_local"); err != nil { + t.Fatal(err) + } +} + +func TestRejectParentsFalse(t *testing.T) { + err := rejectParents(&sandbox.MakeDirectoryOptions{Parents: sandbox.Value(false)}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.FileFieldParents { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/providers/blaxel/go.mod b/sdks/go/providers/blaxel/go.mod new file mode 100644 index 00000000..c6a2cdbb --- /dev/null +++ b/sdks/go/providers/blaxel/go.mod @@ -0,0 +1,36 @@ +module github.com/sandbox-kit/kit/sdks/go/providers/blaxel + +go 1.26.0 + +require ( + github.com/blaxel-ai/sdk-go v0.31.0 + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 + google.golang.org/protobuf v1.36.12 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../sandbox + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../internal/sdk diff --git a/sdks/go/providers/blaxel/go.sum b/sdks/go/providers/blaxel/go.sum new file mode 100644 index 00000000..c177a211 --- /dev/null +++ b/sdks/go/providers/blaxel/go.sum @@ -0,0 +1,49 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdks/go/providers/blaxel/instance.go b/sdks/go/providers/blaxel/instance.go new file mode 100644 index 00000000..3d0d4af6 --- /dev/null +++ b/sdks/go/providers/blaxel/instance.go @@ -0,0 +1,24 @@ +package blaxel + +import ( + "context" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// getInstance loads a sandbox handle. Callers check FS / Process / Sandbox. +func (b *backend) getInstance(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { + instance, err := b.client.Sandboxes.GetInstance(ctx, id) + if err != nil { + return nil, mapProviderError(err, operation) + } + return instance, nil +} + +func requireSandbox(instance *blaxel.SandboxInstance, operation string) (*blaxel.Sandbox, error) { + if instance == nil || instance.Sandbox == nil { + return nil, invalidResponse(operation, sandbox.InfoFieldId, "SDK returned no sandbox") + } + return instance.Sandbox, nil +} diff --git a/sdks/go/providers/blaxel/lifecycle.go b/sdks/go/providers/blaxel/lifecycle.go new file mode 100644 index 00000000..c4546e17 --- /dev/null +++ b/sdks/go/providers/blaxel/lifecycle.go @@ -0,0 +1,73 @@ +package blaxel + +import ( + "context" + "errors" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +var _ sandbox.SandboxDeleter = (*backend)(nil) + +const deletePollInterval = time.Second + +// DeleteSandbox permanently deletes a Blaxel sandbox by name. +// Start, stop, pause, archive, and terminate are not implemented. +// When Wait is omitted or false, a successful DELETE returns Deleted with +// Terminal true (Blaxel may still finish asynchronously). When Wait is true, +// Kit polls Get until the name is gone or Deleted. +func (b *backend) DeleteSandbox(ctx context.Context, id string, options *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { + instance, err := b.client.Sandboxes.DeleteInstance(ctx, id) + if err != nil { + return nil, mapProviderError(err, "delete") + } + native, err := requireSandbox(instance, "delete") + if err != nil { + return nil, err + } + info, err := sandboxInfo(native) + if err != nil { + return nil, err + } + if options != nil && options.GetWait() { + // Blaxel has no native wait-for-delete; Kit polls until gone. + return b.waitUntilDeleted(ctx, id) + } + info.State = sandbox.SandboxStateDeleted + info.Terminal = sandbox.Value(true) + info.ProviderState = sandbox.Value(string(blaxel.StatusTerminated)) + return info, nil +} + +func (b *backend) waitUntilDeleted(ctx context.Context, id string) (*sandbox.SandboxInfo, error) { + ticker := time.NewTicker(deletePollInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return nil, mapProviderError(ctx.Err(), "delete") + case <-ticker.C: + info, err := b.RefreshSandbox(ctx, id) + if err != nil { + var detail *sandbox.Error + if errors.As(err, &detail) && detail.Kind == sandbox.ErrorKindNotFound { + return &sandbox.SandboxInfo{ + Id: id, + Provider: "blaxel", + State: sandbox.SandboxStateDeleted, + Terminal: sandbox.Value(true), + ProviderState: sandbox.Value(string(blaxel.StatusTerminated)), + }, nil + } + return nil, err + } + if info.State == sandbox.SandboxStateDeleted { + info.Terminal = sandbox.Value(true) + info.ProviderState = sandbox.Value(string(blaxel.StatusTerminated)) + return info, nil + } + } + } +} diff --git a/sdks/go/providers/blaxel/lifecycle_test.go b/sdks/go/providers/blaxel/lifecycle_test.go new file mode 100644 index 00000000..86b26fdc --- /dev/null +++ b/sdks/go/providers/blaxel/lifecycle_test.go @@ -0,0 +1,114 @@ +package blaxel + +import ( + "context" + "errors" + "net/http" + "sync/atomic" + "testing" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestDeleteSandboxReportsDeletedWithoutWait(t *testing.T) { + var gets atomic.Int32 + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/sandboxes/doomed": + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/doomed": + gets.Add(1) + http.NotFound(w, r) + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + info, err := backend.DeleteSandbox(context.Background(), "doomed", &sandbox.DeleteOptions{}) + if err != nil { + t.Fatal(err) + } + if info.State != sandbox.SandboxStateDeleted || info.GetTerminal() != true || info.GetProviderState() != "TERMINATED" { + t.Fatalf("info = %#v", info) + } + if gets.Load() != 0 { + t.Fatalf("gets = %d", gets.Load()) + } +} + +func TestDeleteSandboxWaitsUntilNotFound(t *testing.T) { + var gets atomic.Int32 + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/sandboxes/doomed": + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/doomed": + n := gets.Add(1) + if n < 2 { + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + return + } + http.NotFound(w, r) + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + info, err := backend.DeleteSandbox(ctx, "doomed", &sandbox.DeleteOptions{Wait: sandbox.Value(true)}) + if err != nil { + t.Fatal(err) + } + if info.State != sandbox.SandboxStateDeleted || info.GetTerminal() != true { + t.Fatalf("info = %#v", info) + } + if gets.Load() < 2 { + t.Fatalf("gets = %d", gets.Load()) + } +} + +func TestDeleteSandboxWaitHonorsCancel(t *testing.T) { + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/sandboxes/doomed": + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/doomed": + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(200 * time.Millisecond) + cancel() + }() + _, err := backend.DeleteSandbox(ctx, "doomed", &sandbox.DeleteOptions{Wait: sandbox.Value(true)}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindCanceled { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/providers/blaxel/local.go b/sdks/go/providers/blaxel/local.go new file mode 100644 index 00000000..a89c8d5f --- /dev/null +++ b/sdks/go/providers/blaxel/local.go @@ -0,0 +1,76 @@ +package blaxel + +import ( + "errors" + "os" + "path/filepath" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func localSourceFile(path, operation string) error { + info, err := os.Stat(path) + if err != nil { + return localPathError(err, operation) + } + if info.IsDir() { + return localDirectoryError(operation) + } + return nil +} + +func localDestination(path, operation string) error { + info, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + return nil + } + if err != nil { + return localPathError(err, operation) + } + if info.IsDir() { + return localDirectoryError(operation) + } + return nil +} + +func ensureLocalParent(path, operation string) error { + parent := filepath.Dir(path) + if parent == "." || parent == string(filepath.Separator) { + return nil + } + if err := os.MkdirAll(parent, 0o755); err != nil { + return localPathError(err, operation) + } + return nil +} + +func localDirectoryError(operation string) error { + return kitError(kitFault{ + Kind: sandbox.ErrorKindInvalidArgument, + Operation: operation, + Field: sandbox.FileFieldLocalPath, + Message: "local path is a directory", + }) +} + +func localPathError(err error, operation string) error { + kind := sandbox.ErrorKindInvalidArgument + if errors.Is(err, os.ErrNotExist) { + kind = sandbox.ErrorKindNotFound + } + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: kind, + Provider: "blaxel", + Operation: operation, + Field: sandbox.FileFieldLocalPath, + Message: err.Error(), + }, err) +} + +func mapCallerPathError(err error, operation, localPath string) error { + var pathError *os.PathError + if errors.As(err, &pathError) && filepath.Clean(pathError.Path) == filepath.Clean(localPath) { + return localPathError(pathError, operation) + } + return mapProviderError(err, operation) +} diff --git a/sdks/go/providers/blaxel/metadata.go b/sdks/go/providers/blaxel/metadata.go new file mode 100644 index 00000000..aabc4821 --- /dev/null +++ b/sdks/go/providers/blaxel/metadata.go @@ -0,0 +1,81 @@ +package blaxel + +import ( + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +type observedSandbox struct { + native *blaxel.Sandbox + info *sandbox.SandboxInfo +} + +func sandboxInfo(native *blaxel.Sandbox) (*sandbox.SandboxInfo, error) { + info, err := mapSandboxInfo(native) + if err != nil { + return info, err + } + observed := &observedSandbox{native: native, info: info} + if err := observed.resources(); err != nil { + return info, err + } + observed.state() + return info, nil +} + +func (s *observedSandbox) resources() error { + resources, err := mapAllocatedResources(s.native) + if err != nil { + return err + } + if resources.MemoryMib != nil { + s.info.Resources = resources + } + return nil +} + +func (s *observedSandbox) state() { + switch s.native.Status { + case blaxel.StatusUploading, blaxel.StatusBuilding, blaxel.StatusDeploying, blaxel.StatusBuilt: + s.info.State = sandbox.SandboxStateQueued + case blaxel.StatusDeployed: + s.info.State = runtimeState(s.native.State, sandbox.SandboxStateRunning) + case blaxel.StatusDeleting, blaxel.StatusDeactivating: + s.info.State = sandbox.SandboxStateDeleting + case blaxel.StatusTerminated, blaxel.StatusDeactivated, blaxel.Status("DELETED"): + // DELETED is not a typed Status constant in sdk-go v0.31.0; accept the + // wire string so refresh matches TypeScript mapSandboxState. + s.info.State = sandbox.SandboxStateDeleted + s.info.Terminal = sandbox.Value(true) + case blaxel.StatusArchiving: + s.info.State = sandbox.SandboxStateArchiving + case blaxel.StatusArchived: + s.info.State = sandbox.SandboxStateArchived + case blaxel.StatusUnarchiving: + s.info.State = sandbox.SandboxStateStarting + case blaxel.StatusFailed: + s.info.State = sandbox.SandboxStateError + default: + if mapped, ok := runtimeOnlyState(s.native.State); ok { + s.info.State = mapped + } + } +} + +func runtimeState(runtime blaxel.SandboxState, fallback sandbox.SandboxState) sandbox.SandboxState { + if runtime == blaxel.SandboxStateStandby { + return sandbox.SandboxStatePaused + } + return fallback +} + +func runtimeOnlyState(runtime blaxel.SandboxState) (sandbox.SandboxState, bool) { + switch runtime { + case blaxel.SandboxStateRunning: + return sandbox.SandboxStateRunning, true + case blaxel.SandboxStateStandby: + return sandbox.SandboxStatePaused, true + default: + return 0, false + } +} diff --git a/sdks/go/providers/blaxel/process.go b/sdks/go/providers/blaxel/process.go new file mode 100644 index 00000000..3acd5b65 --- /dev/null +++ b/sdks/go/providers/blaxel/process.go @@ -0,0 +1,172 @@ +package blaxel + +import ( + "context" + "strings" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/internal/sdk" + "github.com/sandbox-kit/kit/sdks/go/sandbox" + "google.golang.org/protobuf/types/known/durationpb" +) + +const ( + // Process.Wait treats 0 as 1m; only -1 is unlimited. + blaxelWaitUnlimited = time.Duration(-1) +) + +var _ sandbox.SandboxProcess = (*backend)(nil) + +func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.ExecOptions) (*sandbox.ExecResult, error) { + instance, err := b.processSandbox(ctx, id, "run") + if err != nil { + return nil, err + } + timeout, err := commandTimeout("run", request.GetTimeout()) + if err != nil { + return nil, err + } + body, err := processRequest(request, timeout) + if err != nil { + return nil, err + } + started, err := instance.Process.New(ctx, body) + if err != nil { + return nil, mapProviderError(err, "run") + } + return finishProcess(ctx, instance, started, timeout) +} + +// processRequest starts without waitForCompletion; Kit waits via Process.Wait. +// Timeout 0 disables Blaxel process auto-kill. +func processRequest(request *sandbox.ExecOptions, timeout time.Duration) (blaxel.ProcessRequestParam, error) { + command, err := blaxelCommand(request) + if err != nil { + return blaxel.ProcessRequestParam{}, err + } + body := blaxel.ProcessRequestParam{ + Command: command, + Timeout: blaxel.Int(int64(timeout / time.Second)), + WaitForCompletion: blaxel.Bool(false), + } + if request.WorkingDirectory != nil { + body.WorkingDir = blaxel.String(*request.WorkingDirectory) + } + if request.Environment != nil { + body.Env = request.Environment + } + return body, nil +} + +// finishProcess returns a terminal start as-is; otherwise one native Wait. +func finishProcess(ctx context.Context, instance *blaxel.SandboxInstance, started *blaxel.ProcessResponse, timeout time.Duration) (*sandbox.ExecResult, error) { + if started == nil || processFinished(started) { + return execResult(started) + } + identifier := processIdentifier(started) + if identifier == "" { + return nil, invalidResponse("run", "", "SDK returned no process identifier") + } + budget := blaxelWaitUnlimited + if timeout > 0 { + budget = timeout + } + finished, err := instance.Process.Wait(ctx, identifier, budget, 0) + if err != nil { + return nil, mapProviderError(err, "run") + } + return execResult(finished) +} + +var terminalProcessStatus = map[blaxel.ProcessResponseStatus]struct{}{ + blaxel.ProcessResponseStatusCompleted: {}, + blaxel.ProcessResponseStatusFailed: {}, + blaxel.ProcessResponseStatusKilled: {}, + blaxel.ProcessResponseStatusStopped: {}, +} + +func processFinished(response *blaxel.ProcessResponse) bool { + if response == nil { + return false + } + _, ok := terminalProcessStatus[response.Status] + return ok +} + +func processIdentifier(response *blaxel.ProcessResponse) string { + if response == nil { + return "" + } + if response.Pid != "" { + return response.Pid + } + return response.Name +} + +func execResult(response *blaxel.ProcessResponse) (*sandbox.ExecResult, error) { + if response == nil { + return nil, invalidResponse("run", "", "SDK returned no exec result") + } + return &sandbox.ExecResult{ + ExitCode: int32(response.ExitCode), + Stdout: []byte(response.Stdout), + Stderr: []byte(response.Stderr), + }, nil +} + +func (b *backend) processSandbox(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { + instance, err := b.getInstance(ctx, id, operation) + if err != nil { + return nil, err + } + if instance == nil || instance.Process == nil { + return nil, invalidResponse(operation, "", "SDK returned no process API") + } + return instance, nil +} + +func blaxelCommand(request *sandbox.ExecOptions) (string, error) { + if request.Shell != nil { + return *request.Shell, nil + } + if len(request.Argv) == 0 { + // Shared kit wording (no provider infix) — keep identical across providers. + return "", sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidArgument, + Provider: "blaxel", + Operation: "run", + Field: sandbox.ExecFieldArgv, + Message: "sandbox-kit: set argv or shell", + }, nil) + } + return posixShellJoin(request.Argv), nil +} + +func commandTimeout(operation string, value *durationpb.Duration) (time.Duration, error) { + timeout, ok := sdk.WholeSecondDuration(value) + if !ok { + return 0, kitError(kitFault{ + Kind: sandbox.ErrorKindInvalidArgument, + Operation: operation, + Field: sandbox.ExecFieldTimeout, + Message: "timeout must be a whole number of seconds", + }) + } + return timeout, nil +} + +func posixShellJoin(argv []string) string { + parts := make([]string, len(argv)) + for i, arg := range argv { + parts[i] = posixQuote(arg) + } + return strings.Join(parts, " ") +} + +func posixQuote(value string) string { + if value == "" { + return "''" + } + return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'" +} diff --git a/sdks/go/providers/blaxel/process_test.go b/sdks/go/providers/blaxel/process_test.go new file mode 100644 index 00000000..eebb2a39 --- /dev/null +++ b/sdks/go/providers/blaxel/process_test.go @@ -0,0 +1,294 @@ +package blaxel + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + "time" + + blaxelsdk "github.com/blaxel-ai/sdk-go" + "github.com/blaxel-ai/sdk-go/option" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestBlaxelCommandArgvAndShell(t *testing.T) { + command, err := blaxelCommand(&sandbox.ExecOptions{Argv: []string{"echo", "hello world"}}) + if err != nil || command != "'echo' 'hello world'" { + t.Fatalf("argv command = %q err=%v", command, err) + } + command, err = blaxelCommand(&sandbox.ExecOptions{Shell: sandbox.Value("echo hi")}) + if err != nil || command != "echo hi" { + t.Fatalf("shell command = %q err=%v", command, err) + } +} + +func TestCommandTimeoutWholeSeconds(t *testing.T) { + timeout, err := commandTimeout("run", sandbox.Duration(2*time.Second)) + if err != nil || timeout != 2*time.Second { + t.Fatalf("timeout = %v err=%v", timeout, err) + } + _, err = commandTimeout("run", sandbox.Duration(1500*time.Millisecond)) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Field != sandbox.ExecFieldTimeout { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func TestRunCommandStartsWithoutWaitForCompletion(t *testing.T) { + var posts []map[string]any + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + body := readJSON(t, r) + posts = append(posts, body) + writeProcess(t, w, "completed", 0, "ok", "") + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + for _, timeout := range []*sandbox.ExecOptions{ + {Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(5 * time.Second), WorkingDirectory: sandbox.Value("/workspace"), Environment: map[string]string{"MODE": "test"}}, + {Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(60 * time.Second)}, + } { + result, err := backend.RunCommand(context.Background(), "sbx", timeout) + if err != nil { + t.Fatal(err) + } + if result.GetExitCode() != 0 || string(result.GetStdout()) != "ok" || string(result.GetStderr()) != "" { + t.Fatalf("result = %#v", result) + } + } + if len(posts) != 2 { + t.Fatalf("posts = %#v", posts) + } + if posts[0]["waitForCompletion"] != false || posts[0]["timeout"] != float64(5) || posts[0]["command"] != "'echo' 'hi'" || posts[0]["workingDir"] != "/workspace" { + t.Fatalf("short post = %#v", posts[0]) + } + env, _ := posts[0]["env"].(map[string]any) + if env["MODE"] != "test" { + t.Fatalf("env = %#v", posts[0]["env"]) + } + if posts[1]["waitForCompletion"] != false || posts[1]["timeout"] != float64(60) { + t.Fatalf("60s post = %#v", posts[1]) + } +} + +func TestRunCommandWaitsWhenStartReturnsRunning(t *testing.T) { + var gets atomic.Int32 + var posts []map[string]any + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + posts = append(posts, readJSON(t, r)) + writeProcess(t, w, "running", 0, "", "") + case r.Method == http.MethodGet && r.URL.Path == "/process/123": + if gets.Add(1) == 1 { + http.Error(w, "gateway timeout", http.StatusGatewayTimeout) + return + } + writeProcess(t, w, "failed", 7, "out", "err") + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + cases := []struct { + name string + timeout *sandbox.ExecOptions + seconds float64 + }{ + {"omit", &sandbox.ExecOptions{Shell: sandbox.Value("echo hi")}, 0}, + {"zero", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(0)}, 0}, + {"long", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(61 * time.Second)}, 61}, + {"short", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(5 * time.Second)}, 5}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + gets.Store(0) + result, err := backend.RunCommand(context.Background(), "sbx", tc.timeout) + if err != nil { + t.Fatal(err) + } + if result.GetExitCode() != 7 || string(result.GetStdout()) != "out" || string(result.GetStderr()) != "err" { + t.Fatalf("result = %#v", result) + } + if gets.Load() != 2 { + t.Fatalf("gets = %d", gets.Load()) + } + }) + } + if len(posts) != len(cases) { + t.Fatalf("posts = %d", len(posts)) + } + for i, tc := range cases { + if posts[i]["waitForCompletion"] != false || posts[i]["timeout"] != tc.seconds { + t.Fatalf("%s post = %#v", tc.name, posts[i]) + } + } +} + +func TestRunCommandReturnsAFinishedStartWithoutWaiting(t *testing.T) { + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + writeProcess(t, w, "killed", 9, "partial", "stopped") + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + result, err := backend.RunCommand(context.Background(), "sbx", &sandbox.ExecOptions{Argv: []string{"sleep", "1"}}) + if err != nil { + t.Fatal(err) + } + if result.GetExitCode() != 9 || string(result.GetStdout()) != "partial" || string(result.GetStderr()) != "stopped" { + t.Fatalf("result = %#v", result) + } +} + +func TestRunCommandRequiresAProcessIdentifier(t *testing.T) { + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + writeJSON(t, w, map[string]any{"status": "running", "exitCode": 0, "stdout": "", "stderr": ""}) + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + _, err := backend.RunCommand(context.Background(), "sbx", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidResponse || detail.Operation != "run" { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func TestRunCommandCancelAndGatewayTimeout(t *testing.T) { + t.Run("cancel", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + writeProcess(t, w, "running", 0, "", "") + case r.Method == http.MethodGet && r.URL.Path == "/process/123": + cancel() + writeProcess(t, w, "running", 0, "", "") + default: + http.NotFound(w, r) + } + }) + _, err := backend.RunCommand(ctx, "sbx", &sandbox.ExecOptions{Argv: []string{"sleep", "30"}, Timeout: sandbox.Duration(0)}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindCanceled { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + t.Run("gateway timeout", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + defer cancel() + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + writeProcess(t, w, "running", 0, "", "") + case r.Method == http.MethodGet && r.URL.Path == "/process/123": + http.Error(w, "gateway timeout", http.StatusGatewayTimeout) + default: + http.NotFound(w, r) + } + }) + _, err := backend.RunCommand(ctx, "sbx", &sandbox.ExecOptions{Argv: []string{"sleep", "90"}, Timeout: sandbox.Duration(90 * time.Second)}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindTimeout { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) +} + +func processBackend(t *testing.T, handler http.HandlerFunc) *backend { + t.Helper() + server := httptest.NewServer(handler) + t.Cleanup(server.Close) + client := blaxelsdk.NewClient( + option.WithBaseURL(server.URL), + option.WithAPIKey("test"), + option.WithMaxRetries(0), + ) + return &backend{client: &client} +} + +func writeProcess(t *testing.T, w http.ResponseWriter, status string, exitCode int, stdout, stderr string) { + t.Helper() + writeJSON(t, w, map[string]any{ + "command": "echo", + "completedAt": "", + "exitCode": exitCode, + "logs": stdout + stderr, + "name": "proc", + "pid": "123", + "startedAt": "", + "status": status, + "stderr": stderr, + "stdout": stdout, + "workingDir": "/", + }) +} + +func writeJSON(t *testing.T, w http.ResponseWriter, body any) { + t.Helper() + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(body); err != nil { + t.Errorf("encode: %v", err) + } +} + +func readJSON(t *testing.T, r *http.Request) map[string]any { + t.Helper() + data, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("read body: %v", err) + } + var body map[string]any + if err := json.Unmarshal(data, &body); err != nil { + t.Fatalf("decode %s: %v", data, err) + } + return body +} + +func TestMapProviderErrorWaitDeadlineKeepsGatewayTimeout(t *testing.T) { + parsed, err := http.NewRequest(http.MethodGet, "https://example.invalid/process/123", nil) + if err != nil { + t.Fatal(err) + } + apiErr := &blaxelsdk.Error{ + StatusCode: http.StatusGatewayTimeout, + Request: parsed, + Response: &http.Response{StatusCode: http.StatusGatewayTimeout}, + } + mapped := mapProviderError(fmt.Errorf( + "process 123 did not finish in time; it may still be running: %w", + errors.Join(context.DeadlineExceeded, apiErr), + ), "run") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindTimeout || detail.GetStatusCode() != http.StatusGatewayTimeout { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} diff --git a/sdks/go/providers/blaxel/provider.go b/sdks/go/providers/blaxel/provider.go new file mode 100644 index 00000000..de2e8d43 --- /dev/null +++ b/sdks/go/providers/blaxel/provider.go @@ -0,0 +1,85 @@ +package blaxel + +import ( + "context" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// Provider is the Blaxel provider. Pass New to sandbox.NewClient. +type Provider struct{} + +// backend owns the initialized native SDK and captured configuration. +type backend struct { + client *blaxel.Client + // region is the captured default region; create requires it when set on Config. + region *string +} + +// New returns the Blaxel provider for sandbox.NewClient. +// +// Example: +// +// config := &sandbox.Config{ +// Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ +// ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, +// }}, +// Region: sandbox.Value("us-pdx-1"), +// } +// if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { +// config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +// } +// client, err := sandbox.NewClient(New(), config) +func New() *Provider { return &Provider{} } + +func (*Provider) Name() string { return "blaxel" } +func (*backend) Name() string { return "blaxel" } + +// NewClient opens the Blaxel SDK from config. +// Prefer sandbox.NewClient(New(), config); calling this directly skips shared client wiring. +func (*Provider) NewClient(config *sandbox.Config) (sandbox.Backend, error) { + backend, err := newBackend(config) + return backend, mapProviderError(err, "initialize") +} + +func newBackend(config *sandbox.Config) (sandbox.Backend, error) { + if config == nil { + config = &sandbox.Config{} + } + opts, err := mapClientConfig(config) + if err != nil { + return nil, err + } + native := blaxel.NewClient(opts...) + result := &backend{client: &native} + result.capture(config) + return result, nil +} + +func (b *backend) capture(config *sandbox.Config) { + if config == nil || config.Region == nil { + return + } + value := *config.Region + b.region = &value +} + +func (b *backend) Create(ctx context.Context, request *sandbox.CreateOptions) (*sandbox.CreateResult, error) { + response, err := b.create(ctx, request) + return response, mapProviderError(err, "create") +} + +// Close releases local provider state only. It does not stop or delete cloud sandboxes. +func (b *backend) Close(ctx context.Context) error { + if err := ctx.Err(); err != nil { + return mapProviderError(err, "close") + } + return nil +} + +func (*backend) CloseSandbox(context.Context, string) error { return nil } + +var _ sandbox.Provider = (*Provider)(nil) +var _ sandbox.Backend = (*backend)(nil) +var _ sandbox.SandboxCreator = (*backend)(nil) diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go new file mode 100644 index 00000000..3b8cc596 --- /dev/null +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -0,0 +1,101 @@ +package blaxel + +import ( + "regexp" + "strings" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +var providerEnvNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) + +// Blaxel Metadata.Name: lowercase alphanumeric with single hyphens, max 49. +var providerSandboxNamePattern = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`) + +// Matches Daytona/Modal outbound domain checks. +var providerDomainPattern = regexp.MustCompile(`(?i)^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.?$`) + +const providerSandboxNameMaxLen = 49 + +// validateProviderCreate rejects creation settings this provider cannot honor. +// Inventoried in specs/providers/blaxel.yaml checks; kept in sync by hand. +func validateProviderCreate(request *sandbox.CreateOptions) error { + if request == nil { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") + } + name := request.GetName() + if strings.TrimSpace(name) == "" { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") + } + if len(name) > providerSandboxNameMaxLen || !providerSandboxNamePattern.MatchString(name) { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name must be lowercase alphanumeric with single hyphens, at most 49 characters") + } + if request.GetSource().GetImage() == nil || strings.TrimSpace(request.GetSource().GetImage().Reference) == "" { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldSourceImage, "an explicit image source is required") + } + if request.GetSource().GetSnapshot() != nil || request.GetSource().GetWarmPool() != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldSource, "only an image source is supported") + } + if request.Placement != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldPlacement, "per-request Blaxel placement is unsupported; use Config.region") + } + if err := rejectUnsupportedResources(request.Resources); err != nil { + return err + } + for name := range request.Environment { + if !providerEnvNamePattern.MatchString(name) { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldEnvironment, "environment is outside supported values") + } + } + if err := rejectInvalidOutboundDomains(request.Network); err != nil { + return err + } + return nil +} + +func rejectInvalidOutboundDomains(network *sandbox.NetworkConfig) error { + if network == nil || network.OutboundDomains == nil { + return nil + } + for _, value := range network.OutboundDomains.Entries { + domain := strings.TrimPrefix(value, "*.") + if !providerDomainPattern.MatchString(domain) || len(domain) > 253 { + return providerCheckError( + sandbox.ErrorKindInvalidArgument, + sandbox.CreateFieldNetworkOutboundDomainsEntries, + "network.outbound_domains.entries is outside supported values", + ) + } + } + return nil +} + +func rejectUnsupportedResources(resources *sandbox.Resources) error { + if resources == nil { + return nil + } + for _, check := range []struct { + set bool + field string + msg string + }{ + {resources.CpuCores != nil, sandbox.CreateFieldResourcesCpuCores, "Blaxel derives CPU from memory; cpu_cores is not mapped"}, + {resources.CpuLimitCores != nil, sandbox.CreateFieldResourcesCpuLimitCores, "Blaxel resource limits are not mapped"}, + {resources.MemoryLimitMib != nil, sandbox.CreateFieldResourcesMemoryLimitMib, "Blaxel resource limits are not mapped"}, + {resources.DiskMib != nil, sandbox.CreateFieldResourcesDiskMib, "Blaxel disk size is not mapped as MiB"}, + } { + if check.set { + return providerCheckError(sandbox.ErrorKindUnsupported, check.field, check.msg) + } + } + return nil +} + +func providerCheckError(kind sandbox.ErrorKind, field, message string) error { + return kitError(kitFault{ + Kind: kind, + Operation: "create", + Field: field, + Message: message, + }) +} diff --git a/sdks/go/providers/blaxel/provider_mappings.go b/sdks/go/providers/blaxel/provider_mappings.go new file mode 100644 index 00000000..b9a1d362 --- /dev/null +++ b/sdks/go/providers/blaxel/provider_mappings.go @@ -0,0 +1,159 @@ +package blaxel + +import ( + "fmt" + "math" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/blaxel-ai/sdk-go/shared" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +const ( + // Blaxel measures memory in decimal MB while Kit exposes binary MiB. + // 1 MiB = 1.048576 MB = 131072 / 125000 MB. + blaxelMegabytesPerMebibyteNumerator uint64 = 131072 + blaxelMegabytesPerMebibyteDenominator uint64 = 125000 +) + +// mapCommonCreateFields maps name, environment, and labels into the native +// create request and returns the fields that were not mapped. +func mapCommonCreateFields(source *sandbox.CreateOptions) (blaxel.SandboxNewParams, *sandbox.CreateOptions, error) { + target := blaxel.SandboxNewParams{} + if source == nil { + return target, &sandbox.CreateOptions{}, nil + } + remaining := cloneMessage(source) + if source.Name != nil { + target.Sandbox.Metadata.Name = source.GetName() + } + remaining.Name = nil + if len(source.Environment) > 0 { + envs := make([]shared.EnvParam, 0, len(source.Environment)) + for name, value := range source.Environment { + envs = append(envs, shared.EnvParam{ + Name: blaxel.String(name), + Value: blaxel.String(value), + }) + } + target.Sandbox.Spec.Runtime.Envs = envs + } + remaining.Environment = nil + if source.Labels != nil { + target.Sandbox.Metadata.Labels = source.Labels + } + remaining.Labels = nil + return target, remaining, nil +} + +// mapResources maps supplied memory into the native request and returns the +// fields that were not mapped. +func mapResources(source *sandbox.Resources) (blaxel.SandboxRuntimeParam, *sandbox.Resources, error) { + target := blaxel.SandboxRuntimeParam{} + if source == nil { + return target, &sandbox.Resources{}, nil + } + remaining := cloneMessage(source) + if source.MemoryMib != nil { + value := source.GetMemoryMib() + if value == 0 || value > math.MaxInt64 { + return target, remaining, providerCheckError( + sandbox.ErrorKindInvalidArgument, + sandbox.CreateFieldResourcesMemoryMib, + "memory_mib cannot be represented", + ) + } + memory, ok := mibToBlaxelMB(value) + if !ok { + return target, remaining, providerCheckError( + sandbox.ErrorKindInvalidArgument, + sandbox.CreateFieldResourcesMemoryMib, + "memory_mib cannot be represented", + ) + } + target.Memory = blaxel.Int(memory) + } + remaining.MemoryMib = nil + return target, remaining, nil +} + +// mapSandboxInfo maps native fields into shared metadata. It does not invent +// values the SDK omitted. +func mapSandboxInfo(source *blaxel.Sandbox) (*sandbox.SandboxInfo, error) { + target := &sandbox.SandboxInfo{Provider: "blaxel"} + if source == nil { + return target, nil + } + name := source.Metadata.Name + target.Id = name + if name != "" { + target.Name = sandbox.Value(name) + } + if source.Status != "" { + target.ProviderState = sandbox.Value(string(source.Status)) + } else if source.State != "" { + target.ProviderState = sandbox.Value(string(source.State)) + } + if source.Spec.Region != "" { + target.Region = sandbox.Value(source.Spec.Region) + } + if source.Metadata.Labels != nil { + target.Labels = source.Metadata.Labels + } + return target, nil +} + +// mapAllocatedResources maps native memory into shared metadata. +func mapAllocatedResources(source *blaxel.Sandbox) (*sandbox.Resources, error) { + target := &sandbox.Resources{} + if source == nil || source.Spec.Runtime.Memory <= 0 { + return target, nil + } + target.MemoryMib = sandbox.Value(blaxelMBToMib(uint64(source.Spec.Runtime.Memory))) + return target, nil +} + +// mibToBlaxelMB rounds up so a Kit request is never under-provisioned. +func mibToBlaxelMB(mib uint64) (int64, bool) { + if mib == 0 || mib > math.MaxInt64/blaxelMegabytesPerMebibyteNumerator { + return 0, false + } + mb := (mib*blaxelMegabytesPerMebibyteNumerator + blaxelMegabytesPerMebibyteDenominator - 1) / blaxelMegabytesPerMebibyteDenominator + if mb > math.MaxInt64 { + return 0, false + } + return int64(mb), true +} + +// blaxelMBToMib reports the whole MiB guaranteed by a native decimal-MB value. +func blaxelMBToMib(mb uint64) uint64 { + return mb * blaxelMegabytesPerMebibyteDenominator / blaxelMegabytesPerMebibyteNumerator +} + +// formatBlaxelDuration formats a whole-second duration as a Blaxel TTL string. +// minimum is the Blaxel floor for that policy (max-age TTL: 5m, idle TTL: 1m). +// Rejection text matches TypeScript durationString. +func formatBlaxelDuration(value, minimum time.Duration) (string, error) { + if value <= 0 || value%time.Second != 0 { + return "", fmt.Errorf("duration must be a positive whole number of seconds") + } + if value < minimum { + return "", fmt.Errorf("duration must be at least %s", blaxelDurationUnits(minimum)) + } + return blaxelDurationUnits(value), nil +} + +func blaxelDurationUnits(value time.Duration) string { + seconds := int64(value / time.Second) + switch { + case seconds%86400 == 0: + return fmt.Sprintf("%dd", seconds/86400) + case seconds%3600 == 0: + return fmt.Sprintf("%dh", seconds/3600) + case seconds%60 == 0: + return fmt.Sprintf("%dm", seconds/60) + default: + return fmt.Sprintf("%ds", seconds) + } +} diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go new file mode 100644 index 00000000..5787501e --- /dev/null +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -0,0 +1,383 @@ +package blaxel + +import ( + "errors" + "strings" + "testing" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestWhitespaceNameIsRequired(t *testing.T) { + _, err := planCreate(imaged(&sandbox.CreateOptions{Name: sandbox.Value(" ")}), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) { + t.Fatalf("error = %v", err) + } + if detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.CreateFieldName { + t.Fatalf("detail = %#v", detail) + } + if !strings.Contains(detail.Message, "name is required") { + t.Fatalf("message = %q", detail.Message) + } +} + +func TestRejectedCreateSettings(t *testing.T) { + for _, tc := range []struct { + name string + request *sandbox.CreateOptions + kind sandbox.ErrorKind + field string + }{ + { + name: "missing name", + request: imaged(&sandbox.CreateOptions{}), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldName, + }, + { + name: "whitespace name", + request: imaged(&sandbox.CreateOptions{ + Name: sandbox.Value(" "), + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldName, + }, + { + name: "invalid name", + request: imaged(&sandbox.CreateOptions{ + Name: sandbox.Value("My_Sandbox"), + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldName, + }, + { + name: "missing image", + request: &sandbox.CreateOptions{Name: sandbox.Value("sandbox")}, + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldSourceImage, + }, + { + name: "blank image reference", + request: &sandbox.CreateOptions{ + Name: sandbox.Value("sandbox"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: " "}, + }}, + }, + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldSourceImage, + }, + { + name: "invalid outbound domain", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"not a domain!!!"}}, + }, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldNetworkOutboundDomainsEntries, + }, + { + name: "nil port", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{Ports: []*sandbox.PortBinding{nil}}, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldNetworkPorts, + }, + { + name: "cpu cores", + request: namedImage(&sandbox.CreateOptions{ + Resources: &sandbox.Resources{CpuCores: sandbox.Value(2.0)}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldResourcesCpuCores, + }, + { + name: "disk", + request: namedImage(&sandbox.CreateOptions{ + Resources: &sandbox.Resources{DiskMib: sandbox.Value(uint64(1024))}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldResourcesDiskMib, + }, + { + name: "placement", + request: namedImage(&sandbox.CreateOptions{ + Placement: &sandbox.Placement{Cloud: sandbox.Value("aws")}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldPlacement, + }, + { + name: "security", + request: namedImage(&sandbox.CreateOptions{ + Security: &sandbox.SecurityConfig{PublicAccess: sandbox.Value(true)}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldSecurity, + }, + { + name: "private network", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{PrivateNetwork: sandbox.Value(true)}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldNetwork, + }, + { + name: "egress mode", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{Egress: sandbox.EgressModeRestricted}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldNetwork, + }, + { + name: "ephemeral", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{Ephemeral: sandbox.Value(true)}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldLifetime, + }, + { + name: "fractional maximum lifetime", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{MaximumLifetime: sandbox.Duration(1500 * time.Millisecond)}, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldLifetimeMaximumLifetime, + }, + { + name: "maximum lifetime below floor", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{MaximumLifetime: sandbox.Duration(4 * time.Minute)}, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldLifetimeMaximumLifetime, + }, + { + name: "idle terminate below floor", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{ + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(30 * time.Second), + }, + }, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldLifetimeIdleTerminateAfter, + }, + { + name: "idle terminate after required", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{ + IdleTerminate: &sandbox.AutomaticAction{Mode: sandbox.PolicyModeAfter}, + }, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldLifetimeIdleTerminateAfter, + }, + { + name: "reserved port", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{ + {Port: 443, Transport: sandbox.PortTransportTLS}, + }, + }, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldNetworkPortsPort, + }, + } { + t.Run(tc.name, func(t *testing.T) { + _, err := planCreate(tc.request, "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != tc.kind || detail.Field != tc.field { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + } +} + +func TestRejectedClientSettings(t *testing.T) { + for _, tc := range []struct { + name string + config *sandbox.Config + field string + kind sandbox.ErrorKind + }{ + { + name: "endpoint", + config: &sandbox.Config{Endpoint: sandbox.Value("https://example.invalid")}, + field: sandbox.ConfigFieldEndpoint, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "app", + config: &sandbox.Config{Scope: &sandbox.Scope{AppName: sandbox.Value("app")}}, + field: sandbox.ConfigFieldScopeAppName, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "environment", + config: &sandbox.Config{Scope: &sandbox.Scope{Environment: sandbox.Value("env")}}, + field: sandbox.ConfigFieldScopeEnvironment, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "organization", + config: &sandbox.Config{Scope: &sandbox.Scope{OrganizationId: sandbox.Value("org")}}, + field: sandbox.ConfigFieldScopeOrganizationId, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "project", + config: &sandbox.Config{Scope: &sandbox.Scope{ProjectId: sandbox.Value("project")}}, + field: sandbox.ConfigFieldScopeProjectId, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "token pair", + config: &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{TokenPair: &sandbox.TokenPairCredentials{Id: "id", Secret: "secret"}}}}, + field: sandbox.ConfigFieldAuth, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "bearer", + config: &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.BearerToken{BearerToken: &sandbox.BearerTokenCredentials{Token: "jwt"}}}}, + field: sandbox.ConfigFieldAuth, + kind: sandbox.ErrorKindUnsupported, + }, + } { + t.Run(tc.name, func(t *testing.T) { + _, err := mapClientConfig(tc.config) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != tc.kind || detail.Field != tc.field { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + } +} + +func TestMissingRegionRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{}), "") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.ConfigFieldRegion { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func TestCreateTimeoutAcceptedAsKitDeadline(t *testing.T) { + // Shared Client.Create injects Config.Timeout into provisioning.timeout and + // applies it as the context deadline. Blaxel must not reject that field. + if _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Provisioning: &sandbox.ProvisioningOptions{Timeout: sandbox.Duration(30 * time.Second)}, + }), "us-pdx-1"); err != nil { + t.Fatal(err) + } +} + +func TestReadinessRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Readiness: &sandbox.ReadinessProbe{Check: &sandbox.TCPPort{TcpPort: 8080}}, + }), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.CreateFieldReadiness { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func TestWaitForReadyRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Provisioning: &sandbox.ProvisioningOptions{WaitFor: sandbox.WaitConditionReady}, + }), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.CreateFieldProvisioningWaitFor { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func TestInvalidEnvironmentNameRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Environment: map[string]string{"NOT VALID": "value"}, + }), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.CreateFieldEnvironment { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func imaged(request *sandbox.CreateOptions) *sandbox.CreateOptions { + request.Source = &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}} + return request +} + +func namedImage(request *sandbox.CreateOptions) *sandbox.CreateOptions { + request.Name = sandbox.Value("sandbox") + return imaged(request) +} + +func TestMappedNetworkPortsAndDomains(t *testing.T) { + plan, err := planCreate(namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"example.com"}}, + Ports: []*sandbox.PortBinding{ + {Port: 3000, Transport: sandbox.PortTransportTLS}, + {Port: 3001, Transport: sandbox.PortTransportPlain}, + }, + }, + }), "us-pdx-1") + if err != nil { + t.Fatal(err) + } + if got := plan.params.Sandbox.Spec.Network.AllowedDomains; len(got) != 1 || got[0] != "example.com" { + t.Fatalf("AllowedDomains = %#v", got) + } + ports := plan.params.Sandbox.Spec.Runtime.Ports + if len(ports) != 2 || ports[0].Target != 3000 || ports[0].Protocol != blaxel.PortProtocolTls { + t.Fatalf("Ports = %#v", ports) + } + if ports[1].Target != 3001 || ports[1].Protocol != blaxel.PortProtocolTcp { + t.Fatalf("Ports[1] = %#v", ports[1]) + } +} + +func TestDefaultPortOmitsProtocol(t *testing.T) { + plan, err := planCreate(namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{ + {Port: 3000, Transport: sandbox.PortTransportDefault}, + }, + }, + }), "us-pdx-1") + if err != nil { + t.Fatal(err) + } + ports := plan.params.Sandbox.Spec.Runtime.Ports + if len(ports) != 1 || ports[0].Target != 3000 || ports[0].Protocol != "" { + t.Fatalf("Ports = %#v", ports) + } +} + +func TestRejectsHttp2TlsPortTransport(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{ + {Port: 3000, Transport: sandbox.PortTransportHTTP2TLS}, + }, + }, + }), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || + detail.Kind != sandbox.ErrorKindUnsupported || + detail.Field != sandbox.CreateFieldNetworkPortsTransport { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/providers/blaxel/sandbox.go b/sdks/go/providers/blaxel/sandbox.go new file mode 100644 index 00000000..1bf230b0 --- /dev/null +++ b/sdks/go/providers/blaxel/sandbox.go @@ -0,0 +1,83 @@ +package blaxel + +import ( + "context" + "strconv" + "time" + + "github.com/blaxel-ai/sdk-go/option" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// create calls the Blaxel SDK and returns shared metadata. +// Create requires a name and an image source. Native create is used; create-if- +// not-exists is not. +func (b *backend) create(ctx context.Context, request *sandbox.CreateOptions) (*sandbox.CreateResult, error) { + if request == nil { + request = &sandbox.CreateOptions{} + } + region := "" + if b.region != nil { + region = *b.region + } + plan, err := planCreate(request, region) + if err != nil { + return nil, err + } + instance, err := b.client.Sandboxes.NewInstance(ctx, plan.params, blaxelCreateOptions(request)...) + if err != nil { + return nil, err + } + native, err := requireSandbox(instance, "create") + if err != nil { + return nil, err + } + info, err := sandboxInfo(native) + if err != nil { + return nil, err + } + return &sandbox.CreateResult{Sandbox: info}, nil +} + +// blaxelCreateOptions forwards Blaxel's create timeout when Kit's create +// deadline is a whole number of seconds in 1..50. Outside that range Kit keeps +// the context deadline only (same bounds as @blaxel/core). +func blaxelCreateOptions(request *sandbox.CreateOptions) []option.RequestOption { + seconds := blaxelCreateTimeoutSeconds(request) + if seconds == 0 { + return nil + } + return []option.RequestOption{ + option.WithHeader("X-Blaxel-Creation-Timeout", strconv.Itoa(seconds)), + } +} + +func blaxelCreateTimeoutSeconds(request *sandbox.CreateOptions) int { + if request == nil || request.Provisioning == nil || request.Provisioning.Timeout == nil { + return 0 + } + timeout := request.Provisioning.Timeout.AsDuration() + if timeout <= 0 || timeout%time.Second != 0 { + return 0 + } + seconds := int(timeout / time.Second) + if seconds < 1 || seconds > 50 { + return 0 + } + return seconds +} + +// RefreshSandbox retrieves current metadata for an existing Blaxel sandbox by name. +func (b *backend) RefreshSandbox(ctx context.Context, id string) (*sandbox.SandboxInfo, error) { + instance, err := b.getInstance(ctx, id, "refresh") + if err != nil { + return nil, err + } + native, err := requireSandbox(instance, "refresh") + if err != nil { + return nil, err + } + return sandboxInfo(native) +} + +var _ sandbox.SandboxRefresher = (*backend)(nil) diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go new file mode 100644 index 00000000..0c35c782 --- /dev/null +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -0,0 +1,188 @@ +package blaxel + +import ( + "testing" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestPlanCreateMapsRequiredFields(t *testing.T) { + plan, err := planCreate(&sandbox.CreateOptions{ + Name: sandbox.Value("demo"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Labels: map[string]string{"team": "kit"}, + Environment: map[string]string{"FOO": "bar"}, + Resources: &sandbox.Resources{MemoryMib: sandbox.Value(uint64(4096))}, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(30 * time.Minute), + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(10 * time.Minute), + }, + }, + }, "us-pdx-1") + if err != nil { + t.Fatal(err) + } + if plan.params.Sandbox.Metadata.Name != "demo" { + t.Fatalf("name = %q", plan.params.Sandbox.Metadata.Name) + } + if !plan.params.Sandbox.Spec.Runtime.Image.Valid() || plan.params.Sandbox.Spec.Runtime.Image.Value != "blaxel/base-image:latest" { + t.Fatalf("image = %#v", plan.params.Sandbox.Spec.Runtime.Image) + } + if !plan.params.Sandbox.Spec.Region.Valid() || plan.params.Sandbox.Spec.Region.Value != "us-pdx-1" { + t.Fatalf("region = %#v", plan.params.Sandbox.Spec.Region) + } + if !plan.params.Sandbox.Spec.Runtime.Memory.Valid() || plan.params.Sandbox.Spec.Runtime.Memory.Value != 4295 { + t.Fatalf("memory = %#v", plan.params.Sandbox.Spec.Runtime.Memory) + } + if !plan.params.Sandbox.Spec.Runtime.Ttl.Valid() || plan.params.Sandbox.Spec.Runtime.Ttl.Value != "30m" { + t.Fatalf("ttl = %#v", plan.params.Sandbox.Spec.Runtime.Ttl) + } + if len(plan.params.Sandbox.Spec.Lifecycle.ExpirationPolicies) != 1 { + t.Fatalf("policies = %#v", plan.params.Sandbox.Spec.Lifecycle.ExpirationPolicies) + } + policy := plan.params.Sandbox.Spec.Lifecycle.ExpirationPolicies[0] + if policy.Type != blaxel.ExpirationPolicyTypeTtlIdle || !policy.Value.Valid() || policy.Value.Value != "10m" { + t.Fatalf("idle policy = %#v", policy) + } + if plan.params.CreateIfNotExist.Valid() { + t.Fatal("createIfNotExist must stay unset for native create") + } +} + +func TestBlaxelMemoryUnitConversion(t *testing.T) { + mb, ok := mibToBlaxelMB(4096) + if !ok || mb != 4295 { + t.Fatalf("4096 MiB = (%d, %t) MB, want (4295, true)", mb, ok) + } + if mib := blaxelMBToMib(4295); mib != 4096 { + t.Fatalf("4295 MB = %d MiB, want 4096", mib) + } +} + +func TestMapSandboxInfoUsesNameAsID(t *testing.T) { + info, err := mapSandboxInfo(&blaxel.Sandbox{ + Metadata: blaxel.Metadata{ + Name: "demo", + Labels: map[string]string{"team": "kit"}, + }, + Spec: blaxel.SandboxSpec{Region: "us-pdx-1", Runtime: blaxel.SandboxRuntime{Memory: 2048}}, + Status: blaxel.StatusDeployed, + State: blaxel.SandboxStateRunning, + }) + if err != nil { + t.Fatal(err) + } + if info.Id != "demo" || info.GetName() != "demo" || info.GetRegion() != "us-pdx-1" || info.GetProviderState() != "DEPLOYED" { + t.Fatalf("info = %#v", info) + } + observed, err := sandboxInfo(&blaxel.Sandbox{ + Metadata: blaxel.Metadata{ + Name: "demo", + DisplayName: "Demo", + CreatedAt: "2026-01-02T03:04:05Z", + UpdatedAt: "2026-01-02T04:05:06Z", + }, + Spec: blaxel.SandboxSpec{Runtime: blaxel.SandboxRuntime{Memory: 2048}}, + Status: blaxel.StatusDeployed, + State: blaxel.SandboxStateRunning, + LastUsedAt: "2026-01-02T05:06:07Z", + ExpiresIn: 3600, + }) + if err != nil { + t.Fatal(err) + } + if observed.State != sandbox.SandboxStateRunning || observed.Resources.GetMemoryMib() != 1953 { + t.Fatalf("observed = %#v", observed) + } + // Blaxel keeps SandboxInfo to YAML mapSandboxInfo fields; no metadata bag or createdAt. + if observed.ProviderMetadata != nil || observed.CreatedAt != nil { + t.Fatalf("unexpected metadata = %#v createdAt = %#v", observed.ProviderMetadata, observed.CreatedAt) + } + + standby, err := sandboxInfo(&blaxel.Sandbox{ + Metadata: blaxel.Metadata{Name: "demo"}, + Status: blaxel.StatusDeployed, + State: blaxel.SandboxStateStandby, + }) + if err != nil { + t.Fatal(err) + } + if standby.State != sandbox.SandboxStatePaused { + t.Fatalf("standby state = %#v", standby.State) + } + + deleted, err := sandboxInfo(&blaxel.Sandbox{ + Metadata: blaxel.Metadata{Name: "demo"}, + Status: blaxel.Status("DELETED"), + }) + if err != nil { + t.Fatal(err) + } + if deleted.State != sandbox.SandboxStateDeleted || deleted.GetTerminal() != true { + t.Fatalf("deleted = %#v", deleted) + } +} + +func TestBlaxelCreateTimeoutSeconds(t *testing.T) { + for _, tc := range []struct { + timeout *time.Duration + want int + }{ + {nil, 0}, + {durationPtr(0), 0}, + {durationPtr(30 * time.Second), 30}, + {durationPtr(time.Second), 1}, + {durationPtr(50 * time.Second), 50}, + {durationPtr(1500 * time.Millisecond), 0}, + {durationPtr(120 * time.Second), 0}, + } { + request := &sandbox.CreateOptions{} + if tc.timeout != nil { + request.Provisioning = &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(*tc.timeout), + } + } + if got := blaxelCreateTimeoutSeconds(request); got != tc.want { + t.Fatalf("timeout %#v: got %d want %d", tc.timeout, got, tc.want) + } + } +} + +func durationPtr(value time.Duration) *time.Duration { + return &value +} + +func TestFormatBlaxelDuration(t *testing.T) { + for _, tc := range []struct { + in time.Duration + minimum time.Duration + want string + err string + }{ + {30 * time.Second, time.Second, "30s", ""}, + {5 * time.Minute, 5 * time.Minute, "5m", ""}, + {2 * time.Hour, 5 * time.Minute, "2h", ""}, + {48 * time.Hour, 5 * time.Minute, "2d", ""}, + {30 * time.Second, time.Minute, "", "duration must be at least 1m"}, + {4 * time.Minute, 5 * time.Minute, "", "duration must be at least 5m"}, + {1500 * time.Millisecond, time.Second, "", "duration must be a positive whole number of seconds"}, + {0, time.Second, "", "duration must be a positive whole number of seconds"}, + } { + got, err := formatBlaxelDuration(tc.in, tc.minimum) + if tc.err == "" { + if err != nil || got != tc.want { + t.Fatalf("%v (min %v): got %q err %v", tc.in, tc.minimum, got, err) + } + continue + } + if err == nil || err.Error() != tc.err { + t.Fatalf("%v (min %v): got err %v want %q", tc.in, tc.minimum, err, tc.err) + } + } +} diff --git a/sdks/go/sandbox/README.md b/sdks/go/sandbox/README.md index 8ccdc61d..12d097dc 100644 --- a/sdks/go/sandbox/README.md +++ b/sdks/go/sandbox/README.md @@ -1,7 +1,7 @@ # Sandbox client Create and manage cloud sandboxes with one Go client. Install this module and -either the Daytona provider or the Modal provider. +one provider (Daytona, Modal, or Blaxel). Requires Go 1.26.0 or later. @@ -12,8 +12,10 @@ go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest go get github.com/sandbox-kit/kit/sdks/go/providers/daytona@latest ``` -For Modal, install `github.com/sandbox-kit/kit/sdks/go/providers/modal` instead -of the Daytona provider. +For Modal or Blaxel, install +`github.com/sandbox-kit/kit/sdks/go/providers/modal` or +`github.com/sandbox-kit/kit/sdks/go/providers/blaxel` instead of the Daytona +provider. ## Create a sandbox @@ -37,12 +39,14 @@ fmt.Println(instance.ID()) ``` `Create` with a nil request uses the provider default when that default is -valid. Daytona uses its default snapshot. Modal requires an image. +valid. Daytona uses its default snapshot. Modal requires an image. Blaxel +requires a name and an image, plus client `Region`. -`client.Close` closes your connection to Daytona or Modal. `instance.Close` -does not stop, terminate, or delete the sandbox, and you can keep using the -handle. The sandbox keeps running until you stop, terminate, archive, or -delete it. +`client.Close` closes your connection to Daytona, Modal, or Blaxel. +`instance.Close` does not stop, terminate, or delete the sandbox, and you can +keep using the handle. Reclaim a Daytona sandbox with Stop, Archive, or +Delete; a Modal sandbox with Terminate or lifetime; a Blaxel sandbox with +Delete or TTL/idle. Blaxel has no Stop, Terminate, or Archive. ## Run a command @@ -88,11 +92,12 @@ Paths inside the sandbox are absolute. The same handle also supports | ---------------------------------------------------------------------------------- | --------------- | ------------------------ | | [Daytona](https://pkg.go.dev/github.com/sandbox-kit/kit/sdks/go/providers/daytona) | `daytona.New()` | `client.Get` | | [Modal](https://pkg.go.dev/github.com/sandbox-kit/kit/sdks/go/providers/modal) | `modal.New()` | `client.Attach` | +| [Blaxel](https://pkg.go.dev/github.com/sandbox-kit/kit/sdks/go/providers/blaxel) | `blaxel.New()` | `client.Get` (by name) | -`Supports` reports whether the provider implements that operation. An -unimplemented method returns an error and does not call the provider. A -provider rejection, including one from a closed SDK, comes back as that -provider's error. +`Supports` tells you whether this handle can attempt that operation. On +Blaxel, you can `Get`, `Refresh`, `Delete`, and `Close`. Other lifecycle +methods are not available. If the provider rejects a supported call, you get +that provider's error. ## Read an error diff --git a/sdks/go/sandbox/capabilities.go b/sdks/go/sandbox/capabilities.go index 1e0d1391..86f71922 100644 --- a/sdks/go/sandbox/capabilities.go +++ b/sdks/go/sandbox/capabilities.go @@ -2,11 +2,12 @@ package sandbox import "context" -// SandboxCreator creates sandboxes. Daytona and Modal implement it. +// SandboxCreator creates sandboxes. Daytona, Modal, and Blaxel implement it. // Call Client.Create. type SandboxCreator interface { // Create creates one sandbox. - // A nil request is Daytona's default snapshot. Modal requires an image in the request. + // A nil request is Daytona's default snapshot. Modal requires an image in the + // request. Blaxel requires a name and an image. Create(context.Context, *CreateOptions) (*CreateResult, error) } @@ -18,7 +19,7 @@ type SandboxAttacher interface { } // SandboxRefresher loads current metadata for a sandbox ID. -// Daytona implements it. Call Client.Get or Sandbox.Refresh. +// Daytona and Blaxel implement it. Call Client.Get or Sandbox.Refresh. type SandboxRefresher interface { // RefreshSandbox loads current metadata for id. RefreshSandbox(context.Context, string) (*SandboxInfo, error) @@ -39,7 +40,7 @@ type SandboxStopper interface { } // SandboxPauser pauses a sandbox. -// Daytona implements it and accepts or rejects the sandbox. Modal does not. +// Daytona implements it and accepts or rejects the sandbox. Modal and Blaxel do not. type SandboxPauser interface { // PauseSandbox pauses the sandbox identified by id. PauseSandbox(context.Context, string, *PauseOptions) (*SandboxInfo, error) @@ -53,7 +54,7 @@ type SandboxTerminator interface { } // SandboxArchiver archives a sandbox. -// Daytona implements it and accepts or rejects the sandbox. Modal does not. +// Daytona implements it and accepts or rejects the sandbox. Modal and Blaxel do not. type SandboxArchiver interface { // ArchiveSandbox archives the sandbox identified by id. ArchiveSandbox(context.Context, string, *ArchiveOptions) (*SandboxInfo, error) @@ -61,7 +62,7 @@ type SandboxArchiver interface { // SandboxFiles performs file operations in a sandbox: read, write, copy to or // from the caller, make directory, list directory, stat, and remove. -// Daytona and Modal implement it. Use Sandbox.Filesystem. +// Daytona, Modal, and Blaxel implement it. Use Sandbox.Filesystem. type SandboxFiles interface { // ReadFile returns the bytes of one file. ReadFile(context.Context, string, string, *FileReadOptions) ([]byte, error) @@ -71,7 +72,8 @@ type SandboxFiles interface { CopyFromLocal(context.Context, string, string, string, *FileCopyOptions) error // CopyToLocal copies a sandbox file onto the caller's disk. CopyToLocal(context.Context, string, string, string, *FileCopyOptions) error - // MakeDirectory creates a directory. A nil Parents flag is true. Daytona rejects false. + // MakeDirectory creates a directory. A nil Parents flag is true. Daytona and + // Blaxel reject false. MakeDirectory(context.Context, string, string, *MakeDirectoryOptions) error // ListDirectory lists the entries in one directory. ListDirectory(context.Context, string, string, *ListDirectoryOptions) ([]*FileInfo, error) @@ -82,7 +84,7 @@ type SandboxFiles interface { } // SandboxProcess runs a command in a sandbox and waits for it to finish. -// Daytona and Modal implement it. Use Sandbox.Process. +// Daytona, Modal, and Blaxel implement it. Use Sandbox.Process. type SandboxProcess interface { // RunCommand runs one command and returns its exit code and output. // A non-zero exit code is not an error. @@ -90,7 +92,7 @@ type SandboxProcess interface { } // SandboxDeleter deletes a sandbox. -// Daytona implements it. Call Sandbox.Delete. Close does not delete the cloud sandbox. +// Daytona and Blaxel implement it. Call Sandbox.Delete. Close does not delete the cloud sandbox. type SandboxDeleter interface { // DeleteSandbox deletes the sandbox identified by id. DeleteSandbox(context.Context, string, *DeleteOptions) (*SandboxInfo, error) diff --git a/sdks/go/sandbox/client.go b/sdks/go/sandbox/client.go index 51a34a89..09dcdb23 100644 --- a/sdks/go/sandbox/client.go +++ b/sdks/go/sandbox/client.go @@ -18,7 +18,7 @@ import ( // Close releases that connection. It does not stop or delete a cloud sandbox. // CloseSandbox runs when the caller closes a sandbox handle. It does not stop or delete the cloud sandbox. type Backend interface { - // Name returns the provider name, "daytona" or "modal". + // Name returns the provider name, "daytona", "modal", or "blaxel". Name() string // Close releases the provider connection. It does not stop or delete a cloud sandbox. Close(context.Context) error @@ -28,9 +28,9 @@ type Backend interface { } // Provider is the value passed to NewClient. -// Use daytona.New or modal.New. +// Use daytona.New, modal.New, or blaxel.New. type Provider interface { - // Name returns the provider name, "daytona" or "modal". + // Name returns the provider name, "daytona", "modal", or "blaxel". Name() string // NewClient opens the provider from config. // Call the package NewClient. That checks config and owns cleanup. @@ -65,7 +65,7 @@ func missingClientBinding(binding any) bool { // NewClient checks config and opens a client for the selected provider. // Closing the client releases the provider SDK. It does not stop or delete a sandbox. // -// Pass daytona.New or modal.New as providerFactory. +// Pass daytona.New, modal.New, or blaxel.New as providerFactory. // // Example: // @@ -139,10 +139,10 @@ func secureEndpoint(value string) bool { return false } ip, err := netip.ParseAddr(u.Hostname()) - return u.Hostname() == "localhost" || (err == nil && ip.IsLoopback()) + return strings.EqualFold(u.Hostname(), "localhost") || (err == nil && ip.IsLoopback()) } -// ProviderName returns "daytona" or "modal". +// ProviderName returns "daytona", "modal", or "blaxel". // A nil client returns an empty string. func (c *Client) ProviderName() string { if c == nil || c.backend == nil { @@ -173,7 +173,8 @@ func (c *Client) Close(ctx context.Context) (result error) { } // Create creates one sandbox and returns its handle. -// A nil request uses the provider default. Modal requires an image in CreateOptions.Source. +// A nil request uses the provider default. Modal requires an image in +// CreateOptions.Source. Blaxel requires a name and an image. // A nil client or a nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. // Close the client when finished. That does not delete the sandbox. // @@ -223,7 +224,7 @@ func (c *Client) Create(ctx context.Context, request *CreateOptions) (instance * if err != nil { return nil, err } - instance, err = sandboxFromResponse(c.backend, response, "create") + instance, err = sandboxFromResponse(c.backend, response, "create", "") if err != nil { return nil, err } diff --git a/sdks/go/sandbox/client_contracts.go b/sdks/go/sandbox/client_contracts.go index 2d846086..88449422 100644 --- a/sdks/go/sandbox/client_contracts.go +++ b/sdks/go/sandbox/client_contracts.go @@ -11,7 +11,8 @@ import ( type ( // Config is the configuration passed to NewClient. // Set Auth for the provider account. Modal also requires Scope.AppName. - // An omitted Endpoint uses the provider default. Modal rejects Endpoint. + // Blaxel create also requires Region. An omitted Endpoint uses the provider + // default. Modal and Blaxel reject Endpoint. Config = sandboxv1.Config // AuthConfig is the credential set on Config.Auth. @@ -19,7 +20,7 @@ type ( AuthConfig = sandboxv1.AuthConfig // APIKey is the API-key form of AuthConfig.Credentials. - // Daytona accepts an API key. + // Daytona and Blaxel accept an API key. APIKey = sandboxv1.AuthConfig_ApiKey // TokenPair is the id-and-secret form of AuthConfig.Credentials. @@ -47,6 +48,7 @@ type ( // Scope selects the account boundary for the client. // Modal requires AppName. Environment is often "main". // Daytona accepts OrganizationId. + // Workspace is the Blaxel workspace name. Scope = sandboxv1.Scope ) diff --git a/sdks/go/sandbox/client_lifecycle.go b/sdks/go/sandbox/client_lifecycle.go index aa3be366..be609c05 100644 --- a/sdks/go/sandbox/client_lifecycle.go +++ b/sdks/go/sandbox/client_lifecycle.go @@ -33,7 +33,7 @@ func (c *Client) resolveSandbox(ctx context.Context, id, operation string, invok if err != nil { return nil, sdk.WithErrorContext(err, provider, operation) } - instance, err := sandboxFromResponse(c.backend, &CreateResult{Sandbox: info}, operation) + instance, err := sandboxFromResponse(c.backend, &CreateResult{Sandbox: info}, operation, id) if err != nil { return nil, err } diff --git a/sdks/go/sandbox/config_paths.go b/sdks/go/sandbox/config_paths.go index bfaf4e04..b3b13b07 100644 --- a/sdks/go/sandbox/config_paths.go +++ b/sdks/go/sandbox/config_paths.go @@ -68,6 +68,9 @@ const ConfigFieldScopeOrganizationId = "scope.organization_id" // ConfigFieldScopeProjectId is the canonical path "scope.project_id". const ConfigFieldScopeProjectId = "scope.project_id" +// ConfigFieldScopeWorkspace is the canonical path "scope.workspace". +const ConfigFieldScopeWorkspace = "scope.workspace" + // ConfigFieldTimeout is the canonical path "timeout". const ConfigFieldTimeout = "timeout" diff --git a/sdks/go/sandbox/create_validation.go b/sdks/go/sandbox/create_validation.go index eb889bab..82f1b02b 100644 --- a/sdks/go/sandbox/create_validation.go +++ b/sdks/go/sandbox/create_validation.go @@ -27,7 +27,8 @@ var stableRuleIDs = map[string]string{ // ValidateCreateOptions checks a create request before a provider is contacted. // A nil request passes this check. Daytona treats that as its default snapshot. -// Modal still requires an image. Quotas and capacity remain the provider's decision. +// Modal still requires an image. Blaxel still requires a name and an image. +// Quotas and capacity remain the provider's decision. func ValidateCreateOptions(request *CreateOptions) error { if request == nil { return nil diff --git a/sdks/go/sandbox/doc.go b/sdks/go/sandbox/doc.go index 73707752..364169e2 100644 --- a/sdks/go/sandbox/doc.go +++ b/sdks/go/sandbox/doc.go @@ -1,7 +1,7 @@ // Package sandbox creates and manages cloud sandboxes through one client. // -// Install this module and one provider. Pass daytona.New or modal.New to -// [NewClient]. [Client.Create] returns a [Sandbox] handle. +// Install this module and one provider. Pass daytona.New, modal.New, or +// blaxel.New to [NewClient]. [Client.Create] returns a [Sandbox] handle. // // [Client.Close] and [Sandbox.Close] release local resources. They leave the // cloud sandbox running. Stop, terminate, archive, or delete it with a @@ -13,5 +13,6 @@ // Compare it with [CreateFieldResourcesCpuCores], [ConfigFieldEndpoint], // [FileFieldPath], and the other field constants. // -// [Client.Create] with a nil request uses Daytona's default snapshot. Modal requires an image. +// [Client.Create] with a nil request uses Daytona's default snapshot. Modal +// requires an image. Blaxel requires a name and an image. package sandbox diff --git a/sdks/go/sandbox/filesystem.go b/sdks/go/sandbox/filesystem.go index 367c6870..357bc672 100644 --- a/sdks/go/sandbox/filesystem.go +++ b/sdks/go/sandbox/filesystem.go @@ -45,12 +45,16 @@ func (f *Filesystem) ReadFile(ctx context.Context, path string, options *FileRea // WriteFile replaces the file at path with data and creates missing parent directories. // path is absolute. A nil context returns ErrorKindInvalidArgument. func (f *Filesystem) WriteFile(ctx context.Context, path string, data []byte, options *FileWriteOptions) error { + ownedData := append([]byte(nil), data...) + if data != nil && ownedData == nil { + ownedData = []byte{} + } return f.call(ctx, "write_file", options.GetTimeout(), path, nil, func(operationContext context.Context, id string) error { files, err := f.files("write_file") if err != nil { return err } - return files.WriteFile(operationContext, id, path, data, cloneMessage(options)) + return files.WriteFile(operationContext, id, path, ownedData, cloneMessage(options)) }) } @@ -81,7 +85,7 @@ func (f *Filesystem) CopyToLocal(ctx context.Context, remotePath, localPath stri // MakeDirectory creates the directory at path. // An omitted Parents flag is true: missing parents are created. Set false with Value. -// Daytona rejects false. +// Daytona and Blaxel reject false. // path is absolute. A nil context returns ErrorKindInvalidArgument. func (f *Filesystem) MakeDirectory(ctx context.Context, path string, options *MakeDirectoryOptions) error { return f.call(ctx, "make_directory", options.GetTimeout(), path, nil, func(operationContext context.Context, id string) error { diff --git a/sdks/go/sandbox/lifecycle.go b/sdks/go/sandbox/lifecycle.go index 87b2504c..7f9ac4db 100644 --- a/sdks/go/sandbox/lifecycle.go +++ b/sdks/go/sandbox/lifecycle.go @@ -48,7 +48,7 @@ func (s *Sandbox) runInfoOp(ctx context.Context, operation string, timeout *time } // Refresh loads the sandbox's current metadata from the provider and stores it on this handle. -// Daytona supports Refresh. Modal returns ErrorKindUnsupported. +// Daytona and Blaxel support Refresh. Modal returns ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Refresh(ctx context.Context) error { return s.runInfoOp(ctx, "refresh", nil, func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -62,7 +62,7 @@ func (s *Sandbox) Refresh(ctx context.Context) error { // Start starts a stopped sandbox and stores the updated metadata on this handle. // A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Daytona supports Start. Modal returns ErrorKindUnsupported. +// Daytona supports Start. Modal and Blaxel return ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Start(ctx context.Context, options *StartOptions) error { return s.runInfoOp(ctx, "start", sdk.OptionalDuration(options.GetTimeout()), func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -77,7 +77,7 @@ func (s *Sandbox) Start(ctx context.Context, options *StartOptions) error { // Stop stops a sandbox and stores the updated metadata on this handle. // Set StopOptions.Force with Value(true) to request an immediate stop. // A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Daytona supports Stop. Modal returns ErrorKindUnsupported. +// Daytona supports Stop. Modal and Blaxel return ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Stop(ctx context.Context, options *StopOptions) error { return s.runInfoOp(ctx, "stop", sdk.OptionalDuration(options.GetTimeout()), func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -91,7 +91,7 @@ func (s *Sandbox) Stop(ctx context.Context, options *StopOptions) error { // Pause pauses a sandbox and stores the updated metadata on this handle. // A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Daytona exposes Pause and accepts or rejects the sandbox. Modal returns ErrorKindUnsupported. +// Daytona exposes Pause and accepts or rejects the sandbox. Modal and Blaxel return ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Pause(ctx context.Context, options *PauseOptions) error { return s.runInfoOp(ctx, "pause", sdk.OptionalDuration(options.GetTimeout()), func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -106,7 +106,7 @@ func (s *Sandbox) Pause(ctx context.Context, options *PauseOptions) error { // Terminate ends a sandbox. The returned ExitResult includes the exit code when Wait is set. // Set TerminateOptions.Wait with Value(true) to wait until the sandbox exits. // A nil timeout uses the client timeout as this call's deadline. Modal waits on that deadline and does not take a separate terminate duration. -// Modal supports Terminate. Daytona returns ErrorKindUnsupported. Use Delete or Stop for Daytona. +// Modal supports Terminate. Daytona and Blaxel return ErrorKindUnsupported. Use Delete or Stop for Daytona; Delete for Blaxel. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Terminate(ctx context.Context, options *TerminateOptions) (*ExitResult, error) { operationContext, cancel, id, err := s.operationContext(ctx, "terminate", sdk.OptionalDuration(options.GetTimeout())) @@ -127,7 +127,7 @@ func (s *Sandbox) Terminate(ctx context.Context, options *TerminateOptions) (*Ex // Archive archives a sandbox and stores the updated metadata on this handle. // A nil timeout uses the client timeout as this call's deadline. Daytona archive does not take a separate duration. -// Daytona exposes Archive and accepts or rejects the sandbox. Modal returns ErrorKindUnsupported. +// Daytona exposes Archive and accepts or rejects the sandbox. Modal and Blaxel return ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Archive(ctx context.Context, options *ArchiveOptions) error { return s.runInfoOp(ctx, "archive", sdk.OptionalDuration(options.GetTimeout()), func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -140,10 +140,13 @@ func (s *Sandbox) Archive(ctx context.Context, options *ArchiveOptions) error { } // Delete deletes the cloud sandbox and stores the metadata the provider returns. -// Set DeleteOptions.Wait with Value(true) to wait until deletion finishes. Value(false) returns after the provider accepts the request. -// A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Close leaves the cloud sandbox in place. Daytona supports Delete. Modal returns ErrorKindUnsupported. Use Terminate for Modal. -// A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. +// Daytona and Blaxel honor DeleteOptions.Wait: Value(true) blocks until deletion +// finishes. Without Wait, Blaxel returns Deleted with Terminal true as soon as +// delete is accepted. A nil timeout uses the client timeout as this call's +// deadline. The provider receives a timeout only when one is set. Close leaves +// the cloud sandbox in place. Daytona and Blaxel support Delete. Modal returns +// ErrorKindUnsupported; use Terminate for Modal. A nil context returns +// ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Delete(ctx context.Context, options *DeleteOptions) error { operationContext, cancel, id, err := s.operationContext(ctx, "delete", sdk.OptionalDuration(options.GetTimeout())) if err != nil { @@ -175,7 +178,7 @@ func callOptions[T interface { // Get looks up a sandbox by ID and returns a handle the provider has verified. // An empty ID returns ErrorKindInvalidArgument with Field InfoFieldId. -// Daytona supports Get. Modal returns ErrorKindUnsupported. Use Attach for Modal. +// Daytona and Blaxel support Get. Modal returns ErrorKindUnsupported. Use Attach for Modal. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) { return c.resolveSandbox(ctx, id, "get", func(operationContext context.Context) (*SandboxInfo, error) { @@ -189,7 +192,7 @@ func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) { // Attach returns a handle for an existing sandbox ID without asking the provider to look it up. // An empty ID returns ErrorKindInvalidArgument with Field InfoFieldId. -// Modal supports Attach. Daytona returns ErrorKindUnsupported. Use Get for Daytona. +// Modal supports Attach. Daytona and Blaxel return ErrorKindUnsupported. Use Get for Daytona or Blaxel. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (c *Client) Attach(ctx context.Context, id string) (*Sandbox, error) { return c.resolveSandbox(ctx, id, "attach", func(operationContext context.Context) (*SandboxInfo, error) { diff --git a/sdks/go/sandbox/operation.go b/sdks/go/sandbox/operation.go index 1f5e3c8b..0de8b551 100644 --- a/sdks/go/sandbox/operation.go +++ b/sdks/go/sandbox/operation.go @@ -74,12 +74,16 @@ func (s *Sandbox) replaceInfo(operation string, info *SandboxInfo) error { return NewError(&ErrorInfo{Kind: ErrorKindInvalidResponse, Provider: s.ProviderName(), Operation: operation, Field: InfoFieldId, Message: "sandbox-kit: provider returned no sandbox identity"}, nil) } provider := s.ProviderName() + if info.Id != s.currentID() { + return NewError(&ErrorInfo{Kind: ErrorKindInvalidResponse, Provider: provider, Operation: operation, Field: InfoFieldId, Message: "sandbox-kit: provider returned a different sandbox identity"}, nil) + } if info.Provider != "" && info.Provider != provider { return NewError(&ErrorInfo{Kind: ErrorKindInvalidResponse, Provider: provider, Operation: operation, Field: InfoFieldProvider, Message: "sandbox-kit: provider returned inconsistent sandbox metadata"}, nil) } - info.Provider = provider + stored := cloneSandboxInfo(info) + stored.Provider = provider s.mu.Lock() - s.info = cloneSandboxInfo(info) + s.info = stored s.mu.Unlock() return nil } diff --git a/sdks/go/sandbox/sandbox.go b/sdks/go/sandbox/sandbox.go index 776440b9..51dcf3ac 100644 --- a/sdks/go/sandbox/sandbox.go +++ b/sdks/go/sandbox/sandbox.go @@ -25,9 +25,9 @@ type Sandbox struct { info *SandboxInfo } -// sandboxFromResponse checks that creation returned an identifier -// and consistent provider metadata. -func sandboxFromResponse(backend Backend, response *CreateResult, operation string) (*Sandbox, error) { +// sandboxFromResponse checks that a provider returned the expected identifier +// and consistent provider metadata. An empty expectedID accepts creation IDs. +func sandboxFromResponse(backend Backend, response *CreateResult, operation, expectedID string) (*Sandbox, error) { if response == nil || response.Sandbox == nil || response.Sandbox.Id == "" { return nil, NewError(&ErrorInfo{ Kind: ErrorKindInvalidResponse, @@ -37,6 +37,15 @@ func sandboxFromResponse(backend Backend, response *CreateResult, operation stri Message: "sandbox-kit: provider returned no sandbox identity", }, nil) } + if expectedID != "" && response.Sandbox.Id != expectedID { + return nil, NewError(&ErrorInfo{ + Kind: ErrorKindInvalidResponse, + Provider: backend.Name(), + Operation: operation, + Field: InfoFieldId, + Message: "sandbox-kit: provider returned a different sandbox identity", + }, nil) + } info := cloneSandboxInfo(response.Sandbox) name := backend.Name() if info.Provider != "" && info.Provider != name { @@ -58,7 +67,7 @@ func (s *Sandbox) ID() string { return s.currentID() } -// ProviderName returns "daytona" or "modal". +// ProviderName returns "daytona", "modal", or "blaxel". // A nil handle returns an empty string. func (s *Sandbox) ProviderName() string { if s == nil { diff --git a/sdks/go/sandbox/sandbox_data_contracts.go b/sdks/go/sandbox/sandbox_data_contracts.go index e5082fc9..aa62a9e8 100644 --- a/sdks/go/sandbox/sandbox_data_contracts.go +++ b/sdks/go/sandbox/sandbox_data_contracts.go @@ -136,7 +136,7 @@ type ( FileCopyOptions = sandboxv1.FileCopyOptions // MakeDirectoryOptions sets the timeout and Parents flag for Filesystem.MakeDirectory. - // An omitted Parents flag is true. Set false with Value. Daytona rejects false. + // An omitted Parents flag is true. Set false with Value. Daytona and Blaxel reject false. MakeDirectoryOptions = sandboxv1.MakeDirectoryOptions // ListDirectoryOptions sets the timeout for Filesystem.ListDirectory. @@ -152,9 +152,10 @@ type ( // ExecOptions configures Process.Run. Set exactly one of Argv or Shell. // An omitted Timeout uses Config.Timeout as the call deadline. Explicit // zero adds no extra deadline for this call. Both omit and zero also mean - // no provider command limit (Daytona uses 0 so its 10s native default does - // not apply; Modal adds no command limit). Command timeouts must be whole - // seconds. + // no provider process auto-kill (Daytona uses 0 so its 10s native default + // does not apply; Modal adds no command limit; Blaxel adds no auto-kill + // and waits once via Process.Wait). Command + // timeouts must be whole seconds. ExecOptions = sandboxv1.ExecOptions // ExecResult is the outcome of Process.Run. A non-zero ExitCode is not an error. diff --git a/sdks/go/sandbox/tests/client_test.go b/sdks/go/sandbox/tests/client_test.go index a81163b1..4f3d6d5c 100644 --- a/sdks/go/sandbox/tests/client_test.go +++ b/sdks/go/sandbox/tests/client_test.go @@ -2,8 +2,10 @@ package sandbox_test import ( "context" + "encoding/json" "errors" "reflect" + "strings" "testing" "time" @@ -133,6 +135,17 @@ func TestConfigContractsAreProtobufMessages(t *testing.T) { } } +func TestConfigJSONRedactsCredentials(t *testing.T) { + config := &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{TokenPair: &sandbox.TokenPairCredentials{Id: "sensitive-id", Secret: "sensitive-secret"}}}} + encoded, err := json.Marshal(config) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(encoded), "sensitive-id") || strings.Contains(string(encoded), "sensitive-secret") { + t.Fatalf("config JSON contains credentials: %s", encoded) + } +} + func TestProviderRequiresInitializationContract(t *testing.T) { // A name-only implementation no longer satisfies the initialization contract. typeNameOnly := reflect.TypeOf(nameOnly{}) diff --git a/sdks/go/sandbox/tests/filesystem_test.go b/sdks/go/sandbox/tests/filesystem_test.go index 9d7004d3..fdb146ec 100644 --- a/sdks/go/sandbox/tests/filesystem_test.go +++ b/sdks/go/sandbox/tests/filesystem_test.go @@ -9,7 +9,8 @@ import ( ) type fileBackend struct { - read func(context.Context, string, string, *sandbox.FileReadOptions) ([]byte, error) + read func(context.Context, string, string, *sandbox.FileReadOptions) ([]byte, error) + write func([]byte) } func (*fileBackend) Name() string { return "files" } @@ -23,7 +24,10 @@ func (*fileBackend) Create(context.Context, *sandbox.CreateOptions) (*sandbox.Cr func (b *fileBackend) ReadFile(ctx context.Context, id, path string, options *sandbox.FileReadOptions) ([]byte, error) { return b.read(ctx, id, path, options) } -func (*fileBackend) WriteFile(context.Context, string, string, []byte, *sandbox.FileWriteOptions) error { +func (b *fileBackend) WriteFile(_ context.Context, _, _ string, data []byte, _ *sandbox.FileWriteOptions) error { + if b.write != nil { + b.write(data) + } return nil } func (*fileBackend) CopyFromLocal(context.Context, string, string, string, *sandbox.FileCopyOptions) error { @@ -108,6 +112,25 @@ func TestFilesystemReadsAbsolutePath(t *testing.T) { } } +func TestFilesystemWriteOwnsData(t *testing.T) { + backend := &fileBackend{write: func(data []byte) { data[0] = 'X' }} + client, err := sandbox.NewClient(&fileProvider{backend: backend}, nil) + if err != nil { + t.Fatal(err) + } + instance, err := client.Create(context.Background(), nil) + if err != nil { + t.Fatal(err) + } + data := []byte("hello") + if err := instance.Filesystem().WriteFile(context.Background(), "/note", data, nil); err != nil { + t.Fatal(err) + } + if string(data) != "hello" { + t.Fatalf("caller data changed: %q", data) + } +} + func TestFilesystemCopyRequiresLocalPath(t *testing.T) { client, err := sandbox.NewClient(&fileProvider{backend: &fileBackend{}}, &sandbox.Config{}) if err != nil { diff --git a/sdks/go/sandbox/tests/lifecycle_test.go b/sdks/go/sandbox/tests/lifecycle_test.go index e946b6dd..fe580499 100644 --- a/sdks/go/sandbox/tests/lifecycle_test.go +++ b/sdks/go/sandbox/tests/lifecycle_test.go @@ -46,6 +46,15 @@ func (p *lifecycleProvider) NewClient(*sandbox.Config) (sandbox.Backend, error) return p.backend, nil } +type attachLifecycleBackend struct { + *lifecycleBackend + attach func(context.Context, string) (*sandbox.SandboxInfo, error) +} + +func (b *attachLifecycleBackend) AttachSandbox(ctx context.Context, id string) (*sandbox.SandboxInfo, error) { + return b.attach(ctx, id) +} + func TestLifecycleDispatchAndUnsupportedOperation(t *testing.T) { called := false backend := &lifecycleBackend{start: func(ctx context.Context, id string, options *sandbox.StartOptions) (*sandbox.SandboxInfo, error) { @@ -170,6 +179,95 @@ func TestGetAndRefreshOwnMetadata(t *testing.T) { } } +func TestGetAndAttachRejectInconsistentProviderResponses(t *testing.T) { + tests := []struct { + name string + operation string + field string + backend sandbox.Backend + invoke func(*sandbox.Client) error + }{ + { + name: "get returns a different identity", + operation: "get", + field: sandbox.InfoFieldId, + backend: &lifecycleBackend{get: func(context.Context, string) (*sandbox.SandboxInfo, error) { + return &sandbox.SandboxInfo{Id: "different"}, nil + }}, + invoke: func(client *sandbox.Client) error { + _, err := client.Get(context.Background(), "requested") + return err + }, + }, + { + name: "attach returns a different provider", + operation: "attach", + field: sandbox.InfoFieldProvider, + backend: &attachLifecycleBackend{ + lifecycleBackend: &lifecycleBackend{}, + attach: func(_ context.Context, id string) (*sandbox.SandboxInfo, error) { + return &sandbox.SandboxInfo{Id: id, Provider: "different"}, nil + }, + }, + invoke: func(client *sandbox.Client) error { + _, err := client.Attach(context.Background(), "requested") + return err + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + client, err := sandbox.NewClient(&lifecycleProvider{backend: test.backend}, &sandbox.Config{}) + if err != nil { + t.Fatal(err) + } + err = test.invoke(client) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidResponse || detail.Operation != test.operation || detail.Field != test.field { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + +func TestRefreshRejectsInconsistentMetadataWithoutUpdatingTheHandle(t *testing.T) { + tests := []struct { + name string + info *sandbox.SandboxInfo + field string + }{ + {name: "different identity", info: &sandbox.SandboxInfo{Id: "different"}, field: sandbox.InfoFieldId}, + {name: "different provider", info: &sandbox.SandboxInfo{Id: "created", Provider: "different"}, field: sandbox.InfoFieldProvider}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + backend := &lifecycleBackend{ + get: func(context.Context, string) (*sandbox.SandboxInfo, error) { + return test.info, nil + }, + } + client, err := sandbox.NewClient(&lifecycleProvider{backend: backend}, &sandbox.Config{}) + if err != nil { + t.Fatal(err) + } + instance, err := client.Create(context.Background(), nil) + if err != nil { + t.Fatal(err) + } + err = instance.Refresh(context.Background()) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidResponse || detail.Operation != "refresh" || detail.Field != test.field { + t.Fatalf("unexpected error: %v", err) + } + if instance.ID() != "created" || instance.ProviderName() != "lifecycle" { + t.Fatalf("refresh changed handle metadata: %#v", instance.Info()) + } + }) + } +} + func TestLifecycleInheritsClientTimeout(t *testing.T) { backend := &lifecycleBackend{start: func(ctx context.Context, _ string, _ *sandbox.StartOptions) (*sandbox.SandboxInfo, error) { <-ctx.Done() diff --git a/sdks/go/sandbox/tests/validation_limits_test.go b/sdks/go/sandbox/tests/validation_limits_test.go index e8185f33..aff15d5e 100644 --- a/sdks/go/sandbox/tests/validation_limits_test.go +++ b/sdks/go/sandbox/tests/validation_limits_test.go @@ -13,8 +13,9 @@ func TestSecureEndpoints(t *testing.T) { endpoint string valid bool }{ - {"https://api.example.test/api", true}, {"http://127.0.0.1:1234", true}, {"http://[::1]:1234", true}, {"http://localhost:1234", true}, + {"https://api.example.test/api", true}, {"http://127.0.0.1:1234", true}, {"http://[::1]:1234", true}, {"http://[0:0:0:0:0:0:0:1]:1234", true}, {"http://localhost:1234", true}, {"http://LOCALHOST:1234", true}, {"http://api.example.test", false}, {"http://127.0.0.1.evil.test", false}, {"http://0.0.0.0:1234", false}, {"https://user:secret@api.example.test", false}, {"https://api.example.test/#fragment", false}, + {"http://@localhost", false}, {"http://127.1", false}, } { t.Run(tc.endpoint, func(t *testing.T) { err := sandbox.ValidateConfig(&sandbox.Config{Endpoint: sandbox.Value(tc.endpoint)}) @@ -46,4 +47,3 @@ func TestImageReferenceValidationBeforeBackend(t *testing.T) { }) } } - diff --git a/sdks/typescript/README.md b/sdks/typescript/README.md index 97b4777b..0815dbbf 100644 --- a/sdks/typescript/README.md +++ b/sdks/typescript/README.md @@ -8,6 +8,7 @@ Workspace for the published TypeScript packages: | `@sandbox-kit/sandbox` | Core client | | `@sandbox-kit/daytona` | Daytona provider | | `@sandbox-kit/modal` | Modal provider | +| `@sandbox-kit/blaxel` | Blaxel provider | ## Local development @@ -42,6 +43,7 @@ cd contracts && npm publish cd ../sandbox && npm publish cd ../providers/daytona && npm publish cd ../providers/modal && npm publish +cd ../providers/blaxel && npm publish ``` Before a real publish, dry-run each package: @@ -51,6 +53,7 @@ cd sdks/typescript/contracts && npm pack --dry-run cd ../sandbox && npm pack --dry-run cd ../providers/daytona && npm pack --dry-run cd ../providers/modal && npm pack --dry-run +cd ../providers/blaxel && npm pack --dry-run ``` `prepublishOnly` deletes `dist/` then rebuilds so stale outputs are not packed. diff --git a/sdks/typescript/contracts/README.md b/sdks/typescript/contracts/README.md index 7b57de79..a64bb23c 100644 --- a/sdks/typescript/contracts/README.md +++ b/sdks/typescript/contracts/README.md @@ -9,6 +9,8 @@ Install the public client and a provider instead: npm install @sandbox-kit/sandbox @sandbox-kit/daytona # or npm install @sandbox-kit/sandbox @sandbox-kit/modal +# or +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel ``` See [`@sandbox-kit/sandbox`](https://www.npmjs.com/package/@sandbox-kit/sandbox) diff --git a/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts b/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts index bf93979d..a0b1739e 100644 --- a/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts +++ b/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts @@ -13,7 +13,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file kit/sandbox/v1/client.proto. */ export const file_kit_sandbox_v1_client: GenFile = /*@__PURE__*/ - fileDesc("ChtraXQvc2FuZGJveC92MS9jbGllbnQucHJvdG8SDmtpdC5zYW5kYm94LnYxIssCCgZDb25maWcSKAoEYXV0aBgBIAEoCzIaLmtpdC5zYW5kYm94LnYxLkF1dGhDb25maWcSTQoIZW5kcG9pbnQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gAiAEBEksKBnJlZ2lvbhgDIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAGIAQESLQoFc2NvcGUYBCABKAsyFS5raXQuc2FuZGJveC52MS5TY29wZVIHY29udGV4dBI0Cgd0aW1lb3V0GAUgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uQgi6SAWqAQIyAEILCglfZW5kcG9pbnRCCQoHX3JlZ2lvbiLCAwoKQXV0aENvbmZpZxI0CgdhcGlfa2V5GAEgASgLMiEua2l0LnNhbmRib3gudjEuQVBJS2V5Q3JlZGVudGlhbHNIABI6Cgp0b2tlbl9wYWlyGAIgASgLMiQua2l0LnNhbmRib3gudjEuVG9rZW5QYWlyQ3JlZGVudGlhbHNIABI+CgxiZWFyZXJfdG9rZW4YAyABKAsyJi5raXQuc2FuZGJveC52MS5CZWFyZXJUb2tlbkNyZWRlbnRpYWxzSAASOQoNb2F1dGhfcmVmcmVzaBgEIAEoCzIgLmtpdC5zYW5kYm94LnYxLk9BdXRoQ3JlZGVudGlhbHNIADq3AbpIswEasAEKGWF1dGhfY3JlZGVudGlhbHNfcmVxdWlyZWQSM2F1dGggY3JlZGVudGlhbHMgYXJlIHJlcXVpcmVkIHdoZW4gYXV0aCBpcyBzdXBwbGllZBpeaGFzKHRoaXMuYXBpX2tleSkgfHwgaGFzKHRoaXMudG9rZW5fcGFpcikgfHwgaGFzKHRoaXMuYmVhcmVyX3Rva2VuKSB8fCBoYXModGhpcy5vYXV0aF9yZWZyZXNoKUINCgtjcmVkZW50aWFscyJYChFBUElLZXlDcmVkZW50aWFscxJDCgNrZXkYASABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJyKiAQoUVG9rZW5QYWlyQ3JlZGVudGlhbHMSQgoCaWQYASABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJxJGCgZzZWNyZXQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJyJfChZCZWFyZXJUb2tlbkNyZWRlbnRpYWxzEkUKBXRva2VuGAEgASgJQja6SDO6ATAKCG5vbmJsYW5rEhFtdXN0IG5vdCBiZSBibGFuaxoRdGhpcy50cmltKCkgIT0gJyci3gMKEE9BdXRoQ3JlZGVudGlhbHMSTQoNcmVmcmVzaF90b2tlbhgBIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnEkkKCWNsaWVudF9pZBgCIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnElIKDWNsaWVudF9zZWNyZXQYAyABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gAiAEBEkwKB2p3dF9rZXkYBCABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gBiAEBOnC6SG0aawoLZXhhY3RseV9vbmUSLnJlcXVpcmVzIGV4YWN0bHkgb25lIG9mIGNsaWVudF9zZWNyZXQsIGp3dF9rZXkaLGhhcyh0aGlzLmNsaWVudF9zZWNyZXQpICE9IGhhcyh0aGlzLmp3dF9rZXkpQhAKDl9jbGllbnRfc2VjcmV0QgoKCF9qd3Rfa2V5Io8DCgVTY29wZRJNCghhcHBfbmFtZRgBIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSACIAQESUAoLZW52aXJvbm1lbnQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gBiAEBElQKD29yZ2FuaXphdGlvbl9pZBgDIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAKIAQESTwoKcHJvamVjdF9pZBgEIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAOIAQFCCwoJX2FwcF9uYW1lQg4KDF9lbnZpcm9ubWVudEISChBfb3JnYW5pemF0aW9uX2lkQg0KC19wcm9qZWN0X2lkQlBaTmdpdGh1Yi5jb20vc2FuZGJveC1raXQva2l0L3Nka3MvZ28vaW50ZXJuYWwvY29udHJhY3RzL2tpdC9zYW5kYm94L3YxO3NhbmRib3h2MWIGcHJvdG8z", [file_buf_validate_validate, file_google_protobuf_duration]); + fileDesc("ChtraXQvc2FuZGJveC92MS9jbGllbnQucHJvdG8SDmtpdC5zYW5kYm94LnYxIssCCgZDb25maWcSKAoEYXV0aBgBIAEoCzIaLmtpdC5zYW5kYm94LnYxLkF1dGhDb25maWcSTQoIZW5kcG9pbnQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gAiAEBEksKBnJlZ2lvbhgDIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAGIAQESLQoFc2NvcGUYBCABKAsyFS5raXQuc2FuZGJveC52MS5TY29wZVIHY29udGV4dBI0Cgd0aW1lb3V0GAUgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uQgi6SAWqAQIyAEILCglfZW5kcG9pbnRCCQoHX3JlZ2lvbiLCAwoKQXV0aENvbmZpZxI0CgdhcGlfa2V5GAEgASgLMiEua2l0LnNhbmRib3gudjEuQVBJS2V5Q3JlZGVudGlhbHNIABI6Cgp0b2tlbl9wYWlyGAIgASgLMiQua2l0LnNhbmRib3gudjEuVG9rZW5QYWlyQ3JlZGVudGlhbHNIABI+CgxiZWFyZXJfdG9rZW4YAyABKAsyJi5raXQuc2FuZGJveC52MS5CZWFyZXJUb2tlbkNyZWRlbnRpYWxzSAASOQoNb2F1dGhfcmVmcmVzaBgEIAEoCzIgLmtpdC5zYW5kYm94LnYxLk9BdXRoQ3JlZGVudGlhbHNIADq3AbpIswEasAEKGWF1dGhfY3JlZGVudGlhbHNfcmVxdWlyZWQSM2F1dGggY3JlZGVudGlhbHMgYXJlIHJlcXVpcmVkIHdoZW4gYXV0aCBpcyBzdXBwbGllZBpeaGFzKHRoaXMuYXBpX2tleSkgfHwgaGFzKHRoaXMudG9rZW5fcGFpcikgfHwgaGFzKHRoaXMuYmVhcmVyX3Rva2VuKSB8fCBoYXModGhpcy5vYXV0aF9yZWZyZXNoKUINCgtjcmVkZW50aWFscyJYChFBUElLZXlDcmVkZW50aWFscxJDCgNrZXkYASABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJyKiAQoUVG9rZW5QYWlyQ3JlZGVudGlhbHMSQgoCaWQYASABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJxJGCgZzZWNyZXQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJyJfChZCZWFyZXJUb2tlbkNyZWRlbnRpYWxzEkUKBXRva2VuGAEgASgJQja6SDO6ATAKCG5vbmJsYW5rEhFtdXN0IG5vdCBiZSBibGFuaxoRdGhpcy50cmltKCkgIT0gJyci3gMKEE9BdXRoQ3JlZGVudGlhbHMSTQoNcmVmcmVzaF90b2tlbhgBIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnEkkKCWNsaWVudF9pZBgCIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnElIKDWNsaWVudF9zZWNyZXQYAyABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gAiAEBEkwKB2p3dF9rZXkYBCABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gBiAEBOnC6SG0aawoLZXhhY3RseV9vbmUSLnJlcXVpcmVzIGV4YWN0bHkgb25lIG9mIGNsaWVudF9zZWNyZXQsIGp3dF9rZXkaLGhhcyh0aGlzLmNsaWVudF9zZWNyZXQpICE9IGhhcyh0aGlzLmp3dF9rZXkpQhAKDl9jbGllbnRfc2VjcmV0QgoKCF9qd3Rfa2V5Iu0DCgVTY29wZRJNCghhcHBfbmFtZRgBIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSACIAQESUAoLZW52aXJvbm1lbnQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gBiAEBElQKD29yZ2FuaXphdGlvbl9pZBgDIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAKIAQESTwoKcHJvamVjdF9pZBgEIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAOIAQESTgoJd29ya3NwYWNlGAUgASgJQja6SDO6ATAKCG5vbmJsYW5rEhFtdXN0IG5vdCBiZSBibGFuaxoRdGhpcy50cmltKCkgIT0gJydIBIgBAUILCglfYXBwX25hbWVCDgoMX2Vudmlyb25tZW50QhIKEF9vcmdhbml6YXRpb25faWRCDQoLX3Byb2plY3RfaWRCDAoKX3dvcmtzcGFjZUJQWk5naXRodWIuY29tL3NhbmRib3gta2l0L2tpdC9zZGtzL2dvL2ludGVybmFsL2NvbnRyYWN0cy9raXQvc2FuZGJveC92MTtzYW5kYm94djFiBnByb3RvMw", [file_buf_validate_validate, file_google_protobuf_duration]); /** * Shared client configuration. Provider selection is a language SDK concern and @@ -121,7 +121,7 @@ export const AuthConfigSchema: GenMessage = /*@__PURE__*/ */ export type APIKeyCredentials = Message<"kit.sandbox.v1.APIKeyCredentials"> & { /** - * The secret API key. It is excluded from generated JSON. + * The secret API key. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string key = 1; */ @@ -142,14 +142,14 @@ export const APIKeyCredentialsSchema: GenMessage = /*@__PURE_ */ export type TokenPairCredentials = Message<"kit.sandbox.v1.TokenPairCredentials"> & { /** - * The token identifier. + * The token identifier. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string id = 1; */ id: string; /** - * The token secret. It is excluded from generated JSON. + * The token secret. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string secret = 2; */ @@ -170,7 +170,7 @@ export const TokenPairCredentialsSchema: GenMessage = /*@_ */ export type BearerTokenCredentials = Message<"kit.sandbox.v1.BearerTokenCredentials"> & { /** - * The bearer token. It is excluded from generated JSON. + * The bearer token. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string token = 1; */ @@ -191,7 +191,7 @@ export const BearerTokenCredentialsSchema: GenMessage = */ export type OAuthCredentials = Message<"kit.sandbox.v1.OAuthCredentials"> & { /** - * The refresh token. It is excluded from generated JSON. + * The refresh token. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string refresh_token = 1; */ @@ -205,14 +205,14 @@ export type OAuthCredentials = Message<"kit.sandbox.v1.OAuthCredentials"> & { clientId: string; /** - * One client secret. Do not set this together with JWTKey. + * One client secret. Do not set this together with JWTKey. Go encoding/json excludes it when marshaling a pointer. * * @generated from field: optional string client_secret = 3; */ clientSecret?: string | undefined; /** - * One JWT signing key. Do not set this together with ClientSecret. + * One JWT signing key. Do not set this together with ClientSecret. Go encoding/json excludes it when marshaling a pointer. * * @generated from field: optional string jwt_key = 4; */ @@ -259,6 +259,13 @@ export type Scope = Message<"kit.sandbox.v1.Scope"> & { * @generated from field: optional string project_id = 4; */ projectId?: string | undefined; + + /** + * The Blaxel workspace name. + * + * @generated from field: optional string workspace = 5; + */ + workspace?: string | undefined; }; /** diff --git a/sdks/typescript/package-lock.json b/sdks/typescript/package-lock.json index bd729346..bbad7eb0 100644 --- a/sdks/typescript/package-lock.json +++ b/sdks/typescript/package-lock.json @@ -8,6 +8,7 @@ "workspaces": [ "contracts", "sandbox", + "providers/blaxel", "providers/daytona", "providers/modal" ] @@ -42,10 +43,6 @@ "@bufbuild/protobuf": "^2.6.2" } }, - "contracts/node_modules/@bufbuild/protobuf": { - "version": "2.16.0", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, "contracts/node_modules/@bufbuild/protovalidate": { "version": "1.3.0", "license": "Apache-2.0", @@ -61,18 +58,6 @@ "version": "0.6.1", "license": "MIT" }, - "contracts/node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@aws-sdk/checksums": { "version": "3.1001.4", "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1001.4.tgz", @@ -401,6 +386,36 @@ "node": ">=18.0.0" } }, + "node_modules/@blaxel/core": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@blaxel/core/-/core-0.3.36.tgz", + "integrity": "sha512-nzwK3QMe6kxcw/8kihdYQjpm+Lu2LnOGm/QTySjCJLk7h8nFRAhNQU/fbKCOT0GB5UhuMtTsP0meWWMmFtDuLQ==", + "license": "MIT", + "dependencies": { + "@hey-api/client-fetch": "^0.10.0", + "@modelcontextprotocol/sdk": "^1.20.0", + "archiver": "^7.0.1", + "axios": "^1.9.0", + "dockerfile-ast": "^0.7.1", + "dotenv": "^17.4.2", + "form-data": "^4.0.2", + "jwt-decode": "^4.0.0", + "smol-toml": "^1.8.0", + "uuid": "^11.1.0", + "ws": "^8.18.2", + "yaml": "^2.7.1", + "zod": "^4.6.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.16.0.tgz", + "integrity": "sha512-FWa0sPlqYGJgpTs6OxBRcL/AW4JT0OIO+W1ajerluoTVh8CV0B7XM1qsNCRgnkSCkRCgOICKHRQlY17jrsm0+Q==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.2.tgz", @@ -571,12 +586,244 @@ "node": ">=6" } }, + "node_modules/@hey-api/client-fetch": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.10.2.tgz", + "integrity": "sha512-AGiFYDx+y8VT1wlQ3EbzzZtfU8EfV+hLLRTtr8Y/tjYZaxIECwJagVZf24YzNbtEBXONFV50bwcU1wLVGXe1ow==", + "deprecated": "Starting with v0.73.0, this package is bundled directly inside @hey-api/openapi-ts.", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "@hey-api/openapi-ts": "< 2" + } + }, + "node_modules/@hey-api/codegen-core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.9.1.tgz", + "integrity": "sha512-s97jL1dgTMuiMHv2BZ1X4Tgd99Mf9GOvGdNqNcGwIMmnR+PgYNoraj4Zvp134MKsNCap/m7k0r0vKKnl56pj4w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hey-api/types": "0.1.4", + "ansi-colors": "4.1.3", + "c12": "3.3.4", + "color-support": "1.1.3" + }, + "engines": { + "node": ">=22.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/json-schema-ref-parser": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.4.4.tgz", + "integrity": "sha512-otmd+zCxbYVBIp/mlMTnGkvlNYLkVKgs3VOIq0kSnenhB1+fRwLPQIeSwyWM6E51oXhUedkYjVsVpkVexeuJOA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jsdevtools/ono": "7.1.3", + "@types/json-schema": "7.0.15", + "js-yaml": "4.2.0" + }, + "engines": { + "node": ">=22.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.99.0", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.99.0.tgz", + "integrity": "sha512-SePU/5oEWWkvUBYmvzdYRctseoLuskyhs4ET0RvLIcmzc8yLQoA2R+KtBIQ8bPsoSUB0m4E5SmBnl6aGSA0szQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hey-api/codegen-core": "0.9.1", + "@hey-api/json-schema-ref-parser": "1.4.4", + "@hey-api/shared": "0.5.0", + "@hey-api/spec-types": "0.2.0", + "@hey-api/types": "0.1.4", + "@lukeed/ms": "2.0.2", + "ansi-colors": "4.1.3", + "color-support": "1.1.3", + "commander": "15.0.0", + "get-tsconfig": "4.14.0" + }, + "bin": { + "openapi-ts": "bin/run.js" + }, + "engines": { + "node": ">=22.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": ">=5.5.3 || >=6.0.0 || 6.0.1-rc" + } + }, + "node_modules/@hey-api/shared": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@hey-api/shared/-/shared-0.5.0.tgz", + "integrity": "sha512-JN/j4Ebh4cJGYIQ5cwWuqe7GeSUyQoz7oC51WqyhKOcrejK6DKZMDkshc5d1eKTRuRL+rjozuRcoUaZZn2DGPw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hey-api/codegen-core": "0.9.1", + "@hey-api/json-schema-ref-parser": "1.4.4", + "@hey-api/spec-types": "0.2.0", + "@hey-api/types": "0.1.4", + "ansi-colors": "4.1.3", + "cross-spawn": "7.0.6", + "open": "11.0.0", + "semver": "7.8.4" + }, + "engines": { + "node": ">=22.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/spec-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@hey-api/spec-types/-/spec-types-0.2.0.tgz", + "integrity": "sha512-ibQ8Is7evMavzr8GNyJCcTg975d8DpaMUyLmOrQ85UBdy1l6t1KuRAwgChAbesJsIlNV6gjmlXruWyegDX18Fg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hey-api/types": "0.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@hey-api/types/-/types-0.1.4.tgz", + "integrity": "sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg==", + "license": "MIT", + "peer": true + }, + "node_modules/@hono/node-server": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.6.tgz", + "integrity": "sha512-p/KH9MnfNcsm3sR6x4lNgm5j+QN4Et8srKredl9DZfXiyZ1UQ3aQ2YVpFLbqktYN5JI+DB+VZFKM2PqE5hLKjQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4 || ^5.0.0-0" + } + }, "node_modules/@iarna/toml": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", "license": "ISC" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.6.0.tgz", + "integrity": "sha512-XZ5apbzreCbXSKwPjJstu8R0zOi247w3XYwWxtwBXgpZrCyc1Ka7p9/xY0A9/diMewmHbEKz+FIraWaEWjlN5g==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -589,6 +836,13 @@ "node": ">=18.0.0" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", @@ -599,6 +853,63 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT", + "peer": true + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.32.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.2.tgz", + "integrity": "sha512-SWsVtH/0vULSQStoZ6xxH2K09Li8fXYTMC64ZeogCdpZ3XSi1eCPOByV1OpuJxuI3OcmHhosDWYyii07tpPsLw==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1246,6 +1557,26 @@ "node": ">=14" } }, + "node_modules/@oxc-project/types": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1303,69 +1634,335 @@ "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, - "node_modules/@sandbox-kit/contracts": { - "resolved": "contracts", - "link": true - }, - "node_modules/@sandbox-kit/daytona": { - "resolved": "providers/daytona", - "link": true - }, - "node_modules/@sandbox-kit/modal": { - "resolved": "providers/modal", - "link": true - }, - "node_modules/@sandbox-kit/sandbox": { - "resolved": "sandbox", - "link": true - }, - "node_modules/@smithy/core": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.0.tgz", - "integrity": "sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.19.0", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.18.tgz", + "integrity": "sha512-ubTG+4KlidvioxjOFdsdEa2F6JVGZb8m2ByJUqQ7fYzUeT1cEn194POcsQ10PKH4p//q0YQn4SB3YpSuzcA1Yg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", - "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.2", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.18.tgz", + "integrity": "sha512-2Xa1t/JeFGTZn5TbzgPppcCftF8sCVnTNvrIvoSdcZhoFzAuVy1L3xxQUQ4sfWDtwVzg1u6Yi1R3SlJugRNV2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", - "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.18.0", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.18.tgz", + "integrity": "sha512-iUzw9HshVvrWci9gVXjxWS7/M/IcLvNo67vu2c/w9zKBz4hIvPRixdUpIW/iCfhldLaq9ER2hecsI9UY5z0/yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@smithy/node-http-handler": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", - "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", - "license": "Apache-2.0", - "dependencies": { + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.18.tgz", + "integrity": "sha512-XuDHFzbFEtZl0QGJi92+fmG67tfnp1iZ9VOeDkwasGyTq2VG3/5PnJnvAr+MTzFh/rNScJ3VhN+XpZyyGsN7Jw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.18.tgz", + "integrity": "sha512-MM8opLfsLK15oEXDz6iBc0t3ahu+ew9jsAmha+ovZ33hSwYgqo7YmpRITqFTfW7xA71BLEQ3jJdTsaCmhB1c9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.18.tgz", + "integrity": "sha512-J6EoCWiy5RnFQzHrKyHXXevZ9lukQfGpnchX/zG/k/WC2nc9O9XyS1JgA8kI9VxMFggMbCPjpv7LcikriXfehw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.18.tgz", + "integrity": "sha512-HIkFuLF7CdDeki+1aCZLLbLq0hk/PHntb+z4/YCTo47JbksTD6jJEELIz92tYROmDmvMrNqUMqBi71BkJLDR+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.18.tgz", + "integrity": "sha512-4oqWItLLYt7IceOnElCct8T2ek5p7PmD7LN04TRJRXGo5f3HtKTgGH6PhYRFK4C2ICkq/LMUU0BVPvvlAKhm8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.18.tgz", + "integrity": "sha512-+AkRly5SrCEgnPhGRfub7pucASYEC2fTsxZg7VCcnozpA2+Wn/gCYy1t7LuJS67/xyJu/7noYBJncDUkbwgw2A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.18.tgz", + "integrity": "sha512-Ttg1r9vVykMnQrE/GwYO9icKtXiuiMfRe/w74xJkeoVuvhRG+WtT6LEMOf58Yl63yd77lhHzO8Y55R8CC3XALA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.18.tgz", + "integrity": "sha512-xX1Tt4o5jUMma0vJsXp5eRZA9nOuyigb2dKJMMzZRmzMxLYGE9PaKKSqHPzPvSQgM8ZOEacSTQwDQcwWfh0xmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.18.tgz", + "integrity": "sha512-4VVlGI5K2kociZlW3lSN8VjG3TW5yQIlAcfCdBXMgGjJxZll15Fa4ocRHXHvIVVnwlibCw/NQajJrNDiqqpiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.18.tgz", + "integrity": "sha512-PApTsgH1jLYuyy3tSCz9De82Gq5qXwi7vImVZhEzTgX1C4150Cp5M2ZfL3pLvbCvbEHL0oJeC6pd6podtv8Y5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.18.tgz", + "integrity": "sha512-J2XjWjx/SP+4x0H4r6dz3Vzb5WVkg7re40Do9K7HJ3sK2/xcmZrWwakXlSwaMvOkvruLJCD4CL/3wqsIR/thgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.18.tgz", + "integrity": "sha512-XwRJxRQvkuEtS0hIIt29QrqPJ+JzMJSNYHJw4ae/mc6uEarfRG1ceyg4HmmWiBdL/YCb6g7N5BLbSvmRJRYZHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.1.0.tgz", + "integrity": "sha512-K+NU2HDCEem4sClgHH4ZZXwdJeNFkJo+hRk479DIwdIviAPN2hFJH1KHHwg8CxHQXvi0M5XY4deTXINfIiSG6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/contracts": { + "resolved": "contracts", + "link": true + }, + "node_modules/@sandbox-kit/daytona": { + "resolved": "providers/daytona", + "link": true + }, + "node_modules/@sandbox-kit/modal": { + "resolved": "providers/modal", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "sandbox", + "link": true + }, + "node_modules/@smithy/core": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.0.tgz", + "integrity": "sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "license": "Apache-2.0", + "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" @@ -1406,6 +2003,45 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT", + "peer": true + }, "node_modules/@types/node": { "version": "26.6.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", @@ -1415,927 +2051,874 @@ "undici-types": "~8.9.0" } }, - "node_modules/abort-controller-x": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.5.0.tgz", - "integrity": "sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==", - "license": "MIT" - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "tinyrainbow": "^3.1.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/vitest" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", - "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.6", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "license": "MIT" - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "dev": true, "license": "MIT", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "dev": true, + "license": "MIT", "dependencies": { - "streamsearch": "^1.1.0" + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">=10.16.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "event-target-shim": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=6.5" } }, - "node_modules/cbor-extract": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.2.tgz", - "integrity": "sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng==", - "hasInstallScript": true, + "node_modules/abort-controller-x": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.5.0.tgz", + "integrity": "sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", - "optional": true, "dependencies": { - "node-gyp-build-optional-packages": "5.1.1" - }, - "bin": { - "download-cbor-prebuilds": "bin/download-prebuilds.js" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, - "optionalDependencies": { - "@cbor-extract/cbor-extract-darwin-arm64": "2.2.2", - "@cbor-extract/cbor-extract-darwin-x64": "2.2.2", - "@cbor-extract/cbor-extract-linux-arm": "2.2.2", - "@cbor-extract/cbor-extract-linux-arm64": "2.2.2", - "@cbor-extract/cbor-extract-linux-x64": "2.2.2", - "@cbor-extract/cbor-extract-win32-x64": "2.2.2" + "engines": { + "node": ">= 0.6" } }, - "node_modules/cbor-x": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.8.tgz", - "integrity": "sha512-wUtIwq2J+Y2bEvDpdJj0z9QIX+PGMzms489HFvNwr0V8Zq3ejqOdZKTaTe6Zru8WU0IwuRDtpys3nUF4LDRKAQ==", + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "optionalDependencies": { - "cbor-extract": "^2.2.2" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": ">= 0.6" } }, - "node_modules/cjs-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz", - "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "debug": "4" }, "engines": { - "node": ">=7.0.0" + "node": ">= 6.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">= 0.8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "ajv": "^8.0.0" }, - "engines": { - "node": ">=6.0" + "peerDependencies": { + "ajv": "^8.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "ajv": { "optional": true } } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "license": "MIT", + "peer": true, "engines": { - "node": ">=0.4.0" + "node": ">=6" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { "node": ">=8" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/engine.io-client": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.7.tgz", - "integrity": "sha512-GyAvmPA7gnRCpDQbYACAy79Lg4ewevrmaPzySZz0OVvgyZc3gEfLLqPTYk8wVBScjOEpM9ZO4KryyFpVItjGEA==", + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "license": "MIT", "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.21.0", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-client/node_modules/ws": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", - "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">= 14" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">= 14" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/es-module-lexer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-3.0.4.tgz", - "integrity": "sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==", - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 0.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "peer": true + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.x" + "node": ">=12" } }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "license": "MIT", "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "node_modules/b4a": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz", + "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" }, - "engines": { - "node": ">=8.6.0" + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } } }, - "node_modules/fastq": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", - "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, + "node_modules/bare-buffer": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.7.1.tgz", + "integrity": "sha512-cIjZnSO+y89ykVX9x96OVnA6tGORBn7vGJ/e3PCbBHMEyfMNG/ixzOS0lLa6saMlWcs69bSKjxFyDwjK152OMg==", + "license": "Apache-2.0", "engines": { - "node": ">=8" + "bare": ">=1.20.0" } }, - "node_modules/follow-redirects": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", - "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" + "node_modules/bare-events": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" }, "peerDependenciesMeta": { - "debug": { + "bare-abort-controller": { "optional": true } } }, - "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", - "license": "MIT", + "node_modules/bare-fs": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.4.tgz", + "integrity": "sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==", + "license": "Apache-2.0", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" }, "engines": { - "node": ">= 6" + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, - "node_modules/forwarded-parse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", - "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", - "license": "MIT" + "node_modules/bare-path": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz", + "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==", + "license": "Apache-2.0" }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", + "node_modules/bare-stream": { + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", + "license": "Apache-2.0", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", + "node_modules/bare-url": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.6.tgz", + "integrity": "sha512-iqkXHkW65De+BT14nHLiBFbNuJ/DdMrZaVNJ1IXPctpK6b1olUJ5Qmy8QBvwROajTiGztso2n82kFEvCsNG7Rg==", + "license": "Apache-2.0", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "bare-buffer": "^3.0.0", + "bare-path": "^3.0.0" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/body-parser": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.1.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.3", + "on-finished": "^2.4.1", + "qs": "^6.16.1", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "balanced-match": "^1.0.0" } }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "fill-range": "^7.1.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "license": "MIT", "dependencies": { - "parse-passwd": "^1.0.0" - }, + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8.0.0" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", "license": "MIT", + "peer": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "run-applescript": "^7.0.0" }, "engines": { - "node": ">= 6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/import-in-the-middle": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.5.2.tgz", - "integrity": "sha512-WseIA/4o56+GYO3RmlOPvejYi771OFeJEeK/ejROsr9bdjW+E5NXTOD1Di8TL40Qgg3JJX9aa97Hf8iiSVIKZw==", - "license": "Apache-2.0", + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { - "cjs-module-lexer": "^2.2.0", - "es-module-lexer": "^3.0.2", - "module-details-from-path": "^1.0.4" + "streamsearch": "^1.1.0" }, "engines": { - "node": ">=18" + "node": ">=10.16.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", "license": "MIT", - "engines": { - "node": ">=8" + "peer": true, + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/isomorphic-ws": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", - "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "node_modules/cbor-extract": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.2.tgz", + "integrity": "sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng==", + "hasInstallScript": true, "license": "MIT", - "peerDependencies": { - "ws": "*" + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.2", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.2", + "@cbor-extract/cbor-extract-linux-arm": "2.2.2", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.2", + "@cbor-extract/cbor-extract-linux-x64": "2.2.2", + "@cbor-extract/cbor-extract-win32-x64": "2.2.2" } }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/cbor-x": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.8.tgz", + "integrity": "sha512-wUtIwq2J+Y2bEvDpdJj0z9QIX+PGMzms489HFvNwr0V8Zq3ejqOdZKTaTe6Zru8WU0IwuRDtpys3nUF4LDRKAQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "optionalDependencies": { + "cbor-extract": "^2.2.2" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", + "peer": true, "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">=8.6" + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", + "node_modules/cjs-module-lexer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz", + "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { - "mime-db": "1.52.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=12" } }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "minipass": "^7.1.2" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 18" - } - }, - "node_modules/modal": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/modal/-/modal-0.11.1.tgz", - "integrity": "sha512-RVb84wPZGG4zd3q2Fuww/ku5kZbp9sDkSUVqIc2VdKa+2QQyuqM/puVDbeVze8XN9tdUp9IJZRbdECJmz56TeQ==", - "license": "Apache-2.0", - "dependencies": { - "cbor-x": "^1.6.0", - "long": "^5.3.1", - "nice-grpc": "^2.1.12", - "protobufjs": "^7.5.0", - "smol-toml": "^1.9.0", - "uuid": "^11.1.0" + "node": ">=7.0.0" } }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/nice-grpc": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.17.tgz", - "integrity": "sha512-pu9xYPlWSeqoYQOCqb2ftQqZi/P2DaI70PSuwnxvoyIRiilaOVtktyPe6LmuXegWB4Ic1sPuDGCnCCASbwzK0w==", - "license": "MIT", - "dependencies": { - "@grpc/grpc-js": "^1.14.0", - "abort-controller-x": "^0.5.0", - "nice-grpc-common": "^2.0.4" - } - }, - "node_modules/nice-grpc-common": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.4.tgz", - "integrity": "sha512-gOEXlD6ShXZMZ8k+49wm/bA2j1+3IKbEFV9WYREJcvZV4kM5L1KkILYTX9VYBzZF2OuYCe1wp8c82bQkvR0fHw==", - "license": "MIT", - "dependencies": { - "ts-error": "^1.0.6" + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "peer": true, + "bin": { + "color-support": "bin.js" } }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", - "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", - "optional": true, "dependencies": { - "detect-libc": "^2.0.1" + "delayed-stream": "~1.0.0" }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", "license": "MIT", + "peer": true, "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=22.12.0" } }, - "node_modules/protobufjs": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", - "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", - "engines": { - "node": ">=10" + "node": ">= 14" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -2350,81 +2933,134 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT", + "peer": true + }, + "node_modules/content-disposition": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz", + "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/require-in-the-middle": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", - "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">=9.3.0 || >=8.10.0 <9.0.0" + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=0.8" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "license": "MIT", "dependencies": { - "queue-microtask": "^1.2.2" + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -2439,199 +3075,231 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } }, - "node_modules/shell-quote": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", - "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/smol-toml": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", - "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 18" + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/cyyynthia" + "engines": { + "node": ">= 8" } }, - "node_modules/socket.io-client": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.4.tgz", - "integrity": "sha512-7wjKAvVXYOJsbmUgkWbnhPD4bFpkqrUR0++XOjpGFmqDqsqV1CI93VTraeExLxpjxEqrWgFoSRZu1ihjLWfkAA==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" + "ms": "^2.1.3" }, "engines": { - "node": ">=10.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/socket.io-parser": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", - "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "license": "MIT", + "peer": true, "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stream-browserify": { + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "license": "MIT", - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT", + "peer": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=0.4.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "engines": { + "node": ">= 0.8" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "peer": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", "engines": { "node": ">=8" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/dockerfile-ast": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/dockerfile-ast/-/dockerfile-ast-0.7.1.tgz", + "integrity": "sha512-oX/A4I0EhSkGqrFv0YuvPkBUSYp1XiY8O8zAKc8Djglx8ocz+JfOr8gP0ryRMC2myqvDLagmnZaU9ot1vG2ijw==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3" }, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/tar": { - "version": "7.5.22", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", - "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=8.0" + "node": ">= 0.4" } }, - "node_modules/ts-error": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", - "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/undici-types": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", - "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/uuid": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", - "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/encodeurl": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.1.tgz", + "integrity": "sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==", "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=10" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ws": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", - "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "node_modules/engine.io-client": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.7.tgz", + "integrity": "sha512-GyAvmPA7gnRCpDQbYACAy79Lg4ewevrmaPzySZz0OVvgyZc3gEfLLqPTYk8wVBScjOEpM9ZO4KryyFpVItjGEA==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -2649,300 +3317,445 @@ } } }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=10.0.0" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.4" } }, - "node_modules/yaml": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", - "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" + "node_modules/es-module-lexer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-3.0.4.tgz", + "integrity": "sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" }, "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" + "node": ">= 0.4" } }, - "node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6" } }, - "providers/daytona": { - "name": "@sandbox-kit/daytona", - "version": "0.1.7", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", "dependencies": { - "@bufbuild/protobuf": "2.16.0", - "@daytona/sdk": "0.225.0", - "@sandbox-kit/sandbox": "0.1.7" - }, - "devDependencies": { - "@types/node": "24.10.1", - "typescript": "5.9.3", - "vitest": "4.1.11" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + "@types/estree": "^1.0.0" } }, - "providers/daytona/node_modules/@bufbuild/protobuf": { - "version": "2.16.0", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "providers/daytona/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "dev": true, - "license": "MIT" + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "providers/daytona/node_modules/@oxc-project/types": { - "version": "0.153.0", - "dev": true, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/oxc-project" + "engines": { + "node": ">=6" } }, - "providers/daytona/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.13", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.8.x" } }, - "providers/daytona/node_modules/@rolldown/pluginutils": { + "node_modules/events-universal": { "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/@standard-schema/spec": { - "version": "1.1.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } }, - "providers/daytona/node_modules/@types/chai": { - "version": "5.2.3", - "dev": true, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "license": "MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" } }, - "providers/daytona/node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/@types/estree": { - "version": "1.0.9", - "dev": true, - "license": "MIT" + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } }, - "providers/daytona/node_modules/@types/node": { - "version": "24.10.1", - "dev": true, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "providers/daytona/node_modules/@vitest/expect": { - "version": "4.1.11", + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz", + "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.3.0", + "content-disposition": "^2.0.1", + "content-type": "^2.0.0", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.8", + "qs": "^6.16.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.1.0", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/@vitest/mocker": { - "version": "4.1.11", - "dev": true, + "node_modules/express-rate-limit": { + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.3.tgz", + "integrity": "sha512-zPLqDI2f0gV2WmGnacLf9XSSqn4VldRyP5LCzOwx0BKH40mO6+2PCFkoSsRO2jePx87/Kf6NUF6P2QOTKXHbQA==", "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.11", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "express": ">= 4.11" } }, - "providers/daytona/node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "dev": true, + "node_modules/express/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" + "engines": { + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/@vitest/runner": { - "version": "4.1.11", - "dev": true, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/@vitest/snapshot": { - "version": "4.1.11", - "dev": true, + "node_modules/exsolve": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.2.0.tgz", + "integrity": "sha512-h1pQPR9XoeSMdntRNCjyTznaume4UpfXrULBnlkZ2CUM/G5eSa+pBByGldO5uR8dxbPC+OuJXsPPNf+JZ81ADg==", + "license": "MIT", + "peer": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=8.6.0" } }, - "providers/daytona/node_modules/@vitest/spy": { - "version": "4.1.11", - "dev": true, + "node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/@vitest/utils": { - "version": "4.1.11", - "dev": true, + "node_modules/follow-redirects": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/isaacs" } }, - "providers/daytona/node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, "engines": { - "node": ">=12" + "node": ">= 6" } }, - "providers/daytona/node_modules/chai": { - "version": "6.3.0", - "dev": true, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.6" } }, - "providers/daytona/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", "license": "MIT" }, - "providers/daytona/node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "providers/daytona/node_modules/expect-type": { - "version": "1.4.0", - "dev": true, - "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": ">= 0.8" } }, - "providers/daytona/node_modules/fsevents": { + "node_modules/fsevents": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -2952,749 +3765,787 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "providers/daytona/node_modules/lightningcss": { - "version": "1.33.0", - "dev": true, - "license": "MPL-2.0", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" + "url": "https://github.com/sponsors/ljharb" } }, - "providers/daytona/node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">= 0.4" } }, - "providers/daytona/node_modules/magic-string": { - "version": "0.30.21", - "dev": true, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "license": "MIT", + "peer": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "providers/daytona/node_modules/nanoid": { - "version": "3.3.20", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/giget": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", + "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", "license": "MIT", + "peer": true, "bin": { - "nanoid": "bin/nanoid.cjs" + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "providers/daytona/node_modules/obug": { - "version": "2.2.1", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { - "node": ">=12.20.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "providers/daytona/node_modules/picocolors": { - "version": "1.1.1", - "dev": true, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "providers/daytona/node_modules/postcss": { - "version": "8.5.30", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { - "nanoid": "^3.3.20", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.2" + "has-symbols": "^1.0.3" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "providers/daytona/node_modules/rolldown": { - "version": "1.2.13", - "dev": true, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.153.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" + "function-bind": "^1.1.2" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.13", - "@rolldown/binding-android-arm64": "1.2.13", - "@rolldown/binding-darwin-arm64": "1.2.13", - "@rolldown/binding-darwin-x64": "1.2.13", - "@rolldown/binding-freebsd-x64": "1.2.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", - "@rolldown/binding-linux-arm64-gnu": "1.2.13", - "@rolldown/binding-linux-arm64-musl": "1.2.13", - "@rolldown/binding-linux-ppc64-gnu": "1.2.13", - "@rolldown/binding-linux-s390x-gnu": "1.2.13", - "@rolldown/binding-linux-x64-gnu": "1.2.13", - "@rolldown/binding-linux-x64-musl": "1.2.13", - "@rolldown/binding-openharmony-arm64": "1.2.13", - "@rolldown/binding-win32-arm64-msvc": "1.2.13", - "@rolldown/binding-win32-x64-msvc": "1.2.13" - } - }, - "providers/daytona/node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" + "node": ">= 0.4" + } }, - "providers/daytona/node_modules/source-map-js": { - "version": "1.2.2", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, "engines": { "node": ">=0.10.0" } }, - "providers/daytona/node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/std-env": { - "version": "4.3.0", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/tinybench": { - "version": "2.9.0", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/tinyexec": { - "version": "1.3.1", - "dev": true, + "node_modules/hono": { + "version": "4.13.17", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.17.tgz", + "integrity": "sha512-vZIcbkvjOR5hHRWERbGkr8fGfF0GGM1ooYkuH448iO3b2AdVEr3AXZSw4Z3Kn+1L5z0CVoVeyV29VGYmFh05kQ==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=16.9.0" } }, - "providers/daytona/node_modules/tinyglobby": { - "version": "0.2.17", - "dev": true, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "dev": true, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=12.0.0" + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "engines": { + "node": ">=0.10.0" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "license": "BSD-3-Clause" + }, + "node_modules/import-in-the-middle": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.5.2.tgz", + "integrity": "sha512-WseIA/4o56+GYO3RmlOPvejYi771OFeJEeK/ejROsr9bdjW+E5NXTOD1Di8TL40Qgg3JJX9aa97Hf8iiSVIKZw==", + "license": "Apache-2.0", + "dependencies": { + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^3.0.2", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" } }, - "providers/daytona/node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.7.6", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.6.tgz", + "integrity": "sha512-00LYkM6byILa9eQIx6qoaXCM40xTLd56AZ4vyaPW3YTyb49VZYPo5xsiAq4tuQ54GH4R7gVdYScJ1B7B77PuTg==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "providers/daytona/node_modules/tinyrainbow": { - "version": "3.2.0", - "dev": true, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=0.10.0" } }, - "providers/daytona/node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=14.17" + "node": ">=0.10.0" } }, - "providers/daytona/node_modules/undici-types": { - "version": "7.16.0", - "dev": true, - "license": "MIT" + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "providers/daytona/node_modules/vite": { - "version": "8.3.4", - "dev": true, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "license": "MIT", + "peer": true, "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.7", - "postcss": "^8.5.29", - "rolldown": "~1.2.12", - "tinyglobby": "^0.2.17" + "is-docker": "^3.0.0" }, "bin": { - "vite": "bin/vite.js" + "is-inside-container": "cli.js" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "providers/daytona/node_modules/vite/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "providers/daytona/node_modules/vitest": { - "version": "4.1.11", - "dev": true, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "license": "MIT", + "peer": true, "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" + "is-inside-container": "^1.0.0" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=16" }, "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "providers/daytona/node_modules/vitest/node_modules/es-module-lexer": { - "version": "2.3.2", - "dev": true, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, - "providers/daytona/node_modules/vitest/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "ws": "*" } }, - "providers/daytona/node_modules/why-is-node-running": { - "version": "2.3.0", - "dev": true, - "license": "MIT", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" + "@isaacs/cliui": "^8.0.2" }, - "bin": { - "why-is-node-running": "cli.js" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "providers/modal": { - "name": "@sandbox-kit/modal", - "version": "0.1.7", + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", - "dependencies": { - "@bufbuild/protobuf": "2.16.0", - "@sandbox-kit/sandbox": "0.1.7", - "modal": "0.11.1" - }, - "devDependencies": { - "@types/node": "24.10.1", - "typescript": "5.9.3", - "vitest": "4.1.11" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, - "providers/modal/node_modules/@bufbuild/protobuf": { - "version": "2.16.0", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "providers/modal/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/@oxc-project/types": { - "version": "0.153.0", - "dev": true, + "node_modules/jose": { + "version": "6.2.16", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.16.tgz", + "integrity": "sha512-4LSJpbuBuPdGrONCdZUy0xnxtLf8eEz+EY0SMXdHQEBU7bgUHDVblU/JAjryv3wID9/LK7/YuwFwq+AQNfMscQ==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/oxc-project" + "url": "https://github.com/sponsors/panva" } }, - "providers/modal/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.13", - "cpu": [ - "arm64" + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "providers/modal/node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "dev": true, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, - "providers/modal/node_modules/@standard-schema/spec": { - "version": "1.1.0", - "dev": true, - "license": "MIT" + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" }, - "providers/modal/node_modules/@types/chai": { - "version": "5.2.3", - "dev": true, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "license": "MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" } }, - "providers/modal/node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } }, - "providers/modal/node_modules/@types/estree": { - "version": "1.0.9", - "dev": true, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, - "providers/modal/node_modules/@types/node": { - "version": "24.10.1", - "dev": true, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "safe-buffer": "~5.1.0" } }, - "providers/modal/node_modules/@vitest/expect": { - "version": "4.1.11", + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "detect-libc": "^2.0.3" }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "providers/modal/node_modules/@vitest/mocker": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.11", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, - "providers/modal/node_modules/@vitest/pretty-format": { - "version": "4.1.11", + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/@vitest/runner": { - "version": "4.1.11", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/@vitest/snapshot": { - "version": "4.1.11", + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "providers/modal/node_modules/@vitest/spy": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/@vitest/utils": { - "version": "4.1.11", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "providers/modal/node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/chai": { - "version": "6.3.0", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" - } - }, - "providers/modal/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/es-module-lexer": { - "version": "2.3.2", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/expect-type": { - "version": "1.4.0", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/fdir": { - "version": "6.5.0", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "node": ">= 12.0.0" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/fsevents": { - "version": "2.3.3", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/lightningcss": { + "node_modules/lightningcss-linux-x64-musl": { "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" } }, - "providers/modal/node_modules/lightningcss-darwin-arm64": { + "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -3702,7 +4553,7 @@ "license": "MPL-2.0", "optional": true, "os": [ - "darwin" + "win32" ], "engines": { "node": ">= 12.0.0" @@ -3712,204 +4563,1548 @@ "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/magic-string": { - "version": "0.30.21", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "providers/modal/node_modules/nanoid": { - "version": "3.3.20", + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "providers/modal/node_modules/obug": { - "version": "2.2.1", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { - "node": ">=12.20.0" + "node": ">= 0.4" } }, - "providers/modal/node_modules/picocolors": { + "node_modules/media-typer": { "version": "1.1.1", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "providers/modal/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "providers/modal/node_modules/postcss": { - "version": "8.5.30", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { - "nanoid": "^3.3.20", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.2" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8.6" } }, - "providers/modal/node_modules/rolldown": { - "version": "1.2.13", - "dev": true, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.153.0", - "@rolldown/pluginutils": "^1.0.0" + "mime-db": "1.52.0" }, - "bin": { - "rolldown": "bin/cli.mjs" + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=16 || 14 >=14.17" }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.13", - "@rolldown/binding-android-arm64": "1.2.13", - "@rolldown/binding-darwin-arm64": "1.2.13", - "@rolldown/binding-darwin-x64": "1.2.13", - "@rolldown/binding-freebsd-x64": "1.2.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", - "@rolldown/binding-linux-arm64-gnu": "1.2.13", - "@rolldown/binding-linux-arm64-musl": "1.2.13", - "@rolldown/binding-linux-ppc64-gnu": "1.2.13", - "@rolldown/binding-linux-s390x-gnu": "1.2.13", - "@rolldown/binding-linux-x64-gnu": "1.2.13", - "@rolldown/binding-linux-x64-musl": "1.2.13", - "@rolldown/binding-openharmony-arm64": "1.2.13", - "@rolldown/binding-win32-arm64-msvc": "1.2.13", - "@rolldown/binding-win32-x64-msvc": "1.2.13" - } - }, - "providers/modal/node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "providers/modal/node_modules/source-map-js": { - "version": "1.2.2", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=0.10.0" + "node": ">=16 || 14 >=14.17" } }, - "providers/modal/node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } }, - "providers/modal/node_modules/std-env": { - "version": "4.3.0", - "dev": true, + "node_modules/modal": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/modal/-/modal-0.11.1.tgz", + "integrity": "sha512-RVb84wPZGG4zd3q2Fuww/ku5kZbp9sDkSUVqIc2VdKa+2QQyuqM/puVDbeVze8XN9tdUp9IJZRbdECJmz56TeQ==", + "license": "Apache-2.0", + "dependencies": { + "cbor-x": "^1.6.0", + "long": "^5.3.1", + "nice-grpc": "^2.1.12", + "protobufjs": "^7.5.0", + "smol-toml": "^1.9.0", + "uuid": "^11.1.0" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "license": "MIT" }, - "providers/modal/node_modules/tinybench": { - "version": "2.9.0", - "dev": true, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "providers/modal/node_modules/tinyexec": { - "version": "1.3.1", + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=18" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "providers/modal/node_modules/tinyglobby": { - "version": "0.2.17", - "dev": true, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "content-type": "^2.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/modal/node_modules/tinyrainbow": { - "version": "3.2.0", - "dev": true, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/modal/node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" + "node_modules/nice-grpc": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.17.tgz", + "integrity": "sha512-pu9xYPlWSeqoYQOCqb2ftQqZi/P2DaI70PSuwnxvoyIRiilaOVtktyPe6LmuXegWB4Ic1sPuDGCnCCASbwzK0w==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.14.0", + "abort-controller-x": "^0.5.0", + "nice-grpc-common": "^2.0.4" } }, - "providers/modal/node_modules/undici-types": { - "version": "7.16.0", - "dev": true, - "license": "MIT" + "node_modules/nice-grpc-common": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.4.tgz", + "integrity": "sha512-gOEXlD6ShXZMZ8k+49wm/bA2j1+3IKbEFV9WYREJcvZV4kM5L1KkILYTX9VYBzZF2OuYCe1wp8c82bQkvR0fHw==", + "license": "MIT", + "dependencies": { + "ts-error": "^1.0.6" + } }, - "providers/modal/node_modules/vite": { - "version": "8.3.4", - "dev": true, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", "license": "MIT", + "optional": true, "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.7", - "postcss": "^8.5.29", - "rolldown": "~1.2.12", - "tinyglobby": "^0.2.17" + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "license": "MIT", + "peer": true + }, + "node_modules/on-finished": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.2.tgz", + "integrity": "sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "license": "MIT", + "peer": true, + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT", + "peer": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-types": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.4.0.tgz", + "integrity": "sha512-07Fys7iVx0X6TcviMOgogM1Lk33ZyqOezMnU79S+kffWqwolG/5W9V8EgQSeYCIfFTYVgssRbnH630Oa2+soBA==", + "license": "MIT", + "peer": true, + "dependencies": { + "confbox": "^0.3.1", + "exsolve": "^1.1.3", + "pathe": "^2.0.3" + } + }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.2.tgz", + "integrity": "sha512-ZUAKhKlkCOrIGKc8cj7zek9s9z1mveHif/25nUshI2BzCRzF7KH7KHa+Lnh2ubq+e1tIlaFuP763mzW4qVoehg==", + "license": "MIT", + "peer": true + }, + "node_modules/postcss": { + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/qs": { + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.1.tgz", + "integrity": "sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rc9": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", + "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "defu": "^6.1.7", + "destr": "^2.0.5" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.18.tgz", + "integrity": "sha512-C1uuT3UR60SShuCaPQFAAZyefJCWZlAKB02BV3BuPE2Rs4fevRQltEKoFO2tV5W9/eJhU5RieV6vJj3euulfNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.156.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.18", + "@rolldown/binding-android-arm64": "1.2.18", + "@rolldown/binding-darwin-arm64": "1.2.18", + "@rolldown/binding-darwin-x64": "1.2.18", + "@rolldown/binding-freebsd-x64": "1.2.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.18", + "@rolldown/binding-linux-arm64-gnu": "1.2.18", + "@rolldown/binding-linux-arm64-musl": "1.2.18", + "@rolldown/binding-linux-ppc64-gnu": "1.2.18", + "@rolldown/binding-linux-s390x-gnu": "1.2.18", + "@rolldown/binding-linux-x64-gnu": "1.2.18", + "@rolldown/binding-linux-x64-musl": "1.2.18", + "@rolldown/binding-openharmony-arm64": "1.2.18", + "@rolldown/binding-win32-arm64-msvc": "1.2.18", + "@rolldown/binding-win32-x64-msvc": "1.2.18" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smol-toml": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", + "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.4.tgz", + "integrity": "sha512-7wjKAvVXYOJsbmUgkWbnhPD4bFpkqrUR0++XOjpGFmqDqsqV1CI93VTraeExLxpjxEqrWgFoSRZu1ihjLWfkAA==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/streamx": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-stream": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.2.tgz", + "integrity": "sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.9.0", + "bare-fs": "^4.8.2", + "fast-fifo": "^1.3.2", + "streamx": "^2.28.1" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-error": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", + "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "8.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.7.tgz", + "integrity": "sha512-oxh0dMxrayBr33ScJvvBJ2HBQ2jc1TBrXKR2NyL/MJ9WPuX19mN2DXWUmywKdyw5RFVQBo8hmtb2FMC0LHrnng==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.32", + "rolldown": "~1.2.18", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -3925,7 +6120,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -3976,8 +6171,23 @@ } } }, - "providers/modal/node_modules/vitest": { + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { @@ -4064,727 +6274,453 @@ } } }, - "providers/modal/node_modules/why-is-node-running": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "sandbox": { - "name": "@sandbox-kit/sandbox", - "version": "0.1.7", - "license": "MIT", - "dependencies": { - "@bufbuild/protobuf": "2.16.0", - "@bufbuild/protovalidate": "1.3.0", - "@sandbox-kit/contracts": "0.1.7" - }, - "devDependencies": { - "@types/node": "24.10.1", - "typescript": "5.9.3", - "vitest": "4.1.11" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.15.0" - } - }, - "sandbox/node_modules/@bufbuild/cel": { - "version": "0.6.1", - "license": "Apache-2.0", - "dependencies": { - "@bufbuild/cel-spec": "0.6.1", - "@bufbuild/re2": "0.6.1" - }, - "peerDependencies": { - "@bufbuild/protobuf": "^2.6.2" - } - }, - "sandbox/node_modules/@bufbuild/cel-spec": { - "version": "0.6.1", - "license": "Apache-2.0", - "peerDependencies": { - "@bufbuild/protobuf": "^2.6.2" - } - }, - "sandbox/node_modules/@bufbuild/protobuf": { - "version": "2.16.0", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "sandbox/node_modules/@bufbuild/protovalidate": { - "version": "1.3.0", - "license": "Apache-2.0", - "dependencies": { - "@bufbuild/cel": "0.6.1", - "@bufbuild/re2": "0.6.1" - }, - "peerDependencies": { - "@bufbuild/protobuf": "^2.8.0" - } - }, - "sandbox/node_modules/@bufbuild/re2": { - "version": "0.6.1", - "license": "MIT" - }, - "sandbox/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", + "node_modules/vitest/node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, "license": "MIT" }, - "sandbox/node_modules/@oxc-project/types": { - "version": "0.153.0", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/oxc-project" - } - }, - "sandbox/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.13", - "cpu": [ - "arm64" - ], + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "sandbox/node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/@standard-schema/spec": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/@types/chai": { - "version": "5.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "sandbox/node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.16.tgz", + "integrity": "sha512-qeNEuOi9a3qkqRf7Gfez2WL2VwlBTLCNekHBGO9kxPVUp9XuY15yfTJ9jwvOwKI0I2MhUUfZq95NSp+LK1CLQg==", "license": "MIT" }, - "sandbox/node_modules/@types/estree": { - "version": "1.0.9", - "dev": true, + "node_modules/vscode-languageserver-types": { + "version": "3.18.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.5.tgz", + "integrity": "sha512-kPd6+JQNz3HMJvjKlqUxERSlZHAntl/aGpsk7ImwIh0ewDKDcjgAEyVSYciPiOJD3ftbK9IrfiqUeTZLSbCizw==", "license": "MIT" }, - "sandbox/node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "sandbox/node_modules/@vitest/expect": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sandbox/node_modules/@vitest/mocker": { - "version": "4.1.11", - "dev": true, - "license": "MIT", + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { - "@vitest/spy": "4.1.11", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "isexe": "^2.0.0" }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "sandbox/node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" + "bin": { + "node-which": "bin/node-which" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">= 8" } }, - "sandbox/node_modules/@vitest/runner": { - "version": "4.1.11", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" + "siginfo": "^2.0.0", + "stackback": "0.0.2" }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sandbox/node_modules/@vitest/snapshot": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" + "bin": { + "why-is-node-running": "cli.js" }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sandbox/node_modules/@vitest/spy": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=8" } }, - "sandbox/node_modules/@vitest/utils": { - "version": "4.1.11", - "dev": true, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sandbox/node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "sandbox/node_modules/chai": { - "version": "6.3.0", - "dev": true, - "license": "MIT", "engines": { - "node": ">=18" - } - }, - "sandbox/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/es-module-lexer": { - "version": "2.3.2", - "dev": true, - "license": "MIT" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, - "sandbox/node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "sandbox/node_modules/expect-type": { - "version": "1.4.0", - "dev": true, - "license": "Apache-2.0", + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=12.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "sandbox/node_modules/fdir": { - "version": "6.5.0", - "dev": true, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=10.0.0" }, "peerDependencies": { - "picomatch": "^3 || ^4" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { - "picomatch": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { "optional": true } } }, - "sandbox/node_modules/fsevents": { - "version": "2.3.3", - "dev": true, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "sandbox/node_modules/lightningcss": { - "version": "1.33.0", - "dev": true, - "license": "MPL-2.0", + "peer": true, "dependencies": { - "detect-libc": "^2.0.3" + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "sandbox/node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=0.4.0" } }, - "sandbox/node_modules/magic-string": { - "version": "0.30.21", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" } }, - "sandbox/node_modules/nanoid": { - "version": "3.3.20", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", "bin": { - "nanoid": "bin/nanoid.cjs" + "yaml": "bin.mjs" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, - "sandbox/node_modules/obug": { - "version": "2.2.1", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, "engines": { - "node": ">=12.20.0" + "node": ">=12" } }, - "sandbox/node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "sandbox/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, - "license": "MIT", + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" + } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">= 14" } }, - "sandbox/node_modules/postcss": { - "version": "8.5.30", - "dev": true, + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "type": "github", + "url": "https://github.com/sponsors/feross" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "patreon", + "url": "https://www.patreon.com/feross" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "consulting", + "url": "https://feross.org/support" } ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.20", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.2" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "sandbox/node_modules/rolldown": { - "version": "1.2.13", - "dev": true, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.153.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.13", - "@rolldown/binding-android-arm64": "1.2.13", - "@rolldown/binding-darwin-arm64": "1.2.13", - "@rolldown/binding-darwin-x64": "1.2.13", - "@rolldown/binding-freebsd-x64": "1.2.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", - "@rolldown/binding-linux-arm64-gnu": "1.2.13", - "@rolldown/binding-linux-arm64-musl": "1.2.13", - "@rolldown/binding-linux-ppc64-gnu": "1.2.13", - "@rolldown/binding-linux-s390x-gnu": "1.2.13", - "@rolldown/binding-linux-x64-gnu": "1.2.13", - "@rolldown/binding-linux-x64-musl": "1.2.13", - "@rolldown/binding-openharmony-arm64": "1.2.13", - "@rolldown/binding-win32-arm64-msvc": "1.2.13", - "@rolldown/binding-win32-x64-msvc": "1.2.13" - } - }, - "sandbox/node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "sandbox/node_modules/source-map-js": { - "version": "1.2.2", - "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" } }, - "sandbox/node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/std-env": { - "version": "4.3.0", + "providers/blaxel/node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } }, - "sandbox/node_modules/tinybench": { - "version": "2.9.0", + "providers/blaxel/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, - "sandbox/node_modules/tinyexec": { - "version": "1.3.1", - "dev": true, + "providers/daytona": { + "name": "@sandbox-kit/daytona", + "version": "0.1.7", "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@daytona/sdk": "0.225.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, "engines": { - "node": ">=18" + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" } }, - "sandbox/node_modules/tinyglobby": { - "version": "0.2.17", + "providers/daytona/node_modules/@types/node": { + "version": "24.10.1", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "undici-types": "~7.16.0" } }, - "sandbox/node_modules/tinyrainbow": { - "version": "3.2.0", + "providers/daytona/node_modules/undici-types": { + "version": "7.16.0", "dev": true, + "license": "MIT" + }, + "providers/modal": { + "name": "@sandbox-kit/modal", + "version": "0.1.7", "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7", + "modal": "0.11.1" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, "engines": { - "node": ">=14.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" } }, - "sandbox/node_modules/typescript": { - "version": "5.9.3", + "providers/modal/node_modules/@types/node": { + "version": "24.10.1", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" } }, - "sandbox/node_modules/undici-types": { + "providers/modal/node_modules/undici-types": { "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, - "sandbox/node_modules/vite": { - "version": "8.3.4", - "dev": true, + "sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", "license": "MIT", "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.7", - "postcss": "^8.5.29", - "rolldown": "~1.2.12", - "tinyglobby": "^0.2.17" + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" }, - "bin": { - "vite": "bin/vite.js" + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "sandbox/node_modules/@bufbuild/cel": { + "version": "0.6.1", + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/cel-spec": "0.6.1", + "@bufbuild/re2": "0.6.1" }, "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "@bufbuild/protobuf": "^2.6.2" } }, - "sandbox/node_modules/vitest": { - "version": "4.1.11", - "dev": true, - "license": "MIT", + "sandbox/node_modules/@bufbuild/cel-spec": { + "version": "0.6.1", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.6.2" + } + }, + "sandbox/node_modules/@bufbuild/protovalidate": { + "version": "1.3.0", + "license": "Apache-2.0", "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@bufbuild/cel": "0.6.1", + "@bufbuild/re2": "0.6.1" }, "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } + "@bufbuild/protobuf": "^2.8.0" } }, - "sandbox/node_modules/why-is-node-running": { - "version": "2.3.0", + "sandbox/node_modules/@bufbuild/re2": { + "version": "0.6.1", + "license": "MIT" + }, + "sandbox/node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" + "undici-types": "~7.16.0" } + }, + "sandbox/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" } } } diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 4395d56e..ccd67e28 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -5,6 +5,7 @@ "workspaces": [ "contracts", "sandbox", + "providers/blaxel", "providers/daytona", "providers/modal" ] diff --git a/sdks/typescript/providers/blaxel/.gitignore b/sdks/typescript/providers/blaxel/.gitignore new file mode 100644 index 00000000..1eae0cf6 --- /dev/null +++ b/sdks/typescript/providers/blaxel/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/sdks/typescript/providers/blaxel/.nvmrc b/sdks/typescript/providers/blaxel/.nvmrc new file mode 100644 index 00000000..df6ae337 --- /dev/null +++ b/sdks/typescript/providers/blaxel/.nvmrc @@ -0,0 +1 @@ +24.21.0 diff --git a/sdks/typescript/providers/blaxel/LICENSE b/sdks/typescript/providers/blaxel/LICENSE new file mode 100644 index 00000000..d843376c --- /dev/null +++ b/sdks/typescript/providers/blaxel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 sandbox-kit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md new file mode 100644 index 00000000..1768c3d6 --- /dev/null +++ b/sdks/typescript/providers/blaxel/README.md @@ -0,0 +1,153 @@ +# `@sandbox-kit/blaxel` + +Blaxel provider for [`@sandbox-kit/sandbox`](https://www.npmjs.com/package/@sandbox-kit/sandbox). +Use the shared client interface; this package supplies the `blaxel()` provider. + +## Install + +```sh +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel +``` + +Requires Node.js 20, 22, or 24.15+, a Blaxel API key, and a workspace name. + +## Quick start + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION ?? "us-pdx-1", + timeout: 120_000, +}); + +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + lifetime: { maximumLifetime: 24 * 60 * 60_000 }, +}); +console.log(sandbox.id); // same as create name; use with get / BL_SANDBOX_NAME +``` + +Closing the client releases local resources only. Reclaim sandboxes with TTL / +idle policies or `delete`. + +## Configuration + +| Setting | Notes | +| -------------- | ------------------------------------------------------- | +| API key | `auth: { apiKey: { key } }` | +| Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | +| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | +| Region | `region` — required for create | +| Endpoint | Not supported | + +Token-pair, bearer, and OAuth are rejected. Only `scope.workspace` is accepted +on scope. + +In TypeScript, use one Blaxel configuration per process. A second client with +different credentials or workspace is rejected before it can replace the +process-wide SDK configuration. + +## What you can do + +### Create + +You need a valid `name` (required; blank or whitespace-only is rejected; +lowercase alphanumeric with single hyphens, max 49 characters), an image, and +`region`. You can also set `memoryMib`, labels, +environment variables, ports, outbound domains, max-age TTL (≥5 minutes), and +idle-delete (≥1 minute). You cannot disable idle terminate. Runtime and +isolation are not supported. `info().labels` is set only when Blaxel reports +labels. If you omit memory, Blaxel applies its default. CPU and disk overrides +are rejected — Blaxel derives CPU from memory. Set `memoryMib` for memory size; +Kit converts `memoryMib` to Blaxel's decimal MB value, rounding up so a request +is never under-provisioned. Reported native MB values are converted back to +whole MiB. + +Set client or create `timeout` for how long create may wait. With a 1–50s +timeout, a timed-out create frees the name. Ready/Started/Submitted wait is not +supported (`waitFor` only Default or Scheduled). Ports 80, 443, and 8080 are +reserved. + +If Blaxel’s edge returns HTTP 504 during create, TypeScript may keep waiting +until the sandbox is ready or your create timeout ends. Go returns a timeout as +soon as that response arrives. A Go timeout does not always mean the sandbox +was never created — call `get` by name before recreating. +Default port transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is +not supported. Volumes are not part of create. + +If the name already exists, you get `AlreadyExists`. + +### Lifecycle + +You can `get` / `refresh` (by name), `delete`, and `close`. `close` does not +delete the sandbox. Start, stop, pause, archive, terminate, and attach are not +available. + +After a successful `delete`, `state` is `deleted` and `terminal` is true. +Omit `wait` to return as soon as Blaxel accepts the delete — recreating the +same name too soon may return `Conflict`. Pass `wait: true` to wait until +removal finishes before the call returns. + +On `get` / `refresh` / `info`, an idle sandbox shows as `paused`; an active one +shows as `running`. + +### Filesystem + +You get the usual file APIs: `readFile`, `writeFile`, `copyFromLocal`, +`copyToLocal`, `makeDirectory`, `listDirectory`, `stat`, and `remove`. Paths +must be absolute. `parents` defaults to true; Blaxel does not support +`parents: false`. + +`stat` returns `type` (file or directory), `size`, and usually `permissions`, +`owner`, `group`, and `modifiedAt`. `name` and `path` are whatever Blaxel +reports. Missing local files for `copyFromLocal`, and local write failures from +`copyToLocal`, use field `local_path`. + +### Process + +`sandbox.process.run` waits until the command finishes. Set exactly one of +`argv` or `shell`. `argv` is sent as one quoted shell command string. A non-zero exit code is not +an error — check `exitCode`. You get both stdout and stderr. + +Omit or zero `timeout` to wait without a Blaxel process time limit (your +signal can still abort). A positive timeout must be whole seconds (`2000` is +two seconds; `1500` is rejected) and caps how long the command may run. +Aborting the signal stops waiting; the remote command may keep running. If the +wait times out, you get a timeout error. + +### Errors you will see + +| Situation | Kind | +| ------------------------------------------ | ----------------- | +| Create when the name already exists | `AlreadyExists` | +| Conflict while a delete is still finishing | `Conflict` | +| Lifetime below floor or not whole seconds | `InvalidArgument` | +| Unsupported create field (runtime, …) | `Unsupported` | + +Blaxel control-plane codes appear on `providerCode` when present. + +## Examples + +| Example | Project | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | +| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-command) | +| Process | [process examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process) | +| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/read-blaxel-file) | +| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem) | +| Retrieval | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox) | +| Full index | [TypeScript examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript) | + +## License + +MIT diff --git a/sdks/typescript/providers/blaxel/package.json b/sdks/typescript/providers/blaxel/package.json new file mode 100644 index 00000000..685e1517 --- /dev/null +++ b/sdks/typescript/providers/blaxel/package.json @@ -0,0 +1,77 @@ +{ + "name": "@sandbox-kit/blaxel", + "version": "0.1.0", + "description": "blaxel provider for sandbox kit", + "keywords": [ + "sandbox-kit", + "sandbox", + "blaxel", + "sdk", + "provider" + ], + "license": "MIT", + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sandbox-kit/kit.git", + "directory": "sdks/typescript/providers/blaxel" + }, + "homepage": "https://github.com/sandbox-kit/kit/tree/main/sdks/typescript/providers/blaxel", + "bugs": { + "url": "https://github.com/sandbox-kit/kit/issues" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "imports": { + "#mappings/*.js": { + "@sandbox-kit/source": "./src/mappings/*.ts", + "default": "./dist/mappings/*.js" + }, + "#checks/*.js": { + "@sandbox-kit/source": "./src/checks/*.ts", + "default": "./dist/checks/*.js" + }, + "#config/*.js": { + "@sandbox-kit/source": "./src/config/*.ts", + "default": "./dist/config/*.js" + }, + "#errors.js": { + "@sandbox-kit/source": "./src/errors.ts", + "default": "./dist/errors.js" + } + }, + "scripts": { + "clean": "rm -rf dist", + "build": "tsc -p tsconfig.build.json", + "prepublishOnly": "npm run clean && npm run build", + "test": "tsc -p tsconfig.test.json && vitest run" + }, + "dependencies": { + "@blaxel/core": "0.3.36", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + } +} diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts new file mode 100644 index 00000000..628d2472 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -0,0 +1,1672 @@ +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it, vi } from "vitest"; +import { + Client, + EgressMode, + ErrorKind, + FileType, + PolicyMode, + PortTransport, + SandboxState, + WaitCondition, + ConfigFieldAuthApiKeyKey, + ConfigFieldEndpoint, + ConfigFieldRegion, + ConfigFieldScopeWorkspace, + CreateFieldEnvironment, + CreateFieldLifetimeIdleTerminate, + CreateFieldLifetimeMaximumLifetime, + CreateFieldName, + CreateFieldNetworkPortsPort, + CreateFieldNetworkPortsTransport, + CreateFieldProvisioningWaitFor, + FileFieldLocalPath, + FileFieldPath, +} from "@sandbox-kit/sandbox"; +import { + blaxelCreateTimeoutSeconds, + mapClientConfig, + mapCreateRequest, + mapSandboxInfo, +} from "#mappings/mappings.js"; +import { BlaxelProvider } from "./blaxel.js"; +import { + mapAllocatedResources, + mapResources, + mapSandboxIdentity, +} from "#mappings/provider_mappings.js"; +import { captureProviderState } from "#config/provider_config.js"; +import { mapBlaxelError } from "#errors.js"; +import { defaultNativeFactory, projectHandle } from "./native.js"; +import { ResponseError, SandboxInstance } from "@blaxel/core"; + +const provider = { region: "us-pdx-1" }; + +describe("Blaxel mappings", () => { + it("maps API key authentication and workspace scope", () => { + expect( + mapClientConfig({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "my-workspace" }, + }), + ).toEqual({ + apiKey: "bl_key", + workspace: "my-workspace", + }); + }); + + it("rejects unsupported client endpoint before SDK construction", () => { + expect(() => + mapClientConfig({ endpoint: "https://unsupported.example" }), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: ConfigFieldEndpoint, + operation: "initialize", + }), + ); + }); + + it("rejects blank workspace scope", () => { + expect(() => mapClientConfig({ scope: { workspace: " " } })).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldScopeWorkspace, + operation: "initialize", + }), + ); + }); + + it("requires name and image on create", () => { + expect(() => + mapCreateRequest( + { source: { image: { reference: "blaxel/base-image:latest" } } }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldName, + }), + ); + }); + + it("requires client region on create", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + {}, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldRegion, + }), + ); + }); + + it("rejects blank auth credentials in Client construction", () => { + expect( + () => + new Client({ + provider: new BlaxelProvider(() => { + throw new Error("native SDK must not construct"); + }), + auth: { apiKey: { key: "" } }, + scope: { workspace: "ws" }, + }), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldAuthApiKeyKey, + operation: "initialize", + }), + ); + }); + + it("captures create region from client config", () => { + expect( + captureProviderState({ + scope: { workspace: "original" }, + region: "us-pdx-1", + }), + ).toEqual({ region: "us-pdx-1" }); + expect(captureProviderState({ scope: { workspace: "ws" } })).toEqual({}); + }); + + it("maps the creation surface into the native Blaxel plan", () => { + const plan = mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + network: { + outboundDomains: ["example.com"], + ports: [{ port: 3000, transport: PortTransport.Tls }], + }, + lifetime: { + maximumLifetime: 24 * 60 * 60_000, + idleTerminate: { + mode: PolicyMode.After, + after: 30 * 60_000, + }, + }, + labels: { purpose: "test" }, + environment: { NODE_ENV: "test" }, + }, + provider, + ); + expect(plan).toMatchObject({ + params: { + name: "example", + image: "blaxel/base-image:latest", + memory: 4295, + region: "us-pdx-1", + ttl: "1d", + labels: { purpose: "test" }, + envs: [{ name: "NODE_ENV", value: "test" }], + ports: [{ target: 3000, protocol: "TLS" }], + network: { allowedDomains: ["example.com"] }, + lifecycle: { + expirationPolicies: [ + { type: "ttl-idle", action: "delete", value: "30m" }, + ], + }, + }, + }); + }); + + it("maps memory without a Kit-side floor", () => { + expect( + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 128n }, + }, + provider, + ).params.memory, + ).toBe(135); + expect(mapResources({ memoryMib: 4096n })).toEqual({ memory: 4295 }); + }); + + it("accepts kit create timeout as the client deadline", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + provisioning: { timeout: 30_000 }, + }, + provider, + ), + ).not.toThrow(); + }); + + it("forwards Blaxel create timeout only for whole seconds 1..50", () => { + expect(blaxelCreateTimeoutSeconds(30_000)).toBe(30); + expect(blaxelCreateTimeoutSeconds(1_000)).toBe(1); + expect(blaxelCreateTimeoutSeconds(50_000)).toBe(50); + expect(blaxelCreateTimeoutSeconds(undefined)).toBeUndefined(); + expect(blaxelCreateTimeoutSeconds(0)).toBeUndefined(); + expect(blaxelCreateTimeoutSeconds(1_500)).toBeUndefined(); + expect(blaxelCreateTimeoutSeconds(120_000)).toBeUndefined(); + }); + + it("rejects WaitCondition.Ready", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + provisioning: { + waitFor: WaitCondition.Ready, + }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: CreateFieldProvisioningWaitFor, + }), + ); + }); + + it("rejects Blaxel-reserved ports", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { ports: [{ port: 443, transport: PortTransport.Tls }] }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldNetworkPortsPort, + }), + ); + }); + + it("rejects lifetime durations that are not whole seconds", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { maximumLifetime: 1_500 }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldLifetimeMaximumLifetime, + message: + "sandbox-kit blaxel: duration must be a positive whole number of seconds", + }), + ); + }); + + it("rejects lifetime durations below the Blaxel floor", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { maximumLifetime: 4 * 60_000 }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldLifetimeMaximumLifetime, + message: "sandbox-kit blaxel: duration must be at least 5m", + }), + ); + }); + + it("rejects disabling idle terminate", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { idleTerminate: { mode: PolicyMode.Disabled } }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: CreateFieldLifetimeIdleTerminate, + }), + ); + }); + + it("classifies native ResponseError status codes", () => { + const native = new ResponseError( + new Response(null, { status: 404, statusText: "Not Found" }), + { error: { code: "WORKLOAD_NOT_FOUND", message: "missing" } }, + new Error("missing"), + ); + expect(mapBlaxelError(native, "refresh")).toMatchObject({ + kind: ErrorKind.NotFound, + provider: "blaxel", + operation: "refresh", + field: "", + cause: native, + }); + expect(mapBlaxelError(native, "stat")).toMatchObject({ + kind: ErrorKind.NotFound, + operation: "stat", + field: FileFieldPath, + cause: native, + }); + }); + + it("classifies POLICY_VIOLATION as invalid argument", () => { + const native = new ResponseError( + new Response(null, { status: 403, statusText: "Forbidden" }), + { + error: { + code: "POLICY_VIOLATION", + message: "policy", + status: 403, + }, + }, + new Error("policy"), + ); + expect(mapBlaxelError(native, "create")).toMatchObject({ + kind: ErrorKind.InvalidArgument, + provider: "blaxel", + operation: "create", + statusCode: 403, + providerCode: "POLICY_VIOLATION", + cause: native, + }); + }); + + it("classifies bare HTTP 409 as already exists", () => { + const native = new ResponseError( + new Response(null, { status: 409, statusText: "Conflict" }), + { error: { message: "conflict" } }, + new Error("conflict"), + ); + expect(mapBlaxelError(native, "create")).toMatchObject({ + kind: ErrorKind.AlreadyExists, + provider: "blaxel", + operation: "create", + statusCode: 409, + cause: native, + }); + }); + + it("classifies bare HTTP 409 outside create as conflict", () => { + const native = new ResponseError( + new Response(null, { status: 409, statusText: "Conflict" }), + { error: { message: "conflict" } }, + new Error("conflict"), + ); + expect(mapBlaxelError(native, "delete")).toMatchObject({ + kind: ErrorKind.Conflict, + provider: "blaxel", + operation: "delete", + statusCode: 409, + cause: native, + }); + }); + + it("keeps SANDBOX_DELETION_IN_PROGRESS as conflict", () => { + const native = new ResponseError( + new Response(null, { status: 409, statusText: "Conflict" }), + { + error: { + code: "SANDBOX_DELETION_IN_PROGRESS", + message: "deleting", + status: 409, + }, + }, + new Error("deleting"), + ); + expect(mapBlaxelError(native, "delete")).toMatchObject({ + kind: ErrorKind.Conflict, + providerCode: "SANDBOX_DELETION_IN_PROGRESS", + statusCode: 409, + cause: native, + }); + }); + + it("classifies edge gateway 504 as timeout", () => { + const native = new ResponseError( + new Response(null, { status: 504, statusText: "Gateway Timeout" }), + { error: { message: "gateway timeout" } }, + new Error("gateway timeout"), + ); + expect(mapBlaxelError(native, "run")).toMatchObject({ + kind: ErrorKind.Timeout, + provider: "blaxel", + operation: "run", + cause: native, + }); + const waited = new Error( + "Process did not finish in time (proc); it may still be running", + { cause: native }, + ); + expect(mapBlaxelError(waited, "run")).toMatchObject({ + kind: ErrorKind.Timeout, + operation: "run", + statusCode: 504, + cause: waited, + }); + }); + + it("prefers WORKLOAD_FAILED over bare gateway 503 unavailable", () => { + const native = new ResponseError( + new Response(null, { status: 503, statusText: "Service Unavailable" }), + { + error: { + code: "WORKLOAD_FAILED", + message: "failed", + status: 503, + }, + }, + new Error("failed"), + ); + expect(mapBlaxelError(native, "refresh")).toMatchObject({ + kind: ErrorKind.Internal, + providerCode: "WORKLOAD_FAILED", + statusCode: 503, + cause: native, + }); + }); + + it("classifies create-plane codes from @blaxel/core BlaxelErrorCode", () => { + const deployment = new ResponseError( + new Response(null, { status: 500, statusText: "Internal Server Error" }), + { + error: { + code: "DEPLOYMENT_FAILED", + message: "deploy", + status_code: 500, + }, + }, + new Error("deploy"), + ); + expect(mapBlaxelError(deployment, "create")).toMatchObject({ + kind: ErrorKind.Internal, + providerCode: "DEPLOYMENT_FAILED", + }); + const mismatch = new ResponseError( + new Response(null, { status: 400, statusText: "Bad Request" }), + { + error: { + code: "REGION_MISMATCH", + message: "region", + status_code: 400, + }, + }, + new Error("region"), + ); + expect(mapBlaxelError(mismatch, "create")).toMatchObject({ + kind: ErrorKind.InvalidArgument, + providerCode: "REGION_MISMATCH", + }); + const unsupported = new ResponseError( + new Response(null, { status: 400, statusText: "Bad Request" }), + { + error: { + code: "UPLOAD_NOT_SUPPORTED", + message: "upload", + status_code: 400, + }, + }, + new Error("upload"), + ); + expect(mapBlaxelError(unsupported, "create")).toMatchObject({ + kind: ErrorKind.Unsupported, + providerCode: "UPLOAD_NOT_SUPPORTED", + }); + }); + + it("reads provider codes from error.cause", () => { + const native = new ResponseError( + new Response(null, { status: 403, statusText: "Forbidden" }), + { + error: { + code: "POLICY_VIOLATION", + message: "policy", + status: 403, + }, + }, + new Error("policy"), + ); + const wrapped = new Error("wrapped", { cause: native }); + expect(mapBlaxelError(wrapped, "create")).toMatchObject({ + kind: ErrorKind.InvalidArgument, + providerCode: "POLICY_VIOLATION", + statusCode: 403, + cause: wrapped, + }); + }); + + it("maps X-Blaxel-Source and body origin onto providerSource", () => { + const fromHeader = new ResponseError( + new Response(null, { + status: 500, + statusText: "Internal Server Error", + headers: { "X-Blaxel-Source": "platform" }, + }), + { error: { code: "INTERNAL_ERROR", message: "boom", origin: "ignored" } }, + new Error("boom"), + ); + expect(mapBlaxelError(fromHeader, "refresh")).toMatchObject({ + providerSource: "platform", + providerCode: "INTERNAL_ERROR", + }); + const fromOrigin = new ResponseError( + new Response(null, { status: 403, statusText: "Forbidden" }), + { + error: { + code: "POLICY_VIOLATION", + message: "policy", + origin: "gateway", + status: 403, + }, + }, + new Error("policy"), + ); + expect(mapBlaxelError(fromOrigin, "create")).toMatchObject({ + providerSource: "gateway", + providerCode: "POLICY_VIOLATION", + }); + }); + + it("accepts default egress with outbound domains", () => { + const plan = mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { + egress: EgressMode.Default, + outboundDomains: ["example.com"], + }, + }, + provider, + ); + expect(plan.params.network).toEqual({ + allowedDomains: ["example.com"], + }); + }); + + it("maps allocated memory onto SandboxInfo resources", () => { + expect( + mapAllocatedResources({ + metadata: { name: "demo" }, + spec: { runtime: { memory: 2048 } }, + }), + ).toEqual({ memoryMib: 1953n }); + expect( + mapSandboxInfo({ + metadata: { name: "demo" }, + status: "DEPLOYED", + spec: { region: "us-pdx-1", runtime: { memory: 4096 } }, + }), + ).toMatchObject({ + id: "demo", + resources: { memoryMib: 3906n }, + }); + }); + + it("omits labels when Blaxel does not return them", () => { + const info = mapSandboxInfo({ + metadata: { name: "demo" }, + status: "DEPLOYED", + spec: { region: "us-pdx-1" }, + }); + expect(info.labels).toBeUndefined(); + expect(info.createdAt).toBeUndefined(); + expect(info.providerMetadata).toBeUndefined(); + }); + + it("keeps native labels and does not invent from create request", () => { + expect( + mapSandboxInfo({ + metadata: { name: "demo", labels: { team: "kit" } }, + status: "DEPLOYED", + spec: { region: "us-pdx-1" }, + }).labels, + ).toEqual({ team: "kit" }); + expect( + mapSandboxInfo({ + metadata: { name: "demo" }, + status: "DEPLOYED", + spec: { region: "us-pdx-1" }, + }).labels, + ).toBeUndefined(); + }); + + it("marks DELETED and TERMINATED refresh as terminal", () => { + for (const status of ["DELETED", "TERMINATED", "DEACTIVATED"] as const) { + expect( + mapSandboxInfo({ + metadata: { name: "gone" }, + status, + }), + ).toMatchObject({ + id: "gone", + state: SandboxState.Deleted, + terminal: true, + providerState: status, + }); + } + }); + + it("omits protocol for default port transport", () => { + const plan = mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { ports: [{ port: 3000 }] }, + }, + provider, + ); + expect(plan.params.ports).toEqual([{ target: 3000 }]); + }); + + it("rejects HTTP/2 TLS port transport", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { + ports: [{ port: 3000, transport: PortTransport.Http2Tls }], + }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: CreateFieldNetworkPortsTransport, + }), + ); + }); + + it("applies environment checks before mapping", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + environment: { "NOT VALID": "value" }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldEnvironment, + }), + ); + }); + + it("maps public DEPLOYED status to running", () => { + expect( + mapSandboxIdentity({ + metadata: { name: "demo" }, + status: "DEPLOYED", + spec: { region: "us-pdx-1" }, + }), + ).toMatchObject({ + id: "demo", + providerState: "DEPLOYED", + state: SandboxState.Running, + region: "us-pdx-1", + }); + }); + + it("projectHandle uses public SDK properties only", () => { + const instance = new SandboxInstance({ + metadata: { name: "demo" }, + status: "DEPLOYED", + spec: { region: "us-pdx-1" }, + } as never); + const handle = projectHandle(instance); + expect(handle).toMatchObject({ + metadata: { name: "demo" }, + status: "DEPLOYED", + spec: { region: "us-pdx-1" }, + }); + expect("state" in handle).toBe(false); + }); + + it("rejects a conflicting process-wide Blaxel configuration", () => { + defaultNativeFactory({ apiKey: "first", workspace: "workspace-a" }); + expect(() => + defaultNativeFactory({ workspace: "workspace-a", apiKey: "first" }), + ).not.toThrow(); + + expect(() => + defaultNativeFactory({ apiKey: "second", workspace: "workspace-b" }), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.FailedPrecondition, + operation: "initialize", + }), + ); + }); +}); + +describe("Blaxel provider orchestration", () => { + it("creates with SandboxInstance.create and never createIfNotExists", async () => { + let created: unknown; + let createOptions: unknown; + let configured: unknown; + const nativeSandbox = { + metadata: { name: "example" }, + status: "DEPLOYED", + state: "RUNNING", + spec: { region: "us-pdx-1" }, + fs: {} as never, + process: {} as never, + }; + const client = new Client({ + provider: new BlaxelProvider(() => ({ + configure(config) { + configured = config; + }, + async create(params, options) { + created = params; + createOptions = options; + return nativeSandbox; + }, + async get() { + throw new Error("unexpected get"); + }, + async delete() { + throw new Error("unexpected delete"); + }, + })), + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + region: "us-pdx-1", + }); + + const sandbox = await client.create({ + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 2048n }, + provisioning: { timeout: 30_000 }, + }); + await client.close(); + + expect(configured).toEqual({ apiKey: "bl_key", workspace: "ws" }); + expect(created).toMatchObject({ + name: "example", + image: "blaxel/base-image:latest", + memory: 2148, + region: "us-pdx-1", + }); + expect(createOptions).toEqual({ timeout: 30 }); + expect(sandbox.id).toBe("example"); + expect(sandbox.info().name).toBe("example"); + }); + + it("looks up sandboxes by name for get/refresh", async () => { + let lookedUp: string | undefined; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get(name) { + lookedUp = name; + return { + metadata: { name }, + status: "DEPLOYED", + state: "RUNNING", + fs: {} as never, + process: {} as never, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + region: "us-pdx-1", + }); + if (backend.refresh === undefined) throw new Error("refresh is missing"); + const info = await backend.refresh("existing", {}); + expect(lookedUp).toBe("existing"); + expect(info).toMatchObject({ id: "existing", provider: "blaxel" }); + }); + + it("unifies a successful delete as Deleted", async () => { + const deleted: string[] = []; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + throw new Error("unexpected get"); + }, + async delete(name) { + deleted.push(name); + // Wire may still say DELETING; Kit reports Deleted. + return { metadata: { name }, status: "DELETING" }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.delete === undefined) throw new Error("delete is missing"); + const info = await backend.delete("doomed", {}, {}); + expect(deleted).toEqual(["doomed"]); + expect(info).toMatchObject({ + id: "doomed", + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }); + }); + + it("does not poll get when wait is omitted", async () => { + let gets = 0; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + gets += 1; + throw new Error("unexpected get"); + }, + async delete(name) { + return { metadata: { name }, status: "DELETING" }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.delete === undefined) throw new Error("delete is missing"); + await backend.delete("doomed", { wait: false }, {}); + expect(gets).toBe(0); + }); + + it("waits until get returns NotFound when wait is true", async () => { + vi.useFakeTimers(); + try { + let gets = 0; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get(name) { + gets += 1; + if (gets < 2) { + return { + metadata: { name }, + status: "DELETING", + fs: {} as never, + process: {} as never, + }; + } + throw new ResponseError( + new Response(null, { status: 404, statusText: "Not Found" }), + undefined, + undefined, + ); + }, + async delete(name) { + return { metadata: { name }, status: "DELETING" }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.delete === undefined) throw new Error("delete is missing"); + const pending = backend.delete("doomed", { wait: true }, {}); + await vi.advanceTimersByTimeAsync(1_000); + await vi.advanceTimersByTimeAsync(1_000); + const info = await pending; + expect(gets).toBe(2); + expect(info).toMatchObject({ + id: "doomed", + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }); + } finally { + vi.useRealTimers(); + } + }); + + it("stops waiting for delete when the signal aborts", async () => { + vi.useFakeTimers(); + try { + const controller = new AbortController(); + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get(name) { + return { + metadata: { name }, + status: "DELETING", + fs: {} as never, + process: {} as never, + }; + }, + async delete(name) { + return { metadata: { name }, status: "DELETING" }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.delete === undefined) throw new Error("delete is missing"); + const pending = backend.delete( + "doomed", + { wait: true }, + { + signal: controller.signal, + }, + ); + const expectation = expect(pending).rejects.toMatchObject({ + kind: ErrorKind.Canceled, + operation: "delete", + }); + await vi.advanceTimersByTimeAsync(500); + controller.abort(); + await expectation; + } finally { + vi.useRealTimers(); + } + }); + + it("does not create a sandbox after the signal aborts during mapping", async () => { + const controller = new AbortController(); + let created = false; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + created = true; + throw new Error("native create"); + }, + async get() { + throw new Error("unexpected get"); + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + controller.abort(); + if (backend.create === undefined) throw new Error("create is missing"); + await expect( + backend.create( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { signal: controller.signal }, + ), + ).rejects.toMatchObject({ + kind: ErrorKind.Canceled, + operation: "create", + }); + expect(created).toBe(false); + }); + + it("deletes a sandbox created after the caller aborts", async () => { + const controller = new AbortController(); + let resolveCreate: ((value: { + metadata: { name: string }; + fs: never; + process: never; + }) => void) | undefined; + const created = new Promise<{ + metadata: { name: string }; + fs: never; + process: never; + }>((resolve) => { + resolveCreate = resolve; + }); + const deleted: string[] = []; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + return created; + }, + async get() { + throw new Error("unexpected get"); + }, + async delete(name) { + deleted.push(name); + return { metadata: { name } }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + region: "us-pdx-1", + }); + if (backend.create === undefined) throw new Error("create is missing"); + + const pending = backend.create( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { signal: controller.signal }, + ); + controller.abort(); + resolveCreate?.({ metadata: { name: "example" }, fs: {} as never, process: {} as never }); + + await expect(pending).rejects.toMatchObject({ + kind: ErrorKind.Canceled, + operation: "create", + }); + expect(deleted).toEqual(["example"]); + }); + + it("rejects a missing or directory local file before Blaxel is called", async () => { + let lookedUp = false; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + lookedUp = true; + throw new Error("native get must not run"); + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + const copyFromLocal = backend.copyFromLocal; + const copyToLocal = backend.copyToLocal; + if (!copyFromLocal || !copyToLocal) + throw new Error("copy methods are missing"); + await expect( + copyFromLocal.call( + backend, + "sandbox-1", + join(tmpdir(), "kit-missing-source.txt"), + "/tmp/note.txt", + {}, + {}, + ), + ).rejects.toMatchObject({ + kind: ErrorKind.NotFound, + field: FileFieldLocalPath, + operation: "copy_from_local", + }); + await expect( + copyFromLocal.call( + backend, + "sandbox-1", + tmpdir(), + "/tmp/note.txt", + {}, + {}, + ), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidArgument, + field: FileFieldLocalPath, + operation: "copy_from_local", + }); + await expect( + copyToLocal.call(backend, "sandbox-1", "/tmp/note.txt", tmpdir(), {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidArgument, + field: FileFieldLocalPath, + operation: "copy_to_local", + }); + expect(lookedUp).toBe(false); + }); + + it("starts without waitForCompletion and waits when still running", async () => { + const seen: Array<{ + command: string; + waitForCompletion?: boolean; + timeout?: number; + workingDir?: string; + env?: Record; + }> = []; + const waits: Array<{ + identifier: string; + maxWait?: number; + signal?: AbortSignal; + }> = []; + const signal = new AbortController().signal; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: {} as never, + process: { + async exec(request) { + const entry: (typeof seen)[number] = { + command: request.command, + }; + if (request.waitForCompletion !== undefined) + entry.waitForCompletion = request.waitForCompletion; + if (request.timeout !== undefined) + entry.timeout = request.timeout; + if (request.workingDir !== undefined) + entry.workingDir = request.workingDir; + if (request.env !== undefined) entry.env = request.env; + seen.push(entry); + return { + exitCode: 0, + stdout: "", + stderr: "", + status: "running", + pid: "long", + name: "proc", + }; + }, + async wait(identifier, options) { + waits.push({ + identifier, + ...(options?.maxWait === undefined + ? {} + : { maxWait: options.maxWait }), + ...(options?.signal === undefined + ? {} + : { signal: options.signal }), + }); + return { + exitCode: 7, + stdout: "out", + stderr: "err", + status: "failed", + }; + }, + }, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.run === undefined) throw new Error("run is missing"); + + const finished = await backend.run( + "sandbox-1", + { argv: ["echo", "hi"] }, + {}, + ); + await backend.run("sandbox-1", { argv: ["echo", "hi"], timeout: 0 }, {}); + await backend.run( + "sandbox-1", + { + argv: ["echo", "hi"], + timeout: 5_000, + workingDirectory: "/workspace", + environment: { MODE: "test" }, + }, + {}, + ); + await backend.run( + "sandbox-1", + { argv: ["echo", "hi"], timeout: 60_000 }, + {}, + ); + await backend.run( + "sandbox-1", + { argv: ["echo", "hi"], timeout: 61_000 }, + { signal }, + ); + await expect( + backend.run("sandbox-1", { argv: ["echo", "hi"], timeout: 1_500 }, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidArgument, + field: "timeout", + operation: "run", + }); + + expect(finished).toMatchObject({ exitCode: 7 }); + expect(finished.stdout).toEqual(new TextEncoder().encode("out")); + expect(finished.stderr).toEqual(new TextEncoder().encode("err")); + expect(seen).toEqual([ + { command: "'echo' 'hi'", waitForCompletion: false, timeout: 0 }, + { command: "'echo' 'hi'", waitForCompletion: false, timeout: 0 }, + { + command: "'echo' 'hi'", + waitForCompletion: false, + timeout: 5, + workingDir: "/workspace", + env: { MODE: "test" }, + }, + { command: "'echo' 'hi'", waitForCompletion: false, timeout: 60 }, + { command: "'echo' 'hi'", waitForCompletion: false, timeout: 61 }, + ]); + expect(waits).toEqual([ + { identifier: "long", maxWait: -1 }, + { identifier: "long", maxWait: -1 }, + { identifier: "long", maxWait: 5_000 }, + { identifier: "long", maxWait: 60_000 }, + { identifier: "long", maxWait: 61_000, signal }, + ]); + }); + + it("returns a finished process without waiting", async () => { + const backend = runBackend({ + async exec() { + return { + status: "killed", + exitCode: 9, + stdout: "partial", + stderr: "stopped", + }; + }, + async wait() { + throw new Error("wait must not run"); + }, + }); + const result = await backend.run!( + "sandbox-1", + { argv: ["sleep", "1"] }, + {}, + ); + expect(result.exitCode).toBe(9); + expect(result.stdout).toEqual(new TextEncoder().encode("partial")); + expect(result.stderr).toEqual(new TextEncoder().encode("stopped")); + }); + + it("waits when start still returns running", async () => { + const waits: string[] = []; + const backend = runBackend({ + async exec(request) { + expect(request.waitForCompletion).toBe(false); + expect(request.timeout).toBe(5); + return { status: "running", pid: "held", stdout: "", stderr: "" }; + }, + async wait(identifier) { + waits.push(identifier); + return { + status: "completed", + exitCode: 0, + stdout: "late", + stderr: "", + }; + }, + }); + const result = await backend.run!( + "sandbox-1", + { argv: ["echo", "hi"], timeout: 5_000 }, + {}, + ); + expect(waits).toEqual(["held"]); + expect(result.stdout).toEqual(new TextEncoder().encode("late")); + }); + + it("rejects a running process with no identifier", async () => { + const backend = runBackend({ + async exec() { + return { status: "running", exitCode: 0, stdout: "", stderr: "" }; + }, + async wait() { + throw new Error("wait must not run"); + }, + }); + await expect( + backend.run!("sandbox-1", { argv: ["echo", "hi"] }, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "run", + }); + }); + + it("maps an aborted process wait and a wait deadline", async () => { + const controller = new AbortController(); + const aborted = runBackend({ + async exec() { + return { status: "running", pid: "long", stdout: "", stderr: "" }; + }, + async wait(_identifier, options) { + controller.abort(); + options?.signal?.throwIfAborted(); + return { status: "completed", exitCode: 0, stdout: "", stderr: "" }; + }, + }); + await expect( + aborted.run!( + "sandbox-1", + { argv: ["sleep", "30"], timeout: 0 }, + { signal: controller.signal }, + ), + ).rejects.toMatchObject({ kind: ErrorKind.Canceled, operation: "run" }); + + const deadline = runBackend({ + async exec() { + return { status: "running", pid: "long", stdout: "", stderr: "" }; + }, + async wait() { + throw new Error( + "Process did not finish in time (long); it may still be running", + ); + }, + }); + await expect( + deadline.run!( + "sandbox-1", + { argv: ["sleep", "90"], timeout: 61_000 }, + {}, + ), + ).rejects.toMatchObject({ kind: ErrorKind.Timeout, operation: "run" }); + }); +}); + +describe("Blaxel filesystem.stat", () => { + it("does not label a missing sandbox as a missing filesystem path", async () => { + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + throw new ResponseError(new Response(null, { status: 404 }), {}, {}); + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + await expect( + backend.readFile!("deleted-sandbox", "/app/readme.txt", {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.NotFound, + operation: "read_file", + field: "", + }); + }); + + it("returns directory metadata from get on the path", async () => { + const backend = fsBackend({ + async get(path) { + expect(path).toBe("/app"); + return { + name: "app", + path: "/app", + files: [], + subdirectories: [], + }; + }, + }); + await expect(backend.stat!("sandbox-1", "/app", {}, {})).resolves.toEqual({ + name: "app", + path: "/app", + type: FileType.Directory, + size: 0n, + }); + }); + + it("does not invent directory name or path when Blaxel omits them", async () => { + const backend = fsBackend({ + async get() { + return { + name: "", + path: "", + files: [], + subdirectories: [], + }; + }, + }); + await expect(backend.stat!("sandbox-1", "/app", {}, {})).resolves.toEqual({ + name: "", + path: "", + type: FileType.Directory, + size: 0n, + }); + }); + + it("returns file metadata including size from get", async () => { + const backend = fsBackend({ + async get(path) { + expect(path).toBe("/app/readme.txt"); + return { + name: "readme.txt", + path: "/app/readme.txt", + size: 12, + permissions: "0644", + owner: "user", + group: "user", + lastModified: "2024-01-02T03:04:05Z", + }; + }, + }); + await expect( + backend.stat!("sandbox-1", "/app/readme.txt", {}, {}), + ).resolves.toMatchObject({ + name: "readme.txt", + path: "/app/readme.txt", + type: FileType.File, + size: 12n, + permissions: "0644", + owner: "user", + group: "user", + }); + }); + + it("propagates get errors", async () => { + const missing = fsBackend({ + async get() { + throw new Error("boom"); + }, + }); + await expect( + missing.stat!("sandbox-1", "/missing", {}, {}), + ).rejects.toMatchObject({ kind: ErrorKind.Unknown, operation: "stat" }); + }); + + it("maps an unrecognized get body to InvalidResponse", async () => { + const backend = fsBackend({ + async get() { + return { unexpected: true } as never; + }, + }); + await expect( + backend.stat!("sandbox-1", "/app/readme.txt", {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "stat", + field: FileFieldPath, + }); + }); + + it("rejects list entries with empty name and path", async () => { + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: { + async ls() { + return { + files: [{ name: "", path: "", size: 0 }], + subdirectories: [], + }; + }, + } as never, + process: {} as never, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + await expect( + backend.listDirectory!("sandbox-1", "/app", {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "list_directory", + field: FileFieldPath, + }); + }); + + it("maps Node path failures from copyToLocal to local_path", async () => { + const local = join(tmpdir(), "kit-blaxel-copy-fail.txt"); + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: { + async download() { + const err = new Error( + `ENOENT: no such file or directory, open '${local}'`, + ) as NodeJS.ErrnoException; + err.code = "ENOENT"; + err.path = local; + throw err; + }, + } as never, + process: {} as never, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + await expect( + backend.copyToLocal!("sandbox-1", "/tmp/note.txt", local, {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.NotFound, + field: FileFieldLocalPath, + operation: "copy_to_local", + }); + }); + + it("does not tag non-destination Node errors as local_path on copyToLocal", async () => { + const local = join(tmpdir(), "kit-blaxel-copy-ok-dest.txt"); + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: { + async download() { + const err = new Error( + "ENOENT: no such file or directory, open '/other/path'", + ) as NodeJS.ErrnoException; + err.code = "ENOENT"; + err.path = "/other/path"; + throw err; + }, + } as never, + process: {} as never, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + await expect( + backend.copyToLocal!("sandbox-1", "/tmp/note.txt", local, {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.Unknown, + operation: "copy_to_local", + }); + }); +}); + +function fsBackend(fs: { + get(path: string): Promise< + | { + name: string; + path: string; + files: Array<{ + name: string; + path: string; + size: number; + permissions?: string; + owner?: string; + group?: string; + lastModified?: string; + }>; + subdirectories: Array<{ name: string; path: string }>; + } + | { + name: string; + path: string; + size: number; + permissions?: string; + owner?: string; + group?: string; + lastModified?: string; + } + >; +}) { + return new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: fs as never, + process: {} as never, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); +} + +function runBackend(process: { + exec(request: { + command: string; + waitForCompletion?: boolean; + timeout?: number; + }): Promise<{ + pid?: string; + status?: string; + exitCode?: number; + stdout?: string; + stderr?: string; + }>; + wait( + identifier: string, + options?: { signal?: AbortSignal }, + ): Promise<{ + exitCode?: number; + stdout?: string; + stderr?: string; + status?: string; + }>; +}) { + return new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: {} as never, + process, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); +} diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts new file mode 100644 index 00000000..4372e8f9 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -0,0 +1,441 @@ +import { + ErrorKind, + FileFieldParents, + SandboxState, + type FileInfo, + type OperationOptions, + type Provider, + type SandboxInfo, +} from "@sandbox-kit/sandbox"; +import type { + Backend, + Config, + CreateOptions, + CreateResult, + DeleteOptions, + ExecOptions, + ExecResult, + FileCopyOptions, + FileReadOptions, + FileWriteOptions, + ListDirectoryOptions, + MakeDirectoryOptions, + RemoveOptions, + StatOptions, +} from "@sandbox-kit/sandbox"; +import { + blaxelCreateTimeoutSeconds, + mapClientConfig, + mapCreateRequest, + mapSandboxInfo, +} from "#mappings/mappings.js"; +import { mapBlaxelError, providerError } from "#errors.js"; +import { BackendBase, ProviderBase } from "./provider.js"; +import { + captureProviderState, + type ProviderState, +} from "#config/provider_config.js"; +import { blaxelStat, directoryInfo, fileInfo } from "./filesystem.js"; +import { + ensureLocalParent, + mapCallerPathError, + readLocalFile, + requireLocalDestination, +} from "./local.js"; +import { + defaultNativeFactory, + type NativeBlaxel, + type NativeFactory, + type NativeSandboxHandle, +} from "./native.js"; +import { + execTimeoutSeconds, + finishProcess, + processRequest, +} from "./process.js"; + +/** @internal Exported for adapter boundary tests, but not from the package root. */ +export class BlaxelProvider extends ProviderBase { + constructor( + private readonly nativeFactory: NativeFactory = defaultNativeFactory, + ) { + super(); + } + + initialize(config: Config): Backend { + try { + const nativeConfig = mapClientConfig(config); + const native = this.nativeFactory(nativeConfig); + native.configure(nativeConfig); + return new BlaxelBackend(native, captureProviderState(config)); + } catch (error) { + throw mapBlaxelError(error, "initialize"); + } + } +} + +interface WithSandboxArgs { + operation: string; + id: string; + options: OperationOptions; + body: (sandbox: NativeSandboxHandle) => Promise; +} + +class BlaxelBackend extends BackendBase implements Backend { + constructor( + private readonly native: NativeBlaxel, + private readonly config: ProviderState, + ) { + super(); + } + + async create( + request: CreateOptions, + options: OperationOptions, + ): Promise { + try { + throwIfAborted(options.signal); + const plan = mapCreateRequest(request, this.config); + throwIfAborted(options.signal); + const timeoutSeconds = blaxelCreateTimeoutSeconds( + request.provisioning?.timeout, + ); + const sandbox = await this.native.create( + plan.params, + timeoutSeconds === undefined ? undefined : { timeout: timeoutSeconds }, + ); + await deleteCreatedSandboxAfterAbort( + this.native, + sandbox, + request.name, + options.signal, + ); + return { sandbox: mapSandboxInfo(sandbox) }; + } catch (error) { + throw mapBlaxelError(error, "create"); + } + } + + async refresh(id: string, options: OperationOptions): Promise { + return this.#withSandbox({ + operation: "refresh", + id, + options, + body: async (sandbox) => mapSandboxInfo(sandbox), + }); + } + + async delete( + id: string, + request: DeleteOptions, + options: OperationOptions, + ): Promise { + try { + throwIfAborted(options.signal); + const deleted = await this.native.delete(id); + throwIfAborted(options.signal); + if (request.wait === true) { + // Blaxel has no native wait-for-delete; Kit polls until gone. + return await this.#waitUntilDeleted(id, options.signal); + } + // DELETE accepted; Blaxel may still finish asynchronously. + return { + ...mapSandboxInfo(deleted), + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }; + } catch (error) { + throw mapBlaxelError(error, "delete"); + } + } + + async readFile( + id: string, + path: string, + _request: FileReadOptions, + options: OperationOptions, + ): Promise { + return this.#withSandbox({ + operation: "read_file", + id, + options, + body: async (sandbox) => { + const blob = await sandbox.fs.readBinary(path); + return new Uint8Array(await blob.arrayBuffer()); + }, + }); + } + + async writeFile( + id: string, + path: string, + data: Uint8Array, + _request: FileWriteOptions, + options: OperationOptions, + ): Promise { + await this.#withSandbox({ + operation: "write_file", + id, + options, + body: async (sandbox) => { + await sandbox.fs.writeBinary(path, data); + }, + }); + } + + async copyFromLocal( + id: string, + localPath: string, + remotePath: string, + _request: FileCopyOptions, + options: OperationOptions, + ): Promise { + const data = await readLocalFile(localPath, "copy_from_local"); + await this.#withSandbox({ + operation: "copy_from_local", + id, + options, + body: async (sandbox) => { + await sandbox.fs.writeBinary(remotePath, data); + }, + }); + } + + async copyToLocal( + id: string, + remotePath: string, + localPath: string, + _request: FileCopyOptions, + options: OperationOptions, + ): Promise { + await requireLocalDestination(localPath, "copy_to_local"); + await ensureLocalParent(localPath, "copy_to_local"); + await this.#withSandbox({ + operation: "copy_to_local", + id, + options, + body: async (sandbox) => { + try { + await sandbox.fs.download(remotePath, localPath); + } catch (error) { + mapCallerPathError(error, "copy_to_local", localPath); + } + }, + }); + } + + async makeDirectory( + id: string, + path: string, + request: MakeDirectoryOptions, + options: OperationOptions, + ): Promise { + if (request.parents === false) { + providerError({ + kind: ErrorKind.Unsupported, + operation: "make_directory", + field: FileFieldParents, + message: "parents false is not available", + }); + } + await this.#withSandbox({ + operation: "make_directory", + id, + options, + body: async (sandbox) => { + await sandbox.fs.mkdir(path); + }, + }); + } + + async listDirectory( + id: string, + path: string, + _request: ListDirectoryOptions, + options: OperationOptions, + ): Promise { + return this.#withSandbox({ + operation: "list_directory", + id, + options, + body: async (sandbox) => { + const listing = await sandbox.fs.ls(path); + return [ + ...listing.subdirectories.map((entry) => directoryInfo(entry)), + ...listing.files.map((entry) => fileInfo(entry, "list_directory")), + ]; + }, + }); + } + + async stat( + id: string, + path: string, + _request: StatOptions, + options: OperationOptions, + ): Promise { + return this.#withSandbox({ + operation: "stat", + id, + options, + body: async (sandbox) => blaxelStat(sandbox.fs, path), + }); + } + + async remove( + id: string, + path: string, + request: RemoveOptions, + options: OperationOptions, + ): Promise { + await this.#withSandbox({ + operation: "remove", + id, + options, + body: async (sandbox) => { + await sandbox.fs.rm(path, request.recursive === true); + }, + }); + } + + async run( + id: string, + request: ExecOptions, + options: OperationOptions, + ): Promise { + return this.#withSandbox({ + operation: "run", + id, + options, + body: async (sandbox) => { + const timeout = execTimeoutSeconds(request.timeout); + const started = await sandbox.process.exec( + processRequest(request, timeout), + ); + return finishProcess({ + process: sandbox.process, + started, + timeout, + signal: options.signal, + }); + }, + }); + } + + async #withSandbox(args: WithSandboxArgs): Promise { + let sandbox: NativeSandboxHandle; + try { + throwIfAborted(args.options.signal); + sandbox = await this.native.get(args.id); + throwIfAborted(args.options.signal); + } catch (error) { + // A 404 here identifies the sandbox, not the requested filesystem path. + throw mapBlaxelError(error, args.operation, false); + } + try { + return await args.body(sandbox); + } catch (error) { + throw mapBlaxelError(error, args.operation); + } + } + + /** Poll get until NotFound or Deleted. Do not use the DELETE body as done. */ + async #waitUntilDeleted( + id: string, + signal: AbortSignal | undefined, + ): Promise { + for (;;) { + throwIfAborted(signal); + await delay(1_000, signal); + try { + const info = mapSandboxInfo(await this.native.get(id)); + if (info.state === SandboxState.Deleted) { + return { ...info, terminal: true, providerState: "TERMINATED" }; + } + } catch (error) { + const mapped = mapBlaxelError(error, "delete"); + if (mapped.kind === ErrorKind.NotFound) { + return { + id, + provider: "blaxel", + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }; + } + throw mapped; + } + } + } + + async closeSandbox(_id: string, _options: OperationOptions): Promise {} + + async close(options: OperationOptions): Promise { + throwIfAborted(options.signal); + } +} + +function throwIfAborted(signal: AbortSignal | undefined): void { + signal?.throwIfAborted(); +} + +// @blaxel/core create has no AbortSignal. If Kit's outer race has already +// returned cancellation, delete the eventually-created named sandbox so it +// does not become an orphaned resource. +async function deleteCreatedSandboxAfterAbort( + native: NativeBlaxel, + sandbox: NativeSandboxHandle, + requestedName: string | undefined, + signal: AbortSignal | undefined, +): Promise { + if (!signal?.aborted) return; + const name = sandbox.metadata?.name ?? requestedName; + if (name) { + try { + await native.delete(name); + } catch { + // Preserve the caller's cancellation; a cleanup failure cannot safely + // replace it with a successful create result. + } + } + throwIfAborted(signal); +} + +function delay(ms: number, signal: AbortSignal | undefined): Promise { + return new Promise((resolve, reject) => { + if (signal?.aborted) { + reject( + signal.reason ?? + new DOMException("The operation was aborted", "AbortError"), + ); + return; + } + const timer = setTimeout(() => { + signal?.removeEventListener("abort", onAbort); + resolve(); + }, ms); + const onAbort = () => { + clearTimeout(timer); + reject( + signal?.reason ?? + new DOMException("The operation was aborted", "AbortError"), + ); + }; + signal?.addEventListener("abort", onAbort, { once: true }); + }); +} + +/** + * Selects Blaxel as the provider for the shared Sandbox Kit client. + * + * Create needs `name`, an image source, and client `region`. Set + * `auth.apiKey` and `scope.workspace`, or omit them to use `BL_API_KEY` / + * `BL_WORKSPACE` from the environment. Set `memoryMib` for memory size; + * Blaxel documents that value as MB and Kit does not convert units. Volumes + * are not part of create. In TypeScript, one process accepts one Blaxel + * configuration; a conflicting client configuration is rejected. + */ +export function blaxel(): Provider { + return new BlaxelProvider(); +} diff --git a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts new file mode 100644 index 00000000..1f85bc96 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts @@ -0,0 +1,127 @@ +import { + ErrorKind, + CreateFieldEnvironment, + CreateFieldName, + CreateFieldNetworkOutboundDomainsEntries, + CreateFieldPlacement, + CreateFieldResourcesCpuCores, + CreateFieldResourcesCpuLimitCores, + CreateFieldResourcesDiskMib, + CreateFieldResourcesMemoryLimitMib, + CreateFieldSource, + CreateFieldSourceImage, + validDomain, + validEnvName, + type ErrorInfo, +} from "@sandbox-kit/sandbox"; +import type { CreateOptions, Resources } from "@sandbox-kit/sandbox"; +import { providerError } from "#errors.js"; + +// Blaxel Metadata.Name: lowercase alphanumeric with single hyphens, max 49. +const PROVIDER_SANDBOX_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +const PROVIDER_SANDBOX_NAME_MAX = 49; + +// validateProviderCreate rejects creation settings this provider cannot honor. +// Inventoried in specs/providers/blaxel.yaml checks when present. +export function validateProviderCreate(request: CreateOptions): void { + if (request.name === undefined || request.name.trim() === "") { + reject(ErrorKind.InvalidArgument, CreateFieldName, "name is required"); + } + if ( + request.name.length > PROVIDER_SANDBOX_NAME_MAX || + !PROVIDER_SANDBOX_NAME.test(request.name) + ) { + reject( + ErrorKind.InvalidArgument, + CreateFieldName, + "name must be lowercase alphanumeric with single hyphens, at most 49 characters", + ); + } + if ( + request.source?.image === undefined || + !request.source.image.reference?.trim() + ) { + reject( + ErrorKind.InvalidArgument, + CreateFieldSourceImage, + "an explicit image source is required", + ); + } + if ( + request.source?.snapshot !== undefined || + request.source?.warmPool !== undefined + ) { + reject( + ErrorKind.Unsupported, + CreateFieldSource, + "only an image source is supported", + ); + } + if (request.placement !== undefined) { + reject( + ErrorKind.Unsupported, + CreateFieldPlacement, + "per-request Blaxel placement is unsupported; use Config.region", + ); + } + rejectUnsupportedResources(request.resources); + for (const value of Object.keys(request.environment ?? {})) { + if (!validEnvName(value)) { + reject( + ErrorKind.InvalidArgument, + CreateFieldEnvironment, + "environment is outside supported values", + ); + } + } + for (const value of request.network?.outboundDomains ?? []) { + if (!validDomain(value)) { + reject( + ErrorKind.InvalidArgument, + CreateFieldNetworkOutboundDomainsEntries, + "network.outbound_domains.entries is outside supported values", + ); + } + } +} + +function rejectUnsupportedResources(resources: Resources | undefined): void { + if (resources === undefined) return; + const checks: ReadonlyArray<{ + set: boolean; + field: string; + message: string; + }> = [ + { + set: resources.cpuCores !== undefined, + field: CreateFieldResourcesCpuCores, + message: "Blaxel derives CPU from memory; cpu_cores is not mapped", + }, + { + set: resources.cpuLimitCores !== undefined, + field: CreateFieldResourcesCpuLimitCores, + message: "Blaxel resource limits are not mapped", + }, + { + set: resources.memoryLimitMib !== undefined, + field: CreateFieldResourcesMemoryLimitMib, + message: "Blaxel resource limits are not mapped", + }, + { + set: resources.diskMib !== undefined, + field: CreateFieldResourcesDiskMib, + message: "Blaxel disk size is not mapped as MiB", + }, + ]; + for (const check of checks) { + if (check.set) reject(ErrorKind.Unsupported, check.field, check.message); + } +} + +function reject( + kind: ErrorInfo["kind"], + field: string, + message: string, +): never { + providerError({ kind, operation: "create", field, message }); +} diff --git a/sdks/typescript/providers/blaxel/src/config/provider_config.ts b/sdks/typescript/providers/blaxel/src/config/provider_config.ts new file mode 100644 index 00000000..55907a70 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/config/provider_config.ts @@ -0,0 +1,70 @@ +// Client configuration composition for the native Blaxel settings object. +// Kept in sync with specs/providers/blaxel.yaml client: rules when present. + +import type { Config as NativeClientConfig } from "@blaxel/core"; +import { + ErrorKind, + type Config, + ConfigFieldAuth, + ConfigFieldEndpoint, + ConfigFieldScopeAppName, + ConfigFieldScopeEnvironment, + ConfigFieldScopeOrganizationId, + ConfigFieldScopeProjectId, +} from "@sandbox-kit/sandbox"; +import { + mapAPIKeyCredentials, + mapClientScope, +} from "#mappings/provider_mappings.js"; +import { providerError } from "#errors.js"; + +export function mapGeneratedClientConfig(config: Config): NativeClientConfig { + const source = config; + const target: NativeClientConfig = {}; + if (source.endpoint !== undefined) + unsupported( + ConfigFieldEndpoint, + "Endpoint cannot be configured through this SDK's public client constructor", + ); + if (source.scope !== undefined) { + Object.assign(target, mapClientScope(source.scope, "initialize")); + for (const [set, field] of [ + [source.scope.appName !== undefined, ConfigFieldScopeAppName], + [source.scope.environment !== undefined, ConfigFieldScopeEnvironment], + [ + source.scope.organizationId !== undefined, + ConfigFieldScopeOrganizationId, + ], + [source.scope.projectId !== undefined, ConfigFieldScopeProjectId], + ] as const) { + if (set) unsupported(field, "unsupported client configuration field"); + } + } + if (source.auth?.apiKey !== undefined) { + Object.assign( + target, + mapAPIKeyCredentials(source.auth.apiKey, "initialize"), + ); + } else if (source.auth !== undefined) { + unsupported(ConfigFieldAuth, "unsupported authentication mode"); + } + return target; +} + +/** Retained create inputs. Scope is applied at initialize; create only needs region. */ +export interface ProviderState { + readonly region?: string; +} + +export function captureProviderState(config: Config): ProviderState { + return config.region === undefined ? {} : { region: config.region }; +} + +function unsupported(field: string, message: string): never { + providerError({ + kind: ErrorKind.Unsupported, + operation: "initialize", + field, + message, + }); +} diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts new file mode 100644 index 00000000..230a59b2 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -0,0 +1,325 @@ +// Native Blaxel error translation is handwritten. +// Classification order: existing SandboxError, abort/timeout DOMException, +// then Blaxel helpers / ResponseError status, then unknown. +// Cause stays on SandboxError.cause. + +import { + CredentialsError, + getBlaxelErrorCode, + getBlaxelErrorStatus, + isCreationTimeoutError, + isGatewayError, + isGatewayTimeout, + ResponseError, + SandboxCreationTimeoutError, + SandboxGatewayError, +} from "@blaxel/core"; +import { + ErrorKind, + FileFieldPath, + SandboxError, + sandboxError, + type ErrorInfo, +} from "@sandbox-kit/sandbox"; + +const FILESYSTEM_OPERATIONS = new Set([ + "read_file", + "write_file", + "copy_from_local", + "copy_to_local", + "make_directory", + "list_directory", + "stat", + "remove", +]); + +/** Inputs for translating a native/provider failure into {@link SandboxError}. */ +interface MappedErrorInput { + kind: ErrorInfo["kind"]; + error: unknown; + operation: string; + statusCode?: number; + providerCode?: string; + providerSource?: string; + filesystemNotFound?: boolean; +} + +/** Inputs for throwing a Blaxel-scoped validation or mapping error. */ +export interface ProviderErrorInput { + kind: ErrorInfo["kind"]; + operation: string; + field: string; + message: string; +} + +const CODE_KINDS: Record = { + AUTHENTICATION_REQUIRED: ErrorKind.Authentication, + AUTHENTICATION_FAILED: ErrorKind.Authentication, + TOKEN_REVOKED: ErrorKind.Authentication, + FORBIDDEN: ErrorKind.PermissionDenied, + UNAUTHORIZED: ErrorKind.PermissionDenied, + UNAUTHORIZED_WORKSPACE: ErrorKind.PermissionDenied, + ROUTE_NOT_FOUND: ErrorKind.NotFound, + WORKLOAD_NOT_FOUND: ErrorKind.NotFound, + WORKSPACE_NOT_FOUND: ErrorKind.NotFound, + IMAGE_NOT_FOUND: ErrorKind.NotFound, + VOLUME_NOT_FOUND: ErrorKind.NotFound, + ACCOUNT_NOT_FOUND: ErrorKind.NotFound, + SANDBOX_DELETED: ErrorKind.NotFound, + VOLUME_DELETED: ErrorKind.NotFound, + SANDBOX_ALREADY_EXISTS: ErrorKind.AlreadyExists, + VOLUME_ALREADY_ATTACHED: ErrorKind.AlreadyExists, + BAD_REQUEST: ErrorKind.InvalidArgument, + POLICY_VIOLATION: ErrorKind.InvalidArgument, + INVALID_INPUT: ErrorKind.InvalidArgument, + INVALID_IMAGE: ErrorKind.InvalidArgument, + INVALID_PORTS: ErrorKind.InvalidArgument, + INVALID_REGION: ErrorKind.InvalidArgument, + INVALID_VOLUME: ErrorKind.InvalidArgument, + INVALID_VOLUME_COUNT: ErrorKind.InvalidArgument, + INVALID_VOLUME_REGION: ErrorKind.InvalidArgument, + VALIDATION_FAILED: ErrorKind.InvalidArgument, + VALIDATION_ERROR: ErrorKind.InvalidArgument, + WORKSPACE_REQUIRED: ErrorKind.InvalidArgument, + PARSE_FAILED: ErrorKind.InvalidArgument, + REGION_MISMATCH: ErrorKind.InvalidArgument, + VOLUME_PROVIDER_MISMATCH: ErrorKind.InvalidArgument, + KERNEL_GENERATION_MISMATCH: ErrorKind.InvalidArgument, + VALIDATED_DATA_ERROR: ErrorKind.InvalidArgument, + WORKSPACE_DATA_MISSING: ErrorKind.InvalidArgument, + UPLOAD_NOT_SUPPORTED: ErrorKind.Unsupported, + UNSUPPORTED_GENERATION: ErrorKind.Unsupported, + CREATION_TIMEOUT: ErrorKind.Timeout, + UPSTREAM_CONNECT_TIMEOUT: ErrorKind.Timeout, + UPSTREAM_TIMEOUT: ErrorKind.Timeout, + QUOTA_EXCEEDED: ErrorKind.ResourceExhausted, + USAGE_LIMIT_EXCEEDED: ErrorKind.ResourceExhausted, + RATE_LIMIT_EXCEEDED: ErrorKind.RateLimited, + WORKSPACE_RATE_LIMIT_EXCEEDED: ErrorKind.RateLimited, + SANDBOX_DELETION_IN_PROGRESS: ErrorKind.Conflict, + WORKLOAD_UNAVAILABLE: ErrorKind.Unavailable, + ROUTING_UNAVAILABLE: ErrorKind.Unavailable, + SERVICE_UNAVAILABLE: ErrorKind.Unavailable, + AUTHENTICATION_UNAVAILABLE: ErrorKind.Unavailable, + UPSTREAM_CONNECT_FAILED: ErrorKind.Unavailable, + UPSTREAM_ERROR: ErrorKind.Unavailable, + GATEWAY_INTERNAL_ERROR: ErrorKind.Unavailable, + UPLOAD_UNAVAILABLE: ErrorKind.Unavailable, + LOCK_ACQUISITION_FAILED: ErrorKind.Unavailable, + VOLUME_LOOKUP_FAILED: ErrorKind.Unavailable, + CLUSTER_GATEWAY_ERROR: ErrorKind.Unavailable, + WORKLOAD_FAILED: ErrorKind.Internal, + INTERNAL_ERROR: ErrorKind.Internal, + HANDLER_ERROR: ErrorKind.Internal, + DATABASE_ERROR: ErrorKind.Internal, + READ_FAILED: ErrorKind.Internal, + BUILD_TRIGGER_ERROR: ErrorKind.Internal, + PRESIGNED_URL_ERROR: ErrorKind.Internal, + DEPLOYMENT_FAILED: ErrorKind.Internal, + PROXY_CONFIG_ERROR: ErrorKind.Internal, + CALLBACK_SEND_FAILED: ErrorKind.Internal, + FORK_FAILED: ErrorKind.Internal, + LOCKDOWN_ERROR: ErrorKind.Internal, + UNKNOWN_ERROR: ErrorKind.Internal, +}; + +const STATUS_KINDS: Record = { + 400: ErrorKind.InvalidArgument, + 422: ErrorKind.InvalidArgument, + 401: ErrorKind.Authentication, + 402: ErrorKind.ResourceExhausted, + 403: ErrorKind.PermissionDenied, + 404: ErrorKind.NotFound, + 410: ErrorKind.NotFound, + 408: ErrorKind.Timeout, + // Bare 409 is Conflict for non-create ops. Create maps it in statusKindFor. + // Coded conflicts still win through CODE_KINDS above. + 409: ErrorKind.Conflict, + 412: ErrorKind.FailedPrecondition, + 429: ErrorKind.RateLimited, + 500: ErrorKind.Internal, + 502: ErrorKind.Unavailable, + 503: ErrorKind.Unavailable, + 504: ErrorKind.Timeout, +}; + +export function mapBlaxelError( + error: unknown, + operation: string, + filesystemNotFound: boolean = FILESYSTEM_OPERATIONS.has(operation), +): SandboxError { + if (error instanceof SandboxError) return error; + if (error instanceof DOMException && error.name === "AbortError") + return create({ kind: ErrorKind.Canceled, error, operation }); + if (error instanceof DOMException && error.name === "TimeoutError") + return create({ kind: ErrorKind.Timeout, error, operation }); + + const cause = causeOf(error); + const statusCode = getBlaxelErrorStatus(error) ?? getBlaxelErrorStatus(cause); + const providerCode = stringifyCode( + getBlaxelErrorCode(error) ?? getBlaxelErrorCode(cause), + ); + // Match Go: X-Blaxel-Source first, then body origin (BlaxelSource / Origin). + const providerSource = + blaxelProviderSource(error) ?? blaxelProviderSource(cause); + return create({ + kind: classifyKind({ error, cause, providerCode, statusCode, operation }), + error, + operation, + filesystemNotFound, + ...(statusCode === undefined ? {} : { statusCode }), + ...(providerCode === undefined ? {} : { providerCode }), + ...(providerSource === undefined ? {} : { providerSource }), + }); +} + +interface ClassifyArgs { + error: unknown; + cause: unknown; + providerCode: string | undefined; + statusCode: number | undefined; + operation: string; +} + +function classifyKind(args: ClassifyArgs): ErrorInfo["kind"] { + const { error, cause, providerCode, statusCode, operation } = args; + if (error instanceof CredentialsError) return ErrorKind.Authentication; + if ( + error instanceof SandboxCreationTimeoutError || + isCreationTimeoutError(error) + ) + return ErrorKind.Timeout; + // process.wait deadline is a plain Error; a 504 on that wait may be its cause. + if (isProcessWaitTimeout(error)) return ErrorKind.Timeout; + + // Provider codes win over bare gateway status so WORKLOAD_FAILED on 503 is + // Internal in both languages (Go maps ErrorCode before StatusCode). + if (providerCode !== undefined && CODE_KINDS[providerCode] !== undefined) + return CODE_KINDS[providerCode]!; + + // Bare edge 504 is a timeout; other gateway statuses are Unavailable. + if (isGatewayTimeout(error) || isGatewayTimeout(cause) || statusCode === 504) + return ErrorKind.Timeout; + if (error instanceof SandboxGatewayError || isGatewayError(error)) + return ErrorKind.Unavailable; + + if (statusCode !== undefined) { + const mapped = statusKindFor(statusCode, operation); + if (mapped !== undefined) return mapped; + } + if (error instanceof ResponseError && error.status !== undefined) + return statusKindFor(error.status, operation) ?? ErrorKind.Unknown; + return ErrorKind.Unknown; +} + +/** Bare 409 on create → AlreadyExists (name collision); otherwise Conflict. */ +function statusKindFor( + status: number, + operation: string, +): ErrorInfo["kind"] | undefined { + if (status === 409 && operation === "create") return ErrorKind.AlreadyExists; + return STATUS_KINDS[status]; +} + +function isProcessWaitTimeout(error: unknown): boolean { + return ( + error instanceof Error && + error.message.startsWith("Process did not finish in time") + ); +} + +function causeOf(error: unknown): unknown { + return error instanceof Error ? error.cause : undefined; +} + +function stringifyCode(code: unknown): string | undefined { + if (typeof code === "string" && code !== "") return code; + if (typeof code === "number") return String(code); + return undefined; +} + +function readProperty(value: unknown, key: string): unknown { + try { + return typeof value === "object" && value !== null + ? Reflect.get(value, key) + : undefined; + } catch { + return undefined; + } +} + +/** Same candidate walk as @blaxel/core getBlaxelErrorCode (error / data / nested). */ +function errorBodies(error: unknown): unknown[] { + const bodies = [ + error, + readProperty(error, "error"), + readProperty(error, "data"), + ]; + return bodies.flatMap((body) => [body, readProperty(body, "error")]); +} + +function headerValue(error: unknown, name: string): string | undefined { + try { + const headers = readProperty(readProperty(error, "response"), "headers"); + const get = readProperty(headers, "get"); + if (typeof get !== "function") return undefined; + const value = Reflect.apply(get, headers, [name]); + return typeof value === "string" && value !== "" ? value : undefined; + } catch { + return undefined; + } +} + +/** Prefer X-Blaxel-Source (Go BlaxelSource), else body origin (Go Origin). */ +function blaxelProviderSource(error: unknown): string | undefined { + const fromHeader = headerValue(error, "x-blaxel-source"); + if (fromHeader !== undefined) return fromHeader; + for (const body of errorBodies(error)) { + const origin = readProperty(body, "origin"); + if (typeof origin === "string" && origin !== "") return origin; + } + return undefined; +} + +function create(input: MappedErrorInput): SandboxError { + const { + kind, + error, + operation, + statusCode, + providerCode, + providerSource, + filesystemNotFound, + } = input; + const field = + kind === ErrorKind.NotFound && filesystemNotFound + ? FileFieldPath + : ""; + return sandboxError( + { + kind, + provider: "blaxel", + operation, + field, + message: + error instanceof Error + ? error.message + : "sandbox-kit blaxel: provider operation failed", + ...(statusCode === undefined ? {} : { statusCode }), + ...(providerCode === undefined ? {} : { providerCode }), + ...(providerSource === undefined ? {} : { providerSource }), + }, + error, + ); +} + +/** Local throw helper for mappings, checks, and config. */ +export function providerError(input: ProviderErrorInput): never { + throw sandboxError({ + kind: input.kind, + provider: "blaxel", + operation: input.operation, + field: input.field, + message: "sandbox-kit blaxel: " + input.message, + }); +} diff --git a/sdks/typescript/providers/blaxel/src/filesystem.ts b/sdks/typescript/providers/blaxel/src/filesystem.ts new file mode 100644 index 00000000..10c9c42b --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/filesystem.ts @@ -0,0 +1,82 @@ +import type { Directory, File as BlaxelFile } from "@blaxel/core"; +import { + ErrorKind, + FileFieldPath, + FileType, + type FileInfo, +} from "@sandbox-kit/sandbox"; +import { providerError } from "./errors.js"; +import { isDirectoryListing, type NativeSandboxHandle } from "./native.js"; + +type BlaxelSubdirectory = Directory["subdirectories"][number]; + +export function fileInfo(info: BlaxelFile, operation = "stat"): FileInfo { + requireFileIdentity(info, operation); + const modified = Date.parse(info.lastModified); + return { + name: info.name, + path: info.path, + type: FileType.File, + size: BigInt(Math.trunc(info.size)), + ...(info.permissions ? { permissions: info.permissions } : {}), + ...(info.owner ? { owner: info.owner } : {}), + ...(info.group ? { group: info.group } : {}), + ...(Number.isNaN(modified) ? {} : { modifiedAt: new Date(modified) }), + }; +} + +export function directoryInfo(info: BlaxelSubdirectory): FileInfo { + return { + name: info.name, + path: info.path, + type: FileType.Directory, + size: 0n, + }; +} + +/** + * Stat one path. Uses the same filesystem GET as Go: a directory listing or + * file metadata (size, permissions when Blaxel supplies them). Does not invent + * name or path when Blaxel omits them. + */ +export async function blaxelStat( + fs: NativeSandboxHandle["fs"], + path: string, +): Promise { + const data = await fs.get(path); + if (isDirectoryListing(data)) { + return { + name: data.name ?? "", + path: data.path ?? "", + type: FileType.Directory, + size: 0n, + }; + } + return fileInfo( + { + name: data.name ?? "", + path: data.path ?? "", + size: data.size, + permissions: data.permissions ?? "", + owner: data.owner ?? "", + group: data.group ?? "", + lastModified: data.lastModified ?? "", + }, + "stat", + ); +} + +/** Match Go blaxelFileInfo: empty name and path is not usable file metadata. */ +function requireFileIdentity( + info: Pick, + operation: string, +): void { + if (!info.name && !info.path) { + providerError({ + kind: ErrorKind.InvalidResponse, + operation, + field: FileFieldPath, + message: "SDK returned no file metadata", + }); + } +} diff --git a/sdks/typescript/providers/blaxel/src/index.ts b/sdks/typescript/providers/blaxel/src/index.ts new file mode 100644 index 00000000..4d8b5e92 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/index.ts @@ -0,0 +1,2 @@ +export { providerName } from "./provider.js"; +export { blaxel } from "./blaxel.js"; diff --git a/sdks/typescript/providers/blaxel/src/local.ts b/sdks/typescript/providers/blaxel/src/local.ts new file mode 100644 index 00000000..20250d64 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/local.ts @@ -0,0 +1,124 @@ +import { mkdir, readFile, stat } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; + +import { + ErrorKind, + FileFieldLocalPath, + SandboxError, + sandboxError, +} from "@sandbox-kit/sandbox"; +import { providerError } from "./errors.js"; + +/** Rejects a missing local file or a directory before Blaxel reads it. */ +async function requireLocalFile( + localPath: string, + operation: string, +): Promise { + let info; + try { + info = await stat(localPath); + } catch (error) { + if (nodeCode(error)) throw localFileError(error, operation); + throw error; + } + if (info.isDirectory()) localDirectory(operation); +} + +/** Rejects a local destination that is already a directory. */ +export async function requireLocalDestination( + localPath: string, + operation: string, +): Promise { + try { + const info = await stat(localPath); + if (info.isDirectory()) localDirectory(operation); + } catch (error) { + if (nodeCode(error) === "ENOENT") return; + if (nodeCode(error)) throw localFileError(error, operation); + throw error; + } +} + +/** Creates the parent directory for a local destination path. */ +export async function ensureLocalParent( + localPath: string, + operation: string, +): Promise { + try { + await mkdir(dirname(localPath), { recursive: true }); + } catch (error) { + throw localFileError(error, operation); + } +} + +/** Reads a local file for upload into the sandbox. */ +export async function readLocalFile( + localPath: string, + operation: string, +): Promise { + await requireLocalFile(localPath, operation); + try { + return await readFile(localPath); + } catch (error) { + throw localFileError(error, operation); + } +} + +/** + * Classifies a local destination path failure from Blaxel download as + * `local_path`. Matches Go: only when the error path equals `localPath`. + * Other errors are rethrown for `mapBlaxelError`. + */ +export function mapCallerPathError( + error: unknown, + operation: string, + localPath: string, +): never { + if (error instanceof SandboxError) throw error; + const pathErr = nodePathError(error) ?? nodePathError(causeOf(error)); + if (pathErr !== undefined && resolve(pathErr.path) === resolve(localPath)) { + throw localFileError(error, operation); + } + throw error; +} + +/** Node `ErrnoException`-style path error (Go `os.PathError` counterpart). */ +function nodePathError(error: unknown): { path: string } | undefined { + if (typeof error !== "object" || !error || !("path" in error)) + return undefined; + const path = error.path; + return typeof path === "string" && path !== "" ? { path } : undefined; +} + +function causeOf(error: unknown): unknown { + return error instanceof Error ? error.cause : undefined; +} + +function localDirectory(operation: string): never { + providerError({ + kind: ErrorKind.InvalidArgument, + operation, + field: FileFieldLocalPath, + message: "local path is a directory", + }); +} + +function localFileError(error: unknown, operation: string): SandboxError { + const code = nodeCode(error) ?? nodeCode(causeOf(error)); + return sandboxError( + { + kind: code === "ENOENT" ? ErrorKind.NotFound : ErrorKind.InvalidArgument, + provider: "blaxel", + operation, + field: FileFieldLocalPath, + message: error instanceof Error ? error.message : "local path failed", + }, + error, + ); +} + +function nodeCode(error: unknown): string | undefined { + if (typeof error !== "object" || !error || !("code" in error)) + return undefined; + return typeof error.code === "string" ? error.code : undefined; +} diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts new file mode 100644 index 00000000..46b893bf --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -0,0 +1,430 @@ +// Provider-specific orchestration around generated portable field mappings. +import type { + Config as NativeClientConfig, + Port, + SandboxCreateConfiguration, + SandboxLifecycle, +} from "@blaxel/core"; +import { + EgressMode, + ErrorKind, + IsolationKind, + PolicyMode, + PortTransport, + SandboxState, + WaitCondition, + type Config, + type CreateOptions, + type SandboxInfo, + ConfigFieldRegion, + CreateFieldIsolation, + CreateFieldLifetime, + CreateFieldLifetimeIdleTerminate, + CreateFieldLifetimeIdleTerminateAfter, + CreateFieldLifetimeIdleTerminateMode, + CreateFieldLifetimeMaximumLifetime, + CreateFieldName, + CreateFieldNetwork, + CreateFieldNetworkPortsPort, + CreateFieldNetworkPortsTransport, + CreateFieldObservability, + CreateFieldProvisioningQueueTimeout, + CreateFieldProvisioningWaitFor, + CreateFieldReadiness, + CreateFieldRuntime, + CreateFieldRuntimeWorkingDirectory, + CreateFieldSecurity, + CreateFieldSourceImageReference, +} from "@sandbox-kit/sandbox"; +import { + mapAllocatedResources, + mapCommonCreateFields as generatedMapCommonCreateFields, + mapResources as generatedMapResources, + mapSandboxIdentity as generatedMapSandboxIdentity, + type BlaxelSandboxIdentity, +} from "./provider_mappings.js"; +import { validateProviderCreate } from "#checks/provider_checks.js"; +import { mapGeneratedClientConfig } from "#config/provider_config.js"; +import type { ProviderState } from "#config/provider_config.js"; +import { providerError } from "#errors.js"; + +export interface BlaxelCreatePlan { + readonly params: SandboxCreateConfiguration; +} + +export function mapClientConfig(config: Config): NativeClientConfig { + return mapGeneratedClientConfig(config); +} + +export function mapCreateRequest( + request: CreateOptions, + config: ProviderState, +): BlaxelCreatePlan { + validateProviderCreate(request); + if (config.region === undefined || config.region.trim() === "") + invalid(ConfigFieldRegion, "Config.region is required"); + + const params: SandboxCreateConfiguration = + generatedMapCommonCreateFields(request); + params.name = nonblank(request.name!, CreateFieldName); + params.image = nonblank( + request.source!.image!.reference, + CreateFieldSourceImageReference, + ); + params.region = nonblank(config.region, ConfigFieldRegion); + + mapRuntime(request); + mapIsolation(request); + mapResources(request, params); + mapNetwork(request, params); + mapSecurity(request); + mapLifetime(request, params); + mapReadiness(request); + mapObservability(request); + mapProvisioning(request); + + return { params }; +} + +function mapRuntime(r: CreateOptions): void { + const v = r.runtime; + if (!v) return; + if (v.workingDirectory !== undefined) + unsupported( + CreateFieldRuntimeWorkingDirectory, + "Blaxel create-time working directory is not mapped", + ); + if (hasUnsupportedRuntimeFields(v)) + unsupported( + CreateFieldRuntime, + "runtime user, language, version, entrypoint, and PTY are not mapped by Blaxel", + ); +} + +function mapIsolation(r: CreateOptions): void { + const v = r.isolation; + if (!v) return; + if (hasUnsupportedIsolation(v)) + unsupported(CreateFieldIsolation, "Blaxel isolation class is not mapped"); +} + +function mapResources(r: CreateOptions, p: SandboxCreateConfiguration): void { + const v = r.resources; + if (!v) return; + Object.assign(p, generatedMapResources(v)); +} + +function mapNetwork(r: CreateOptions, p: SandboxCreateConfiguration): void { + const v = r.network; + if (!v) return; + rejectUnsupportedNetwork(v); + + if (v.outboundDomains !== undefined) { + p.network = { + ...(p.network ?? {}), + allowedDomains: [...v.outboundDomains], + }; + } + + if (v.ports) p.ports = mapPorts(v.ports); +} + +const reservedBlaxelPorts = new Set([80, 443, 8080]); + +function mapPorts( + bindings: NonNullable["ports"]>, +): Port[] { + const ports: Port[] = []; + for (const x of bindings) { + const port = x.port; + if (port === undefined) + invalid(CreateFieldNetworkPortsPort, "port is required"); + if (reservedBlaxelPorts.has(port)) + invalid( + CreateFieldNetworkPortsPort, + "ports 80, 443, and 8080 are reserved by Blaxel", + ); + const protocol = blaxelPortProtocol(x.transport ?? PortTransport.Default); + ports.push( + protocol === undefined ? { target: port } : { target: port, protocol }, + ); + } + return ports; +} + +/** Default egress is a no-op (matches Go). Everything else here is unsupported. */ +function rejectUnsupportedNetwork( + network: NonNullable, +): void { + if (hasUnsupportedNetwork(network)) { + unsupported( + CreateFieldNetwork, + "requested network settings are unsupported by Blaxel", + ); + } +} + +function blaxelPortProtocol( + transport: PortTransport, +): Port["protocol"] | undefined { + // Kit Default leaves the protocol to the provider. Blaxel defaults to HTTP + // when protocol is omitted (normalizePorts). + if (transport === PortTransport.Default) return undefined; + if (transport === PortTransport.Plain) return "TCP"; + if (transport === PortTransport.Tls) return "TLS"; + // Blaxel Port.protocol is HTTP|TCP|UDP|TLS only (Kit Http2Tls included). + unsupported( + CreateFieldNetworkPortsTransport, + "Http2Tls is not a Blaxel port protocol (HTTP|TCP|UDP|TLS)", + ); +} + +function mapSecurity(r: CreateOptions): void { + rejectIfSet( + r.security !== undefined, + CreateFieldSecurity, + "Blaxel security fields are not mapped", + ); +} + +function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { + const v = r.lifetime; + if (!v) return; + // Maximum lifetime first, then idle (matches Go creation.go). + if (v.maximumLifetime !== undefined) { + p.ttl = durationString({ + ms: v.maximumLifetime, + field: CreateFieldLifetimeMaximumLifetime, + minimumMs: 5 * 60_000, + }); + } + + if (v.idleTerminate) { + const lifecycle = mapIdleTerminate(v.idleTerminate); + if (lifecycle !== undefined) p.lifecycle = lifecycle; + } + + if (hasUnsupportedLifetimeFields(v)) + unsupported( + CreateFieldLifetime, + "one or more Blaxel lifetime fields are not mapped", + ); +} + +function mapIdleTerminate( + action: NonNullable["idleTerminate"]>, +): SandboxLifecycle | undefined { + const mode = action.mode ?? PolicyMode.Default; + if (mode === PolicyMode.Disabled) { + unsupported( + CreateFieldLifetimeIdleTerminate, + "disabling idle terminate is not representable", + ); + } + if (mode === PolicyMode.After) { + if (action.after === undefined) + invalid(CreateFieldLifetimeIdleTerminateAfter, "duration is required"); + const value = durationString({ + ms: action.after, + field: CreateFieldLifetimeIdleTerminateAfter, + minimumMs: 60_000, + }); + return { + expirationPolicies: [{ type: "ttl-idle", action: "delete", value }], + }; + } + if (mode !== PolicyMode.Default) + invalid(CreateFieldLifetimeIdleTerminateMode, "unknown policy mode"); + return undefined; +} + +function mapReadiness(r: CreateOptions): void { + rejectIfSet( + r.readiness !== undefined, + CreateFieldReadiness, + "Blaxel readiness probes are not mapped", + ); +} + +function mapObservability(r: CreateOptions): void { + rejectIfSet( + r.observability !== undefined, + CreateFieldObservability, + "Blaxel observability fields are not mapped", + ); +} + +// Kit Client.create applies provisioning.timeout as the abort deadline. +// When that deadline is a whole number of seconds in 1..50, create also +// forwards Blaxel's native create timeout (name freed on CREATION_TIMEOUT). +function mapProvisioning(r: CreateOptions): void { + const v = r.provisioning; + if (!v) return; + if (v.queueTimeout !== undefined) + unsupported( + CreateFieldProvisioningQueueTimeout, + "Blaxel queue timeout is unsupported", + ); + + const waitFor = v.waitFor ?? WaitCondition.Default; + if (waitFor === WaitCondition.Default || waitFor === WaitCondition.Scheduled) + return; + unsupported( + CreateFieldProvisioningWaitFor, + "requested wait condition is unsupported", + ); +} + +/** + * Blaxel `SandboxInstance.create` timeout is whole seconds 1..50. + * Outside that range Kit keeps the abort deadline only. + */ +export function blaxelCreateTimeoutSeconds( + timeoutMs: number | undefined, +): number | undefined { + if (timeoutMs === undefined || timeoutMs === 0) return undefined; + if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs % 1_000 !== 0) + return undefined; + const seconds = timeoutMs / 1_000; + if (seconds < 1 || seconds > 50) return undefined; + return seconds; +} + +/** Maps native fields into shared metadata. Does not invent values Blaxel omitted. */ +export function mapSandboxInfo(value: BlaxelSandboxIdentity): SandboxInfo { + const mapped = generatedMapSandboxIdentity(value); + const id = mapped.id ?? value.metadata?.name ?? ""; + const result: SandboxInfo = { + id, + provider: "blaxel", + state: mapped.state ?? SandboxState.Unknown, + }; + if (mapped.name !== undefined) mutable(result).name = mapped.name; + if (mapped.labels !== undefined) mutable(result).labels = mapped.labels; + if (mapped.region !== undefined) mutable(result).region = mapped.region; + if (mapped.providerState !== undefined) + mutable(result).providerState = mapped.providerState; + if (result.state === SandboxState.Deleted) mutable(result).terminal = true; + const resources = mapAllocatedResources(value); + if (resources !== undefined) mutable(result).resources = resources; + return result; +} + +function mutable(v: SandboxInfo): { + -readonly [K in keyof SandboxInfo]: SandboxInfo[K]; +} { + return v; +} + +function nonblank(v: string, f: string): string { + if (!v.trim()) invalid(f, "must not be blank"); + return v; +} + +interface DurationStringArgs { + ms: number; + field: string; + minimumMs: number; +} + +/** Converts Kit millisecond durations into Blaxel duration strings (s/m/h/d). */ +function durationString(args: DurationStringArgs): string { + const { ms, field, minimumMs } = args; + // Rejection text matches Go formatBlaxelDuration. + if (!Number.isFinite(ms) || ms <= 0 || ms % 1_000 !== 0) + invalid(field, "duration must be a positive whole number of seconds"); + if (ms < minimumMs) + invalid( + field, + `duration must be at least ${blaxelDurationUnits(minimumMs)}`, + ); + return blaxelDurationUnits(ms); +} + +function blaxelDurationUnits(ms: number): string { + const seconds = ms / 1_000; + if (seconds % 86_400 === 0) return `${seconds / 86_400}d`; + if (seconds % 3_600 === 0) return `${seconds / 3_600}h`; + if (seconds % 60 === 0) return `${seconds / 60}m`; + return `${seconds}s`; +} + +function rejectIfSet(set: boolean, field: string, message: string): void { + if (set) unsupported(field, message); +} + +function isDefined(value: T | undefined): value is T { + return value !== undefined; +} + +function hasUnsupportedRuntimeFields( + runtime: NonNullable, +): boolean { + return [ + runtime.entrypoint, + runtime.pty, + runtime.user, + runtime.language, + runtime.version, + ].some(isDefined); +} + +function hasUnsupportedIsolation( + isolation: NonNullable, +): boolean { + return ( + (isolation.kind !== undefined && + isolation.kind !== IsolationKind.Default) || + isolation.providerKind !== undefined || + isolation.nestedVirtualization !== undefined + ); +} + +function hasUnsupportedLifetimeFields( + lifetime: NonNullable, +): boolean { + return [ + lifetime.ephemeral, + lifetime.idleStop, + lifetime.idlePause, + lifetime.stoppedArchive, + lifetime.stoppedDelete, + ].some(isDefined); +} + +function hasUnsupportedNetwork( + network: NonNullable, +): boolean { + const nonDefaultEgress = + network.egress !== undefined && network.egress !== EgressMode.Default; + return ( + nonDefaultEgress || + [ + network.outboundCidrs, + network.inboundCidrs, + network.privateNetwork, + network.linkedSandbox, + network.proxyReference, + network.outboundProxyUrl, + network.customDomain, + ].some(isDefined) + ); +} + +function invalid(field: string, message: string): never { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "create", + field, + message, + }); +} + +function unsupported(field: string, message: string): never { + providerError({ + kind: ErrorKind.Unsupported, + operation: "create", + field, + message, + }); +} diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts new file mode 100644 index 00000000..1e96fddc --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -0,0 +1,208 @@ +// Provider field mappings between shared CreateOptions/Config and the native SDK. +// Inventoried in specs/providers/blaxel.yaml groups when present; kept in sync by hand. + +import { + ConfigFieldScopeWorkspace, + ErrorKind, + SandboxState, + type SandboxInfo, + type Scope, + CreateFieldResourcesMemoryMib, +} from "@sandbox-kit/sandbox"; +import type { + APIKeyCredentials, + CreateOptions, + Resources, +} from "@sandbox-kit/sandbox"; +import type { + Config as NativeClientConfig, + SandboxCreateConfiguration, +} from "@blaxel/core"; +import { providerError } from "#errors.js"; + +type Mutable = { -readonly [K in keyof T]: T[K] }; + +// Blaxel uses decimal MB; Kit uses binary MiB. Keep this rational to avoid +// float rounding at the public boundary. +const BLAXEL_MB_PER_MIB_NUMERATOR = 131_072n; +const BLAXEL_MB_PER_MIB_DENOMINATOR = 125_000n; + +/** Minimal native sandbox shape used for identity mapping. */ +export interface BlaxelSandboxIdentity { + readonly metadata?: { + readonly name?: string; + readonly labels?: Readonly>; + }; + /** Deployment status (DEPLOYED, FAILED, …). */ + readonly status?: unknown; + readonly spec?: { + readonly region?: string; + readonly runtime?: { readonly memory?: number }; + }; +} + +export function mapSandboxIdentity( + source: BlaxelSandboxIdentity | undefined, + _operation: string = "create", +): Partial { + const target: Mutable> = {}; + if (source === undefined) return target; + const name = source.metadata?.name; + if (name !== undefined && name !== "") { + target.id = name; + target.name = name; + } + if (source.status !== undefined && String(source.status) !== "") { + target.providerState = String(source.status); + } + const state = mapSandboxState(source.status); + if (state !== undefined) target.state = state; + const region = source.spec?.region; + if (region !== undefined && region !== "") target.region = region; + const labels = source.metadata?.labels; + if (labels !== undefined) target.labels = { ...labels }; + target.provider = "blaxel"; + return target; +} + +export function mapClientScope( + source: Scope | undefined, + operation: string = "initialize", +): NativeClientConfig { + const target: Mutable = {}; + if (source === undefined) return target; + if (source.workspace !== undefined) { + if (source.workspace.trim() === "") { + providerError({ + kind: ErrorKind.InvalidArgument, + operation, + field: ConfigFieldScopeWorkspace, + message: "workspace requires a non-blank value", + }); + } + target.workspace = source.workspace; + } + return target; +} + +export function mapCommonCreateFields( + source: CreateOptions | undefined, + _operation: string = "create", +): SandboxCreateConfiguration { + const target: Mutable = {}; + if (source === undefined) return target; + if (source.name !== undefined) target.name = source.name; + // Match Go: omit envs when the map is empty so explicit {} equals omit. + if (source.environment !== undefined) { + const entries = Object.entries(source.environment); + if (entries.length > 0) { + target.envs = entries.map(([name, value]) => ({ + name, + value: String(value), + })); + } + } + if (source.labels !== undefined) target.labels = { ...source.labels }; + return target; +} + +export function mapResources( + source: Resources | undefined, + operation: string = "create", +): Pick { + const target: Mutable> = {}; + if (source === undefined) return target; + if (source.memoryMib !== undefined) { + const value = source.memoryMib; + if (value === 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) + providerError({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldResourcesMemoryMib, + message: "memory_mib cannot be represented", + operation, + }); + const megabytes = mibToBlaxelMb(value); + if (megabytes > BigInt(Number.MAX_SAFE_INTEGER)) + providerError({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldResourcesMemoryMib, + message: "memory_mib cannot be represented", + operation, + }); + target.memory = Number(megabytes); + } + return target; +} + +export function mapAPIKeyCredentials( + source: APIKeyCredentials | undefined, + _operation: string = "initialize", +): NativeClientConfig { + const target: Mutable = {}; + if (source === undefined) return target; + target.apiKey = source.key; + return target; +} + +/** Maps native decimal MB onto the whole binary MiB guaranteed by that value. */ +export function mapAllocatedResources( + source: BlaxelSandboxIdentity | undefined, +): Resources | undefined { + const memory = source?.spec?.runtime?.memory; + if (memory === undefined || !Number.isFinite(memory) || memory <= 0) { + return undefined; + } + return { memoryMib: blaxelMbToMib(BigInt(Math.trunc(memory))) }; +} + +/** Rounds up so a Kit request is never under-provisioned by Blaxel. */ +function mibToBlaxelMb(mib: bigint): bigint { + return ( + (mib * BLAXEL_MB_PER_MIB_NUMERATOR + + BLAXEL_MB_PER_MIB_DENOMINATOR - + 1n) / + BLAXEL_MB_PER_MIB_DENOMINATOR + ); +} + +/** Reports the whole MiB guaranteed by a native decimal-MB allocation. */ +function blaxelMbToMib(megabytes: bigint): bigint { + return ( + (megabytes * BLAXEL_MB_PER_MIB_DENOMINATOR) / + BLAXEL_MB_PER_MIB_NUMERATOR + ); +} + +/** Maps the public deployment status exposed by @blaxel/core. */ +function mapSandboxState(status: unknown): SandboxState | undefined { + const normalized = + status === undefined || String(status) === "" + ? "" + : String(status).toUpperCase(); + switch (normalized) { + case "UPLOADING": + case "BUILDING": + case "DEPLOYING": + case "BUILT": + return SandboxState.Queued; + case "DEPLOYED": + return SandboxState.Running; + case "DELETING": + case "DEACTIVATING": + return SandboxState.Deleting; + case "TERMINATED": + case "DEACTIVATED": + case "DELETED": + return SandboxState.Deleted; + case "ARCHIVING": + return SandboxState.Archiving; + case "ARCHIVED": + return SandboxState.Archived; + case "UNARCHIVING": + return SandboxState.Starting; + case "FAILED": + return SandboxState.Error; + default: + return undefined; + } +} diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts new file mode 100644 index 00000000..23b23bf8 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -0,0 +1,195 @@ +import { + getFilesystemByPath, + ResponseError, + SandboxInstance, + settings, + type Config as NativeClientConfig, + type Directory, + type File as BlaxelFile, + type SandboxCreateConfiguration, + type SandboxFileSystem, +} from "@blaxel/core"; +import { ConfigFieldAuth, ErrorKind, FileFieldPath } from "@sandbox-kit/sandbox"; +import type { BlaxelSandboxIdentity } from "#mappings/provider_mappings.js"; +import { providerError } from "./errors.js"; + +let configuredProcessConfig: string | undefined; + +export interface BlaxelProcessResult { + readonly pid?: string; + readonly name?: string; + readonly status?: string; + readonly exitCode?: number; + readonly stdout?: string; + readonly stderr?: string; +} + +export interface ProcessExecRequest { + command: string; + waitForCompletion?: boolean; + timeout?: number; + workingDir?: string; + env?: Record; +} + +export interface NativeSandboxHandle extends BlaxelSandboxIdentity { + readonly fs: { + readBinary(path: string): Promise; + writeBinary(path: string, content: Uint8Array): Promise; + mkdir(path: string, permissions?: string): Promise; + ls(path: string): Promise; + /** GET path: directory listing or file metadata (size, permissions, …). */ + get(path: string): Promise; + rm(path: string, recursive?: boolean): Promise; + download(src: string, destinationPath: string): Promise; + }; + readonly process: { + exec(request: ProcessExecRequest): Promise; + wait( + identifier: string, + options?: { + maxWait?: number; + signal?: AbortSignal; + }, + ): Promise; + }; +} + +export interface NativeBlaxel { + configure(config: NativeClientConfig): void; + create( + params: SandboxCreateConfiguration, + /** Blaxel create timeout in whole seconds (1..50). */ + options?: { readonly timeout?: number }, + ): Promise; + get(name: string): Promise; + delete(name: string): Promise; +} + +export type NativeFactory = (config: NativeClientConfig) => NativeBlaxel; + +export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { + configureProcessSettings(config); + return { + configure(next: NativeClientConfig): void { + configureProcessSettings(next); + }, + async create(params, options) { + return projectHandle( + await SandboxInstance.create( + params, + options?.timeout === undefined + ? undefined + : { timeout: options.timeout }, + ), + ); + }, + async get(name) { + return projectHandle(await SandboxInstance.get(name)); + }, + async delete(name) { + return (await SandboxInstance.delete(name)) as BlaxelSandboxIdentity; + }, + }; +} + +/** Projects public SDK properties and wraps filesystem/process helpers. */ +export function projectHandle(instance: SandboxInstance): NativeSandboxHandle { + return { + metadata: instance.metadata, + status: instance.status, + spec: instance.spec, + fs: wrapFilesystem(instance.fs), + process: { + exec: (request) => + instance.process.exec(request) as Promise, + wait: (identifier, options) => + instance.process.wait( + identifier, + options, + ) as Promise, + }, + }; +} + +function wrapFilesystem(fs: SandboxFileSystem): NativeSandboxHandle["fs"] { + return { + readBinary: (path) => fs.readBinary(path), + writeBinary: (path, content) => fs.writeBinary(path, content), + mkdir: (path, permissions) => fs.mkdir(path, permissions), + ls: (path) => fs.ls(path), + rm: (path, recursive) => fs.rm(path, recursive), + download: (src, destinationPath) => fs.download(src, destinationPath), + get: (path) => filesystemGet(fs, path), + }; +} + +async function filesystemGet( + fs: SandboxFileSystem, + path: string, +): Promise { + const { response, data, error } = await getFilesystemByPath({ + path: { path }, + baseUrl: fs.url, + client: fs.client, + }); + if (!response.ok || data == null) { + throw new ResponseError(response, data, error); + } + if (isDirectoryListing(data)) return data; + if (isFileMetadata(data)) { + const { content: _content, ...meta } = data as BlaxelFile & { + content?: string; + }; + return meta; + } + // Match Go Stat: a non-listing GET body is treated as file metadata; when it + // is not usable Kit reports InvalidResponse (not Unknown from a plain Error). + providerError({ + kind: ErrorKind.InvalidResponse, + operation: "stat", + field: FileFieldPath, + message: "SDK returned no file metadata", + }); +} + +/** True when a filesystem GET body is a directory listing (shared with Stat). */ +export function isDirectoryListing(data: unknown): data is Directory { + return ( + !!data && + typeof data === "object" && + ("files" in data || "subdirectories" in data) + ); +} + +function isFileMetadata(data: unknown): data is BlaxelFile { + return ( + !!data && + typeof data === "object" && + "size" in data && + !("files" in data) && + !("subdirectories" in data) + ); +} + +function configureProcessSettings(config: NativeClientConfig): void { + const serialized = JSON.stringify( + Object.entries(config) + .filter(([, value]) => value !== undefined) + .sort(([left], [right]) => left.localeCompare(right)), + ); + if ( + configuredProcessConfig !== undefined && + configuredProcessConfig !== serialized + ) { + providerError({ + kind: ErrorKind.FailedPrecondition, + operation: "initialize", + field: ConfigFieldAuth, + message: + "a Blaxel TypeScript client with a different configuration already exists in this process", + }); + } + settings.setConfig(config); + configuredProcessConfig = serialized; +} diff --git a/sdks/typescript/providers/blaxel/src/process.ts b/sdks/typescript/providers/blaxel/src/process.ts new file mode 100644 index 00000000..c3ecb9b3 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/process.ts @@ -0,0 +1,122 @@ +import { + ErrorKind, + ExecFieldTimeout, + sandboxError, + type ExecOptions, + type ExecResult, +} from "@sandbox-kit/sandbox"; +import { providerError } from "./errors.js"; +import type { + BlaxelProcessResult, + NativeSandboxHandle, + ProcessExecRequest, +} from "./native.js"; + +const terminalProcessStatus = new Set([ + "completed", + "failed", + "killed", + "stopped", +]); + +/** Starts without waitForCompletion; Kit waits via process.wait. */ +export function processRequest( + request: ExecOptions, + timeout: number | undefined, +): ProcessExecRequest { + const body: ProcessExecRequest = { + command: blaxelCommand(request), + waitForCompletion: false, + // omit/0 → 0 disables Blaxel's default process auto-kill. + timeout: timeout ?? 0, + }; + if (request.workingDirectory !== undefined) { + body.workingDir = request.workingDirectory; + } + if (request.environment !== undefined) { + body.env = { ...request.environment }; + } + return body; +} + +/** Internal wait after Process.exec: return terminal start, else one native wait. */ +export interface FinishProcessArgs { + process: NativeSandboxHandle["process"]; + started: BlaxelProcessResult; + /** Kit process timeout in whole seconds; undefined means unlimited. */ + timeout: number | undefined; + signal: AbortSignal | undefined; +} + +export async function finishProcess( + args: FinishProcessArgs, +): Promise { + if (processFinished(args.started.status)) return execResult(args.started); + const identifier = args.started.pid || args.started.name; + if (!identifier) { + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: "blaxel", + operation: "run", + field: "", + message: "sandbox-kit blaxel: SDK returned no process identifier", + }); + } + const waitOptions: { + maxWait: number; + signal?: AbortSignal; + } = { + maxWait: args.timeout === undefined ? -1 : args.timeout * 1_000, + }; + if (args.signal !== undefined) waitOptions.signal = args.signal; + return execResult(await args.process.wait(identifier, waitOptions)); +} + +/** + * Maps Kit ExecOptions.timeout (ms) to Blaxel seconds. + * Omitted and explicit zero become undefined so the caller sends 0 (no limit). + */ +export function execTimeoutSeconds( + timeout: number | undefined, +): number | undefined { + if (timeout === undefined || timeout === 0) return undefined; + if (!isWholeSecondTimeout(timeout)) { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "run", + field: ExecFieldTimeout, + message: "timeout must be a whole number of seconds", + }); + } + return timeout / 1_000; +} + +function isWholeSecondTimeout(timeout: number): boolean { + return Number.isFinite(timeout) && timeout >= 0 && timeout % 1_000 === 0; +} + +function processFinished(status: string | undefined): boolean { + return status !== undefined && terminalProcessStatus.has(status); +} + +function execResult(result: BlaxelProcessResult): ExecResult { + return { + exitCode: result.exitCode ?? 0, + stdout: new TextEncoder().encode(result.stdout ?? ""), + stderr: new TextEncoder().encode(result.stderr ?? ""), + }; +} + +function blaxelCommand(request: ExecOptions): string { + if (request.shell !== undefined) return request.shell; + return posixShellJoin(request.argv ?? []); +} + +function posixShellJoin(argv: readonly string[]): string { + return argv.map(posixQuote).join(" "); +} + +function posixQuote(value: string): string { + if (value === "") return "''"; + return "'" + value.replaceAll("'", `'"'"'`) + "'"; +} diff --git a/sdks/typescript/providers/blaxel/src/provider.ts b/sdks/typescript/providers/blaxel/src/provider.ts new file mode 100644 index 00000000..4d66e586 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/provider.ts @@ -0,0 +1,29 @@ +// Blaxel provider construction and native SDK orchestration are handwritten. + +import type { + Backend, + Config, + CreateOptions, + CreateResult, + OperationOptions, + Provider, +} from "@sandbox-kit/sandbox"; + +export const providerName = "blaxel" as const; + +/** @internal Shared provider identity wiring. */ +export abstract class ProviderBase implements Provider { + readonly name = providerName; + abstract initialize(config: Config): Backend; +} + +/** @internal Shared backend identity and required create/close surface. */ +export abstract class BackendBase implements Backend { + readonly name = providerName; + abstract create( + request: CreateOptions, + options: OperationOptions, + ): Promise; + abstract close(options: OperationOptions): Promise; + abstract closeSandbox(id: string, options: OperationOptions): Promise; +} diff --git a/sdks/typescript/providers/blaxel/src/rejection.test.ts b/sdks/typescript/providers/blaxel/src/rejection.test.ts new file mode 100644 index 00000000..a7325df4 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/rejection.test.ts @@ -0,0 +1,285 @@ +import { + ConfigFieldAuth, + ConfigFieldEndpoint, + ConfigFieldRegion, + ConfigFieldScopeAppName, + ConfigFieldScopeOrganizationId, + CreateFieldIsolation, + CreateFieldLifetime, + CreateFieldName, + CreateFieldNetwork, + CreateFieldNetworkOutboundDomainsEntries, + CreateFieldObservability, + CreateFieldPlacement, + CreateFieldProvisioningQueueTimeout, + CreateFieldProvisioningWaitFor, + CreateFieldReadiness, + CreateFieldResourcesCpuCores, + CreateFieldResourcesDiskMib, + CreateFieldRuntime, + CreateFieldSecurity, + CreateFieldSourceImage, + ErrorKind, + IsolationKind, + WaitCondition, + type Config, + type CreateOptions, +} from "@sandbox-kit/sandbox"; +import { describe, expect, it } from "vitest"; + +import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; + +const provider = { region: "us-pdx-1" }; + +describe("rejected Blaxel create settings", () => { + const cases: { + name: string; + request: CreateOptions; + kind: (typeof ErrorKind)[keyof typeof ErrorKind]; + field: string; + }[] = [ + { + name: "cpu resources", + request: { resources: { cpuCores: 1 } }, + kind: ErrorKind.Unsupported, + field: CreateFieldResourcesCpuCores, + }, + { + name: "disk", + request: { resources: { diskMib: 1024n } }, + kind: ErrorKind.Unsupported, + field: CreateFieldResourcesDiskMib, + }, + { + name: "placement", + request: { placement: { regions: ["us-pdx-1"] } }, + kind: ErrorKind.Unsupported, + field: CreateFieldPlacement, + }, + { + name: "queue timeout", + request: { provisioning: { queueTimeout: 60_000 } }, + kind: ErrorKind.Unsupported, + field: CreateFieldProvisioningQueueTimeout, + }, + { + name: "wait for ready", + request: { provisioning: { waitFor: WaitCondition.Ready } }, + kind: ErrorKind.Unsupported, + field: CreateFieldProvisioningWaitFor, + }, + { + name: "readiness", + request: { readiness: { tcpPort: 8080 } }, + kind: ErrorKind.Unsupported, + field: CreateFieldReadiness, + }, + { + name: "observability", + request: { + observability: { telemetryEndpoint: "https://otel.example" }, + }, + kind: ErrorKind.Unsupported, + field: CreateFieldObservability, + }, + { + name: "security", + request: { security: { publicAccess: true } }, + kind: ErrorKind.Unsupported, + field: CreateFieldSecurity, + }, + { + name: "private network", + request: { network: { privateNetwork: true } }, + kind: ErrorKind.Unsupported, + field: CreateFieldNetwork, + }, + { + name: "ephemeral lifetime", + request: { lifetime: { ephemeral: true } }, + kind: ErrorKind.Unsupported, + field: CreateFieldLifetime, + }, + { + name: "runtime language", + request: { runtime: { language: "python" } }, + kind: ErrorKind.Unsupported, + field: CreateFieldRuntime, + }, + { + name: "isolation container", + request: { isolation: { kind: IsolationKind.Container } }, + kind: ErrorKind.Unsupported, + field: CreateFieldIsolation, + }, + ]; + + it("missing name", () => { + expect(() => + mapCreateRequest( + { source: { image: { reference: "blaxel/base-image:latest" } } }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldName, + }), + ); + }); + + it("whitespace name", () => { + expect(() => + mapCreateRequest( + { + name: " ", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldName, + message: expect.stringContaining("name is required"), + }), + ); + }); + + it("invalid name", () => { + expect(() => + mapCreateRequest( + { + name: "My_Sandbox", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldName, + }), + ); + }); + + it("missing image", () => { + expect(() => mapCreateRequest({ name: "box" }, provider)).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldSourceImage, + }), + ); + }); + + it("blank image reference", () => { + expect(() => + mapCreateRequest( + { name: "box", source: { image: { reference: " " } } }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldSourceImage, + }), + ); + }); + + it("invalid outbound domain", () => { + expect(() => + mapCreateRequest( + namedImage({ network: { outboundDomains: ["not a domain!!!"] } }), + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldNetworkOutboundDomainsEntries, + }), + ); + }); + + it("missing region", () => { + expect(() => mapCreateRequest(namedImage({}), {})).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldRegion, + }), + ); + }); + + it("accepts kit create timeout as deadline", () => { + expect(() => + mapCreateRequest( + namedImage({ provisioning: { timeout: 30_000 } }), + provider, + ), + ).not.toThrow(); + }); + + it("accepts default isolation", () => { + expect(() => + mapCreateRequest( + namedImage({ isolation: { kind: IsolationKind.Default } }), + provider, + ), + ).not.toThrow(); + }); + + for (const item of cases) { + it(item.name, () => { + expect(() => + mapCreateRequest(namedImage(item.request), provider), + ).toThrowError( + expect.objectContaining({ kind: item.kind, field: item.field }), + ); + }); + } +}); + +describe("rejected Blaxel client settings", () => { + const cases: { name: string; config: Config; field: string }[] = [ + { + name: "endpoint", + config: { endpoint: "https://unsupported.example" }, + field: ConfigFieldEndpoint, + }, + { + name: "organization", + config: { scope: { organizationId: "org" } }, + field: ConfigFieldScopeOrganizationId, + }, + { + name: "app name", + config: { scope: { appName: "app" } }, + field: ConfigFieldScopeAppName, + }, + { + name: "token pair auth", + config: { + auth: { tokenPair: { id: "id", secret: "secret" } }, + }, + field: ConfigFieldAuth, + }, + ]; + + for (const item of cases) { + it(item.name, () => { + expect(() => mapClientConfig(item.config)).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: item.field, + }), + ); + }); + } +}); + +function namedImage(request: CreateOptions): CreateOptions { + return { + name: "sandbox", + ...request, + source: { image: { reference: "blaxel/base-image:latest" } }, + }; +} diff --git a/sdks/typescript/providers/blaxel/tsconfig.build.json b/sdks/typescript/providers/blaxel/tsconfig.build.json new file mode 100644 index 00000000..cf0eba47 --- /dev/null +++ b/sdks/typescript/providers/blaxel/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false + }, + "exclude": ["src/**/*.test.ts"] +} diff --git a/sdks/typescript/providers/blaxel/tsconfig.json b/sdks/typescript/providers/blaxel/tsconfig.json new file mode 100644 index 00000000..35b5033f --- /dev/null +++ b/sdks/typescript/providers/blaxel/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "declaration": true, + "declarationMap": false, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "noImplicitOverride": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "customConditions": ["@sandbox-kit/source"], + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/sdks/typescript/providers/blaxel/tsconfig.test.json b/sdks/typescript/providers/blaxel/tsconfig.test.json new file mode 100644 index 00000000..fc8520e7 --- /dev/null +++ b/sdks/typescript/providers/blaxel/tsconfig.test.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/sdks/typescript/providers/blaxel/vitest.config.ts b/sdks/typescript/providers/blaxel/vitest.config.ts new file mode 100644 index 00000000..da559838 --- /dev/null +++ b/sdks/typescript/providers/blaxel/vitest.config.ts @@ -0,0 +1,24 @@ +/// +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vitest/config"; + +const root = path.dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + resolve: { + alias: [ + { + find: /^#(mappings|checks|config)\/(.+)\.js$/, + replacement: path.join(root, "src/$1/$2.ts"), + }, + { + find: "#errors.js", + replacement: path.join(root, "src/errors.ts"), + }, + ], + }, + test: { + environment: "node", + }, +}); diff --git a/sdks/typescript/sandbox/README.md b/sdks/typescript/sandbox/README.md index 2eac31c1..286a84c9 100644 --- a/sdks/typescript/sandbox/README.md +++ b/sdks/typescript/sandbox/README.md @@ -1,8 +1,9 @@ # `@sandbox-kit/sandbox` Create and manage cloud sandboxes with a consistent TypeScript client interface. -Pick a provider package (`@sandbox-kit/daytona` or `@sandbox-kit/modal`), -construct a client, then create or open sandboxes through that client. +Pick a provider package (`@sandbox-kit/daytona`, `@sandbox-kit/modal`, or +`@sandbox-kit/blaxel`), construct a client, then create or open sandboxes +through that client. Closing a client or handle releases **local** SDK resources only. It does not stop, terminate, or delete the remote sandbox. @@ -13,12 +14,16 @@ stop, terminate, or delete the remote sandbox. npm install @sandbox-kit/sandbox @sandbox-kit/daytona # or npm install @sandbox-kit/sandbox @sandbox-kit/modal +# or +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel ``` Requires Node.js 20, 22, or 24.15+. ## Quick start +Daytona sample (`create()` with no body uses Daytona's default snapshot): + ```ts import { daytona } from "@sandbox-kit/daytona"; import { Client } from "@sandbox-kit/sandbox"; @@ -40,14 +45,14 @@ Prefer `await using` when available. Otherwise call `await client.close()` in a Pass the provider on `ClientConfig`, then shared settings: -| Field | Purpose | -| ---------- | --------------------------------------------------------------- | -| `provider` | Provider factory (`daytona()`, `modal()`, …) | -| `auth` | One credential shape (`apiKey`, `bearerToken`, `tokenPair`, …) | -| `scope` | App, environment, organization, or project (provider-dependent) | -| `endpoint` | API URL when the provider supports it | -| `region` | Target or placement preference | -| `timeout` | Default operation deadline in **milliseconds** | +| Field | Purpose | +| ---------- | -------------------------------------------------------------------------- | +| `provider` | Provider factory (`daytona()`, `modal()`, `blaxel()`) | +| `auth` | One credential shape (`apiKey`, `bearerToken`, `tokenPair`, …) | +| `scope` | App, environment, organization, project, or workspace (provider-dependent) | +| `endpoint` | API URL when the provider supports it | +| `region` | Target or placement preference | +| `timeout` | Default operation deadline in **milliseconds** | Omit `auth` when the provider SDK should resolve credentials from its own environment. Provider-specific auth and scope rules live in each provider @@ -55,6 +60,8 @@ package README. ## Create a sandbox +Daytona example: + ```ts const sandbox = await client.create({ source: { image: { reference: "daytonaio/playground:latest" } }, @@ -63,15 +70,16 @@ const sandbox = await client.create({ ``` `create()` with no options uses the provider default when that is valid (for -example Daytona's default snapshot). Timeouts and delays are millisecond +example Daytona's default snapshot). Modal requires an image. Blaxel requires +`name`, an image, and client `region`. Timeouts and delays are millisecond numbers. Nested exclusive fields use shapes like `source: { image: … }` or `auth: { apiKey: … }`. ## Open an existing sandbox ```ts -const attached = await client.attach(id); // provider must support attach -const fetched = await client.get(id); // provider-verified lookup +const attached = await client.attach(id); // Modal: id only, no existence check +const fetched = await client.get(id); // Daytona: sandbox id; Blaxel: create name ``` ## Run a command @@ -124,9 +132,10 @@ Sandbox paths are absolute. The same handle also supports `copyFromLocal`, | `filesystem` | Read, write, copy, list, mkdir, stat, remove | | `process` | Run a command and wait for exit code + output | -`supports` reports whether the provider implements that operation. An -unimplemented method throws before the provider call. A provider rejection, -including one from a closed SDK, comes back as that provider's error. +Check `supports` before a lifecycle call that might be unavailable. On Blaxel, +you can `get`, `refresh`, `delete`, and `close`. Other lifecycle methods are +not available. If the provider rejects a supported call, you get that +provider's error. ## Errors @@ -153,6 +162,7 @@ try { | ---------------------- | ----------------- | | `@sandbox-kit/daytona` | Daytona sandboxes | | `@sandbox-kit/modal` | Modal sandboxes | +| `@sandbox-kit/blaxel` | Blaxel sandboxes | ## Examples diff --git a/sdks/typescript/sandbox/src/client/client.test.ts b/sdks/typescript/sandbox/src/client/client.test.ts index d44febc9..3fff8238 100644 --- a/sdks/typescript/sandbox/src/client/client.test.ts +++ b/sdks/typescript/sandbox/src/client/client.test.ts @@ -1,10 +1,13 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; +import { Buffer } from "node:buffer"; +import { getEventListeners } from "node:events"; import { ExecFieldArgv, ExecFieldShell, ExecFieldWorkingDirectory, FileFieldLocalPath, FileFieldPath, + InfoFieldId, CreateFieldLifetimeIdleStopAfter, CreateFieldLifetimeIdleStopMode, CreateFieldNetwork, @@ -25,6 +28,8 @@ import { Client, EgressMode, ErrorKind, + SandboxError, + sandboxError, PolicyMode, validateCreateOptions, type Backend, @@ -190,6 +195,110 @@ describe("Client cleanup", () => { }); }); +describe("Provider response validation", () => { + it.each([undefined, null])("rejects an absent create result: %s", async (result) => { + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + create: async () => result as unknown as CreateResult, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + await expect(client.create()).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "create", + field: InfoFieldId, + }); + }); + it("reports the public get operation and preserves the provider error", async () => { + const native = new Error("native failure"); + const providerError = sandboxError( + { + kind: ErrorKind.Unknown, + provider: "wrong-provider", + operation: "refresh", + field: "", + message: "lookup failed", + }, + native, + ); + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + refresh: async () => { + throw providerError; + }, + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + + const error = await client.get("requested").catch((reason: unknown) => reason); + expect(error).toBeInstanceOf(SandboxError); + expect(error).toMatchObject({ + provider: "test", + operation: "get", + message: "lookup failed", + }); + expect((error as Error).cause).toBe(providerError); + expect(providerError.cause).toBe(native); + }); + + it("rejects lookup responses with a different identity or provider", async () => { + const attachBackend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async () => ({ id: "different", provider: "test" }), + }; + const attachClient = new Client({ + provider: { name: attachBackend.name, initialize: () => attachBackend }, + }); + await expect(attachClient.attach("requested")).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "attach", + field: InfoFieldId, + }); + + const getBackend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + refresh: async (id) => ({ id, provider: "different" }), + }; + const getClient = new Client({ + provider: { name: getBackend.name, initialize: () => getBackend }, + }); + await expect(getClient.get("requested")).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "get", + field: ConfigFieldProvider, + }); + }); + + it("preserves a handle when a lifecycle response changes its identity", async () => { + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + refresh: async () => ({ id: "different", provider: "test" }), + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + const sandbox = await client.attach("requested"); + + await expect(sandbox.refresh()).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "refresh", + field: InfoFieldId, + }); + expect(sandbox.id).toBe("requested"); + }); +}); + describe("Shared request validation", () => { it("exposes validateCreateOptions for public CreateOptions", () => { expect(() => validateCreateOptions({})).not.toThrow(); @@ -326,6 +435,48 @@ describe("Shared request validation", () => { }); describe("Public create options for providers", () => { + it("keeps nested create input owned by the caller", async () => { + const request: CreateOptions = { + environment: { ORIGINAL: "value" }, + runtime: { entrypoint: ["echo"] }, + }; + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + create: async (providerRequest) => { + providerRequest.environment!.ORIGINAL = "mutated"; + providerRequest.runtime!.entrypoint!.push("mutated"); + return { sandbox: { id: "sandbox-1", provider: "test" } }; + }, + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + + await client.create(request); + + expect(request).toEqual({ + environment: { ORIGINAL: "value" }, + runtime: { entrypoint: ["echo"] }, + }); + }); + + it("keeps nested client configuration owned by the caller", () => { + const auth = { apiKey: { key: "secret" } } as const; + const provider: Provider = { + name: "test", + initialize: (config) => { + config.auth!.apiKey!.key = "mutated"; + return new RecordingBackend(); + }, + }; + + new Client({ provider, auth }); + + expect(auth.apiKey.key).toBe("secret"); + }); + it("hands the provider public create options after a create call", async () => { const { client, backend } = fixture(); @@ -352,7 +503,153 @@ describe("Public create options for providers", () => { }); }); +describe("Provider input ownership", () => { + it("captures queued filesystem options before callers can change them", async () => { + let release!: () => void; + const blocked = new Promise((resolve) => { release = resolve; }); + let removed: { recursive?: boolean; timeout?: number } | undefined; + let directory: { parents?: boolean; timeout?: number } | undefined; + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + start: async (id) => { await blocked; return { id, provider: "test" }; }, + remove: async (_id, _path, request) => { removed = request; }, + makeDirectory: async (_id, _path, request) => { directory = request; }, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + const sandbox = await client.attach("sandbox-1"); + const first = sandbox.start(); + const removeOptions = { recursive: false, timeout: 60_000 }; + const mkdirOptions = { parents: false, timeout: 60_000 }; + const remove = sandbox.filesystem.remove("/data", removeOptions); + const mkdir = sandbox.filesystem.makeDirectory("/directory", mkdirOptions); + removeOptions.recursive = true; + removeOptions.timeout = 1; + mkdirOptions.parents = true; + mkdirOptions.timeout = 1; + release(); + await Promise.all([first, remove, mkdir]); + expect(removed).toEqual({ recursive: false, timeout: 60_000 }); + expect(directory).toEqual({ parents: false, timeout: 60_000 }); + }); + it("keeps process arrays and maps owned by the caller", async () => { + const argv = ["echo", "hello"]; + const environment = { ORIGINAL: "value" }; + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + run: async (_id, request) => { + request.argv!.push("mutated"); + request.environment!.ORIGINAL = "mutated"; + return { exitCode: 0 }; + }, + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + const sandbox = await client.attach("sandbox-1"); + + await sandbox.process.run({ argv, environment }); + + expect(argv).toEqual(["echo", "hello"]); + expect(environment).toEqual({ ORIGINAL: "value" }); + }); + + it("keeps write buffers owned by the caller", async () => { + const data = Buffer.from([1, 2, 3]); + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + writeFile: async (_id, _path, providerData) => { + providerData[0] = 9; + }, + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + const sandbox = await client.attach("sandbox-1"); + + await sandbox.filesystem.writeFile("/tmp/file", data); + + expect([...data]).toEqual([1, 2, 3]); + }); + + it("captures process and file inputs before queued operations run", async () => { + let release!: () => void; + const blocked = new Promise((resolve) => { release = resolve; }); + let observedArgv: string[] | undefined; + let observedEnvironment: Record | undefined; + let observedData: number[] | undefined; + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + start: async (id) => { await blocked; return { id, provider: "test" }; }, + run: async (_id, request) => { + observedArgv = request.argv; + observedEnvironment = request.environment; + return { exitCode: 0 }; + }, + writeFile: async (_id, _path, data) => { observedData = [...data]; }, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + const sandbox = await client.attach("sandbox-1"); + const first = sandbox.start(); + const argv = ["echo", "hello"]; + const environment = { KEY: "original" }; + const data = Buffer.from([1, 2, 3]); + const command = sandbox.process.run({ argv, environment }); + const write = sandbox.filesystem.writeFile("/tmp/file", data); + argv.length = 0; + environment.KEY = "changed"; + data[0] = 9; + release(); + await Promise.all([first, command, write]); + expect(observedArgv).toEqual(["echo", "hello"]); + expect(observedEnvironment).toEqual({ KEY: "original" }); + expect(observedData).toEqual([1, 2, 3]); + }); +}); + describe("Shared client configuration validation", () => { + it.each([ + ["https://api.example.test/api", true], + ["http://127.0.0.1:1234", true], + ["http://[::1]:1234", true], + ["http://[0:0:0:0:0:0:0:1]:1234", true], + ["http://localhost:1234", true], + ["http://LOCALHOST:1234", true], + ["http://api.example.test", false], + ["http://127.0.0.1.evil.test", false], + ["http://0.0.0.0:1234", false], + ["https://user:secret@api.example.test", false], + ["https://api.example.test/#fragment", false], + ["http://@localhost", false], + ["http://127.1", false], + ])("validates secure endpoint %s as %s", (endpoint, valid) => { + const provider: Provider = { + name: "test", + initialize: () => new RecordingBackend(), + }; + const construct = () => new Client({ provider, endpoint }); + + if (valid) expect(construct).not.toThrow(); + else + expect(construct).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldEndpoint, + }), + ); + }); + it("rejects insecure remote endpoints before initializing the provider", () => { const backend = new RecordingBackend(); let initialized = false; @@ -405,6 +702,62 @@ describe("Shared client configuration validation", () => { }); describe("Operation cancellation", () => { + it("observes a provider rejection after a synchronous abort", async () => { + const controller = new AbortController(); + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + create: () => { + controller.abort(); + return Promise.reject(new Error("provider failure")); + }, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + await expect(client.create({}, { signal: controller.signal })).rejects.toMatchObject({ + kind: ErrorKind.Canceled, + }); + // Give Node an event-loop turn to detect an unhandled provider rejection. + await new Promise((resolve) => setImmediate(resolve)); + }); + + it("removes its listener from the original lifecycle signal", async () => { + let release!: () => void; + const blocked = new Promise((resolve) => { release = resolve; }); + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + refresh: async (id) => { await blocked; return { id, provider: "test" }; }, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + const sandbox = await client.attach("sandbox-1"); + const first = new AbortController(); + const second = new AbortController(); + const options = { signal: first.signal }; + const pending = sandbox.refresh(options); + options.signal = second.signal; + release(); + await pending; + expect(getEventListeners(first.signal, "abort")).toHaveLength(0); + expect(getEventListeners(second.signal, "abort")).toHaveLength(0); + }); + it("keeps long timeouts active until the requested deadline", async () => { + vi.useFakeTimers(); + try { + const { client } = hangingFixture(); + const promise = client.create({}, { timeout: 2_147_483_648 }); + const assertion = expect(promise).rejects.toMatchObject({ kind: ErrorKind.Timeout }); + await vi.advanceTimersByTimeAsync(2_147_483_647); + expect(vi.getTimerCount()).toBe(1); + await vi.advanceTimersByTimeAsync(1); + await assertion; + expect(vi.getTimerCount()).toBe(0); + } finally { + vi.useRealTimers(); + } + }); it("does not invoke the provider for an already-aborted operation", async () => { const { client, backend } = fixture(); const controller = new AbortController(); diff --git a/sdks/typescript/sandbox/src/client/client.ts b/sdks/typescript/sandbox/src/client/client.ts index cf9560c5..f97a68d4 100644 --- a/sdks/typescript/sandbox/src/client/client.ts +++ b/sdks/typescript/sandbox/src/client/client.ts @@ -19,6 +19,7 @@ import { awaitWithSignal, operationOptions, operationSignal, + clone, } from "#client/operation.js"; import { Sandbox, sandboxConstructorToken } from "#client/sandbox.js"; import type { Backend, ClientConfig, OperationOptions } from "#client/types.js"; @@ -69,7 +70,7 @@ export class Client implements AsyncDisposable { this.#timeout = config.timeout; try { const { provider: _provider, ...shared } = config; - this.#backend = config.provider.initialize(shared); + this.#backend = config.provider.initialize(clone(shared)); } catch (error) { throw withContext(error, config.provider.name, "initialize"); } @@ -93,13 +94,14 @@ export class Client implements AsyncDisposable { }); } } - /** `"daytona"` or `"modal"`. */ + /** `"daytona"`, `"modal"`, or `"blaxel"`. */ get providerName(): string { return this.#providerName; } /** * Returns a handle for an existing sandbox ID without looking it up. - * Modal supports attach. Daytona throws `ErrorKind.Unsupported`. Use `get`. + * Modal supports attach. Daytona and Blaxel throw `ErrorKind.Unsupported`. + * Use `get`. */ async attach(id: string, options: OperationOptions = {}): Promise { return this.#openById("attach", id, options, (fetchOptions) => { @@ -109,7 +111,8 @@ export class Client implements AsyncDisposable { } /** * Looks up a sandbox by ID and returns a handle the provider has verified. - * Daytona supports get. Modal throws `ErrorKind.Unsupported`. Use `attach`. + * Daytona and Blaxel support get. Modal throws `ErrorKind.Unsupported`. Use + * `attach`. */ async get(id: string, options: OperationOptions = {}): Promise { return this.#openById("get", id, options, (fetchOptions) => { @@ -138,9 +141,27 @@ export class Client implements AsyncDisposable { () => fetch(operationOptions(options, scoped?.signal)), scoped.signal, ); + if (!info?.id || info.id !== id) + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: this.providerName, + operation, + field: InfoFieldId, + message: + "sandbox-kit: provider returned a different sandbox identity", + }); + if (info.provider && info.provider !== this.providerName) + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: this.providerName, + operation, + field: ConfigFieldProvider, + message: + "sandbox-kit: response provider differs from selected provider", + }); return new Sandbox(sandboxConstructorToken, { backend: this.#backend, - info: { ...info, id, provider: this.providerName }, + info: { ...info, provider: this.providerName }, ...(this.#timeout === undefined ? {} : { timeout: this.#timeout }), }); } catch (error) { @@ -152,7 +173,7 @@ export class Client implements AsyncDisposable { /** * Creates one sandbox and returns its handle. * An empty request uses the provider default. Daytona uses its default - * snapshot. Modal requires an image. + * snapshot. Modal requires an image. Blaxel requires a name and an image. */ async create( request: CreateOptions = {}, @@ -168,7 +189,7 @@ export class Client implements AsyncDisposable { const create = this.#backend.create; if (!create) unsupported(this.providerName, "create"); const providerRequest: CreateOptions = { - ...request, + ...clone(request), ...(timeout === undefined ? {} : { @@ -190,7 +211,7 @@ export class Client implements AsyncDisposable { ), scoped.signal, ); - if (!result.sandbox?.id) + if (!result?.sandbox?.id) throw sandboxError({ kind: ErrorKind.InvalidResponse, provider: this.providerName, diff --git a/sdks/typescript/sandbox/src/client/errors.ts b/sdks/typescript/sandbox/src/client/errors.ts index 6646041e..7a35c003 100644 --- a/sdks/typescript/sandbox/src/client/errors.ts +++ b/sdks/typescript/sandbox/src/client/errors.ts @@ -63,8 +63,8 @@ export function withContext( return sandboxError( { kind: error.kind as ErrorInfo["kind"], - provider: error.provider || provider, - operation: error.operation || operation, + provider: provider || error.provider, + operation: operation || error.operation, field: error.field, message: error.message, ...(error.rule === undefined ? {} : { rule: error.rule }), @@ -78,7 +78,7 @@ export function withContext( ? {} : { providerSource: error.providerSource }), }, - error.cause, + error, ); } if (error instanceof DOMException && error.name === "AbortError") diff --git a/sdks/typescript/sandbox/src/client/filesystem.ts b/sdks/typescript/sandbox/src/client/filesystem.ts index 7f1bed70..fb75e59b 100644 --- a/sdks/typescript/sandbox/src/client/filesystem.ts +++ b/sdks/typescript/sandbox/src/client/filesystem.ts @@ -43,6 +43,7 @@ export class Filesystem { path: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#call("read_file", path, options, (signal) => { const method = this.host.backend.readFile; if (!method) unsupported(this.host.providerName, "read_file"); @@ -62,6 +63,8 @@ export class Filesystem { data: Uint8Array, options: FileCall = {}, ): Promise { + options = { ...options }; + const providerData = new Uint8Array(data); return this.#call("write_file", path, options, (signal) => { const method = this.host.backend.writeFile; if (!method) unsupported(this.host.providerName, "write_file"); @@ -69,7 +72,7 @@ export class Filesystem { this.host.backend, this.host.id, path, - data, + providerData, requestFields(options), operationOptions(options, signal), ); @@ -82,6 +85,7 @@ export class Filesystem { remotePath: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#copy( "copy_from_local", localPath, @@ -112,6 +116,7 @@ export class Filesystem { localPath: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#copy( "copy_to_local", localPath, @@ -135,12 +140,13 @@ export class Filesystem { /** * Creates the directory at `path`. * An omitted `parents` flag is true: missing parents are created. - * Daytona rejects `false`. + * Daytona and Blaxel reject `false`. */ makeDirectory( path: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#call("make_directory", path, options, (signal) => { const method = this.host.backend.makeDirectory; if (!method) unsupported(this.host.providerName, "make_directory"); @@ -159,6 +165,7 @@ export class Filesystem { path: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#call("list_directory", path, options, (signal) => { const method = this.host.backend.listDirectory; if (!method) unsupported(this.host.providerName, "list_directory"); @@ -174,6 +181,7 @@ export class Filesystem { /** Returns metadata for one path. A symlink result describes the symlink. */ stat(path: string, options: FileCall = {}): Promise { + options = { ...options }; return this.#call("stat", path, options, (signal) => { const method = this.host.backend.stat; if (!method) unsupported(this.host.providerName, "stat"); @@ -192,6 +200,7 @@ export class Filesystem { * An omitted `recursive` flag removes a file or an empty directory. */ remove(path: string, options: FileCall = {}): Promise { + options = { ...options }; return this.#call("remove", path, options, (signal) => { const method = this.host.backend.remove; if (!method) unsupported(this.host.providerName, "remove"); diff --git a/sdks/typescript/sandbox/src/client/operation.ts b/sdks/typescript/sandbox/src/client/operation.ts index 1185b01b..2cd5c09d 100644 --- a/sdks/typescript/sandbox/src/client/operation.ts +++ b/sdks/typescript/sandbox/src/client/operation.ts @@ -22,27 +22,36 @@ export function operationSignal( message: "sandbox-kit: timeout must be a finite non-negative number", }); } - if ((timeout === undefined || timeout === 0) && options.signal === undefined) + const parentSignal = options.signal; + if ((timeout === undefined || timeout === 0) && parentSignal === undefined) return { dispose() {} }; const controller = new AbortController(); - const abort = () => controller.abort(options.signal?.reason); - if (options.signal?.aborted) abort(); - else options.signal?.addEventListener("abort", abort, { once: true }); - const timer = - timeout !== undefined && timeout > 0 - ? setTimeout( - () => - controller.abort( - new DOMException("The operation timed out", "TimeoutError"), - ), - timeout, - ) - : undefined; + const abort = () => controller.abort(parentSignal?.reason); + if (parentSignal?.aborted) abort(); + else parentSignal?.addEventListener("abort", abort, { once: true }); + let timer: ReturnType | undefined; + if (timeout !== undefined && timeout > 0) { + let remaining = timeout; + let started = performance.now(); + const schedule = () => { + timer = setTimeout(() => { + const now = performance.now(); + remaining -= now - started; + if (remaining <= 0) { + controller.abort(new DOMException("The operation timed out", "TimeoutError")); + } else { + started = now; + schedule(); + } + }, Math.min(remaining, 2_147_483_647)); + }; + schedule(); + } return { signal: controller.signal, dispose() { if (timer !== undefined) clearTimeout(timer); - options.signal?.removeEventListener("abort", abort); + parentSignal?.removeEventListener("abort", abort); }, }; } @@ -83,7 +92,11 @@ export async function awaitWithSignal( } function raceSignal(pending: Promise, signal?: AbortSignal): Promise { - if (signal?.aborted) throw abortReason(signal); + if (signal?.aborted) { + // The operation has already started; observe its eventual rejection. + void pending.catch(() => {}); + throw abortReason(signal); + } if (signal === undefined) return pending; return new Promise((resolve, reject) => { const aborted = () => reject(abortReason(signal)); diff --git a/sdks/typescript/sandbox/src/client/process.ts b/sdks/typescript/sandbox/src/client/process.ts index 0388b245..33a01bed 100644 --- a/sdks/typescript/sandbox/src/client/process.ts +++ b/sdks/typescript/sandbox/src/client/process.ts @@ -41,15 +41,17 @@ export class Process { * A non-zero exit code is not an error. */ async run(options: ProcessCall): Promise { - validateExecOptions(this.host.providerName, "run", options); - return this.host.run("run", options, (signal) => { + const request = requestFields(options); + const callOptions = operationOptions(options, options.signal); + validateExecOptions(this.host.providerName, "run", request); + return this.host.run("run", callOptions, (signal) => { const method = this.host.backend.run; if (!method) unsupported(this.host.providerName, "run"); return method.call( this.host.backend, this.host.id, - requestFields(options), - operationOptions(options, signal), + request, + operationOptions(callOptions, signal), ); }); } @@ -105,13 +107,13 @@ function validateExecOptions( function requestFields(options: ProcessCall): ExecOptions { const request: ExecOptions = {}; - if (options.argv !== undefined) request.argv = options.argv; + if (options.argv !== undefined) request.argv = [...options.argv]; if (options.shell !== undefined) request.shell = options.shell; if (options.workingDirectory !== undefined) { request.workingDirectory = options.workingDirectory; } if (options.environment !== undefined) { - request.environment = options.environment; + request.environment = { ...options.environment }; } if (options.timeout !== undefined) request.timeout = options.timeout; return request; diff --git a/sdks/typescript/sandbox/src/client/sandbox.ts b/sdks/typescript/sandbox/src/client/sandbox.ts index 76ce87cc..c6f75a1b 100644 --- a/sdks/typescript/sandbox/src/client/sandbox.ts +++ b/sdks/typescript/sandbox/src/client/sandbox.ts @@ -12,7 +12,8 @@ import { type StopOptions, type TerminateOptions, } from "#contracts/sandbox_data_contracts.js"; -import { unsupported, withContext } from "#client/errors.js"; +import { InfoFieldId, InfoFieldProvider } from "#contracts/field_paths.js"; +import { ErrorKind, sandboxError, unsupported, withContext } from "#client/errors.js"; import { awaitWithSignal, clone, @@ -98,7 +99,7 @@ export class Sandbox implements AsyncDisposable { get id(): string { return this.#info.id; } - /** `"daytona"` or `"modal"`. */ + /** `"daytona"`, `"modal"`, or `"blaxel"`. */ get providerName(): string { return this.#info.provider; } @@ -124,7 +125,7 @@ export class Sandbox implements AsyncDisposable { } /** * Loads current metadata from the provider and stores it on this handle. - * Daytona supports refresh. Modal throws `ErrorKind.Unsupported`. + * Daytona and Blaxel support refresh. Modal throws `ErrorKind.Unsupported`. */ async refresh(options: OperationOptions = {}): Promise { return this.#serialized( @@ -135,6 +136,7 @@ export class Sandbox implements AsyncDisposable { const method = this.#backend.refresh; if (!method) unsupported(this.providerName, "refresh"); return this.#storeInfo( + "refresh", await method.call( this.#backend, this.id, @@ -147,7 +149,7 @@ export class Sandbox implements AsyncDisposable { /** * Starts a stopped sandbox and stores the updated metadata on this handle. * An omitted `timeout` uses the client timeout as this call's deadline. - * Daytona supports start. Modal throws `ErrorKind.Unsupported`. + * Daytona supports start. Modal and Blaxel throw `ErrorKind.Unsupported`. */ async start(options: OperationOptions = {}): Promise { return this.#update( @@ -160,7 +162,7 @@ export class Sandbox implements AsyncDisposable { /** * Stops a sandbox and stores the updated metadata on this handle. * Set `force` to request an immediate stop. - * Daytona supports stop. Modal throws `ErrorKind.Unsupported`. + * Daytona supports stop. Modal and Blaxel throw `ErrorKind.Unsupported`. */ async stop(options: StopCallOptions = {}): Promise { const { force, ...call } = options; @@ -176,7 +178,8 @@ export class Sandbox implements AsyncDisposable { } /** * Pauses a sandbox and stores the updated metadata on this handle. - * Daytona accepts or rejects the sandbox. Modal throws `ErrorKind.Unsupported`. + * Daytona accepts or rejects the sandbox. Modal and Blaxel throw + * `ErrorKind.Unsupported`. */ async pause(options: OperationOptions = {}): Promise { return this.#update( @@ -190,7 +193,8 @@ export class Sandbox implements AsyncDisposable { * Ends a sandbox. The result includes `exitCode` when `wait` is set. * An omitted `timeout` uses the client timeout as this call's deadline. * Modal waits on that deadline and does not take a separate terminate duration. - * Modal supports terminate. Daytona throws `ErrorKind.Unsupported`. Use `delete` or `stop`. + * Modal supports terminate. Daytona and Blaxel throw `ErrorKind.Unsupported`. + * Use `delete` or `stop` on Daytona; `delete` on Blaxel. */ async terminate(options: TerminateCallOptions = {}): Promise { const { wait, ...call } = options; @@ -218,7 +222,8 @@ export class Sandbox implements AsyncDisposable { * Archives a sandbox and stores the updated metadata on this handle. * Daytona archive does not take a separate duration. The call still uses * `timeout` as its deadline. - * Daytona accepts or rejects the sandbox. Modal throws `ErrorKind.Unsupported`. + * Daytona accepts or rejects the sandbox. Modal and Blaxel throw + * `ErrorKind.Unsupported`. */ async archive(options: OperationOptions = {}): Promise { return this.#update( @@ -230,8 +235,10 @@ export class Sandbox implements AsyncDisposable { } /** * Deletes the cloud sandbox and stores the metadata the provider returns. - * Set `wait` to wait until deletion finishes. `close` leaves the sandbox in place. - * Daytona supports delete. Modal throws `ErrorKind.Unsupported`. Use `terminate`. + * Daytona and Blaxel honor `wait` to block until deletion finishes. Blaxel + * without `wait` returns `deleted` / `terminal: true` as soon as delete is + * accepted. `close` leaves the sandbox in place. Modal throws + * `ErrorKind.Unsupported` — use `terminate`. */ async delete(options: DeleteCallOptions = {}): Promise { const { wait, ...call } = options; @@ -247,7 +254,8 @@ export class Sandbox implements AsyncDisposable { } /** * Closes this handle. The sandbox stays as it is, and you can keep using the handle. - * Closing a Daytona handle does not contact Daytona. Closing a Modal handle does not terminate the sandbox. + * Closing a Daytona or Blaxel handle does not contact the provider. Closing a + * Modal handle does not terminate the sandbox. */ async close(options: OperationOptions = {}): Promise { await this.#serialized("close", options, this.#timeout, (signal) => @@ -286,6 +294,7 @@ export class Sandbox implements AsyncDisposable { async (signal) => { if (!method) unsupported(this.providerName, operation); return this.#storeInfo( + operation, await method.call( this.#backend, this.id, @@ -296,7 +305,23 @@ export class Sandbox implements AsyncDisposable { }, ); } - #storeInfo(info: SandboxInfo): SandboxInfo { + #storeInfo(operation: string, info: SandboxInfo): SandboxInfo { + if (!info?.id || info.id !== this.id) + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: this.providerName, + operation, + field: InfoFieldId, + message: "sandbox-kit: provider returned a different sandbox identity", + }); + if (info.provider && info.provider !== this.providerName) + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: this.providerName, + operation, + field: InfoFieldProvider, + message: "sandbox-kit: provider returned inconsistent sandbox metadata", + }); const stored = clone(info); stored.provider = this.providerName; this.#info = stored; diff --git a/sdks/typescript/sandbox/src/client/types.ts b/sdks/typescript/sandbox/src/client/types.ts index 6298a7b0..8b1d4bd7 100644 --- a/sdks/typescript/sandbox/src/client/types.ts +++ b/sdks/typescript/sandbox/src/client/types.ts @@ -177,7 +177,7 @@ export interface Provider { initialize(config: Config): Backend; } -/** Settings for `new Client`, including the provider (`daytona()` or `modal()`). */ +/** Settings for `new Client`, including the provider (`daytona()`, `modal()`, or `blaxel()`). */ export type ClientConfig = Config & { readonly provider: Provider; }; diff --git a/sdks/typescript/sandbox/src/client/validation.ts b/sdks/typescript/sandbox/src/client/validation.ts index 914b225d..de12616b 100644 --- a/sdks/typescript/sandbox/src/client/validation.ts +++ b/sdks/typescript/sandbox/src/client/validation.ts @@ -87,15 +87,30 @@ function validationError(input: ValidationErrorInput): never { function validSecureEndpoint(value: string): boolean { try { const url = new URL(value); - if (!url.hostname || url.username || url.password || url.hash) return false; + const authorityStart = value.indexOf("://"); + if (authorityStart < 0) return false; + const authority = value + .slice(authorityStart + 3) + .split(/[/?#]/, 1)[0]!; + if ( + !url.hostname || + authority.includes("@") || + url.username || + url.password || + url.hash + ) + return false; if (url.protocol === "https:") return true; if (url.protocol !== "http:") return false; const host = url.hostname.toLowerCase(); + const rawHost = authority.startsWith("[") + ? authority.slice(0, authority.indexOf("]") + 1).toLowerCase() + : authority.split(":", 1)[0]!.toLowerCase(); return ( - host === "localhost" || + (host === "localhost" && rawHost === host) || host === "[::1]" || host === "::1" || - /^127(?:\.[0-9]{1,3}){3}$/.test(host) + (/^127(?:\.[0-9]{1,3}){3}$/.test(host) && rawHost === host) ); } catch { return false; diff --git a/sdks/typescript/sandbox/src/contracts/config_paths.ts b/sdks/typescript/sandbox/src/contracts/config_paths.ts index 6372fd0c..73f98263 100644 --- a/sdks/typescript/sandbox/src/contracts/config_paths.ts +++ b/sdks/typescript/sandbox/src/contracts/config_paths.ts @@ -65,6 +65,9 @@ export const ConfigFieldScopeOrganizationId = "scope.organization_id" as const; /** Canonical path "scope.project_id". */ export const ConfigFieldScopeProjectId = "scope.project_id" as const; +/** Canonical path "scope.workspace". */ +export const ConfigFieldScopeWorkspace = "scope.workspace" as const; + /** Canonical path "timeout". */ export const ConfigFieldTimeout = "timeout" as const; diff --git a/specs/client.yaml b/specs/client.yaml index c2837f41..411428be 100644 --- a/specs/client.yaml +++ b/specs/client.yaml @@ -56,3 +56,4 @@ validation: environment: { nonblank: true } organization_id: { nonblank: true } project_id: { nonblank: true } + workspace: { nonblank: true } diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml new file mode 100644 index 00000000..b4ebe9eb --- /dev/null +++ b/specs/providers/blaxel.yaml @@ -0,0 +1,367 @@ +version: 1 +provider: blaxel +# Create checks are handwritten. Name/image/placement/resources/env live in +# provider_checks; readiness/security/observability/provisioning/ports/network/ +# runtime/isolation/lifetime live in create mapping (Go creation.go, TS +# mappings/mappings.ts). Keep this list in sync; it is not emitted. +checks: + - { + path: name, + required: true, + format: blaxel_sandbox_name, + notes: "lowercase alphanumeric with single hyphens, max 49", + } + - { path: source.image, required: true } + - { path: source.snapshot, unsupported: true } + - { path: source.warm_pool, unsupported: true } + - { path: environment, format: env_name } + - { path: placement, unsupported: true } + - { path: resources.cpu_cores, unsupported: true } + - { path: resources.cpu_limit_cores, unsupported: true } + - { path: resources.memory_limit_mib, unsupported: true } + - { path: resources.disk_mib, unsupported: true } + - { + path: runtime, + unsupported: true, + notes: "working_directory, user, language, version, entrypoint, and PTY; enforced in create mapping", + } + - { + path: isolation, + notes: "Default-only / empty is a no-op; non-default kind, providerKind, and nestedVirtualization rejected in create mapping", + } + - { + path: lifetime.maximum_lifetime, + notes: "whole seconds ≥5m (Blaxel ttl-max-age floor); enforced in create mapping", + } + - { + path: lifetime.idle_terminate, + notes: "PolicyModeDisabled rejected; After maps to ttl-idle ≥60s; enforced in create mapping", + } + - { + path: lifetime.ephemeral, + unsupported: true, + notes: "with idle_stop, idle_pause, stopped_archive, and stopped_delete", + } + - { + path: network.ports.port, + notes: "reject reserved 80, 443, and 8080; enforced in create mapping", + } + - { + path: network.ports.transport, + allowed: [default, plain, tls], + notes: "Default omits protocol (Blaxel HTTP); Plain→TCP; TLS→TLS; Http2Tls → Unsupported in create mapping", + } + - { + path: network.outbound_domains.entries, + format: domain, + notes: "mapped to allowedDomains; wildcard prefix *. is stripped for format check", + } + - { + path: network.outbound_cidrs, + unsupported: true, + notes: "with outbound_proxy_url, inbound_cidrs, private_network, and related fields", + } + - { path: readiness, unsupported: true } + - { path: security, unsupported: true } + - { path: observability, unsupported: true } + - { path: provisioning.queue_timeout, unsupported: true } + - { + path: provisioning.timeout, + notes: "Kit create deadline; when whole seconds 1..50 also X-Blaxel-Creation-Timeout / SandboxInstance.create timeout so Blaxel frees the name", + } + - { + path: provisioning.wait_for, + allowed: [default, scheduled], + notes: "Ready/Started/Submitted rejected; enforced in create mapping", + } +client: + target: + name: ClientOptions + bindings: + go: { import: github.com/blaxel-ai/sdk-go/option, name: RequestOption } + # Handwritten TS maps into @blaxel/core settings Config (process-global). + typescript: { import: "@blaxel/core", name: Config } + managed: [provider, timeout] + retained: [region] + rejected: + { + endpoint: "Endpoint cannot be configured through this SDK's public client constructor", + } + scope: { field: scope, group: mapClientScope, retained: [workspace] } + auth: + - { field: api_key, group: mapAPIKeyCredentials } +runtime: + go: + # Native SDK entry used by Provider.NewClient. + constructor: { function: NewClient } + state: + - { + name: region, + type: string, + optional: true, + capture: region, + doc: "is the captured default region. Nil means the caller omitted it; create requires a region.", + } + cleanup: { method: Close, accepts_context: true, returns_error: false } + typescript: + # @blaxel/core has no client class; credentials go through process-global settings. + constructor: { function: settings.setConfig } + state: + - { + name: region, + type: string, + optional: true, + capture: region, + doc: "is the captured default region. Omission means create rejects with Config.region required.", + } + cleanup: { method: close, accepts_context: false, returns_error: false } +# Mapping groups are handwritten in sdks/*/providers/blaxel (provider_mappings +# plus create mapping for image/region/ports/network). Keep in sync; not emitted. +groups: + - name: mapClientScope + direction: request + source: { name: Scope } + target: + name: ClientOptions + bindings: + go: + import: github.com/blaxel-ai/sdk-go/option + name: RequestOption + fields: { workspace: WithWorkspace } + typescript: + import: "@blaxel/core" + name: Config + fields: { workspace: workspace } + fields: + - { from: workspace, to: workspace } + - name: mapAPIKeyCredentials + direction: request + source: { name: APIKeyCredentials } + target: + name: ClientOptions + bindings: + go: + import: github.com/blaxel-ai/sdk-go/option + name: RequestOption + fields: { key: WithAPIKey } + typescript: + import: "@blaxel/core" + name: Config + # Handwritten field is apiKey; @blaxel/core Config documents apikey. + fields: { key: apiKey } + fields: + - { from: key, to: key } + - name: mapCommonCreateFields + direction: request + source: { name: CreateOptions } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: + { + name: Sandbox.Metadata.Name, + environment: Sandbox.Spec.Runtime.Envs, + labels: Sandbox.Metadata.Labels, + } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { name: name, environment: envs, labels: labels } + fields: + - { from: name, to: name } + - { from: environment, to: environment } + - { from: labels, to: labels } + - name: mapImage + direction: request + source: { name: ImageSource } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { image: Sandbox.Spec.Runtime.Image } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { image: image } + fields: + - { from: reference, to: image } + - name: mapCreateRegion + direction: request + source: { name: Config } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { region: Sandbox.Spec.Region } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { region: region } + fields: + - { + from: region, + to: region, + notes: "required for create; taken from captured client Config.region", + } + # memory_mib → Blaxel decimal MB. Request mapping rounds up; response mapping + # reports the whole MiB guaranteed by the returned MB value. + - name: mapResources + direction: request + source: { name: Resources } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { memory: Sandbox.Spec.Runtime.Memory } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { memory: memory } + fields: + - { + from: memory_mib, + to: memory, + cast: integer, + notes: "reject 0 and unrepresentable values; converts MiB to decimal MB by rounding up", + } + - name: mapLifetimeDuration + direction: request + source: { name: LifetimePolicy } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { ttl: Sandbox.Spec.Runtime.Ttl } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { ttl: ttl } + fields: + - { + from: maximum_lifetime, + to: ttl, + transform: duration_blaxel, + notes: "whole seconds ≥5m", + } + - name: mapIdleDuration + error_path: lifetime.idle_terminate + direction: request + source: { name: AutomaticAction } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { lifecycle: Sandbox.Spec.Lifecycle.ExpirationPolicies } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { lifecycle: lifecycle } + fields: + - { from: after, to: lifecycle, transform: duration_blaxel_idle } + - name: mapPorts + direction: request + source: { name: PortBinding } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { ports: Sandbox.Spec.Runtime.Ports } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { ports: ports } + fields: + - { from: port, to: target } + - { + from: transport, + to: protocol, + notes: "Default omits protocol; Plain→TCP; TLS→TLS", + } + - name: mapOutboundDomains + direction: request + source: { name: NetworkConfig } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { outbound_domains: Sandbox.Spec.Network.AllowedDomains } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { outbound_domains: network.allowedDomains } + fields: + - { from: outbound_domains, to: outbound_domains } + - name: mapSandboxInfo + direction: response + source: + name: Sandbox + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: Sandbox + fields: + { + id: Metadata.Name, + name: Metadata.Name, + state: Status, + region: Spec.Region, + labels: Metadata.Labels, + } + typescript: + import: "@blaxel/core" + name: Sandbox + fields: + { + id: metadata.name, + name: metadata.name, + state: status, + region: spec.region, + labels: metadata.labels, + } + target: { name: SandboxInfo } + fields: + - { from: id, to: id } + - { from: name, to: name } + - { + from: state, + to: provider_state, + cast: string, + notes: "Status preferred; runtime State used when Status empty; DELETED/TERMINATED/DEACTIVATED → Deleted with terminal=true", + } + - { from: region, to: region } + - { from: labels, to: labels } + - name: mapAllocatedResources + direction: response + when: + - { field: memory, greater_than: 0 } + source: + name: Sandbox + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: Sandbox + fields: { memory: Spec.Runtime.Memory } + typescript: + import: "@blaxel/core" + name: Sandbox + fields: { memory: spec.runtime.memory } + target: { name: Resources } + fields: + - { from: memory, to: memory_mib, cast: number, notes: "decimal MB to whole guaranteed MiB" } diff --git a/tooling/README.md b/tooling/README.md index d22d9489..046da515 100644 --- a/tooling/README.md +++ b/tooling/README.md @@ -54,13 +54,14 @@ descriptors; SDKs evaluate create and client-config rules at runtime exports compiled descriptors from `dist/` via `@sandbox-kit/contracts/sandbox` and `@sandbox-kit/contracts/client`. -`test go` tests the public SDK, tooling, both providers, and every independent -Go example module registered by the command. It also checks that `go.work.sum` -matches the checksums written by loading those modules' packages. Example tests -use dummy credentials and local servers; they create no cloud resources. +`test go` tests the public SDK, tooling, all providers (Daytona, Modal, Blaxel), +and every independent Go example module registered by the command. It also +checks that `go.work.sum` matches the checksums written by loading those +modules' packages. Example tests use dummy credentials and local servers; they +create no cloud resources. `test typescript` builds contracts first, then builds and tests the shared -package and both providers, discovers every TypeScript example containing a +package and all providers, discovers every TypeScript example containing a `package.json`, and builds each example without making provider calls. Install TypeScript workspace deps once from `sdks/typescript/` (`npm install`) before running it. Manual npm publish steps live in diff --git a/tooling/internal/commands/commands_test.go b/tooling/internal/commands/commands_test.go index 258a635a..4db7b464 100644 --- a/tooling/internal/commands/commands_test.go +++ b/tooling/internal/commands/commands_test.go @@ -210,7 +210,7 @@ func TestTypeScriptVerificationBuildsBeforeTests(t *testing.T) { if err := command.ExecuteContext(context.Background()); err != nil { t.Fatal(err) } - if len(runner.steps) != 9 { + if len(runner.steps) != 11 { t.Fatalf("unexpected TypeScript verification steps: %#v", runner.steps) } for _, task := range runner.steps { @@ -218,7 +218,8 @@ func TestTypeScriptVerificationBuildsBeforeTests(t *testing.T) { t.Fatalf("TypeScript verification did not use configured npm: %#v", runner.steps) } } - if strings.Join(runner.steps[0].args, " ") != "run build" || strings.Join(runner.steps[1].args, " ") != "run build" || strings.Join(runner.steps[2].args, " ") != "test" || strings.Join(runner.steps[8].args, " ") != "run build" { + last := len(runner.steps) - 1 + if strings.Join(runner.steps[0].args, " ") != "run build" || strings.Join(runner.steps[1].args, " ") != "run build" || strings.Join(runner.steps[2].args, " ") != "test" || strings.Join(runner.steps[last].args, " ") != "run build" { t.Fatalf("TypeScript verification order is incorrect: %#v", runner.steps) } } diff --git a/tooling/internal/commands/test.go b/tooling/internal/commands/test.go index 6c67b81a..b67a4264 100644 --- a/tooling/internal/commands/test.go +++ b/tooling/internal/commands/test.go @@ -10,7 +10,7 @@ import ( ) var goTestModules = []string{ - "sdks/go/sandbox", "tooling", "sdks/go/providers/modal", "sdks/go/providers/daytona", + "sdks/go/sandbox", "tooling", "sdks/go/providers/modal", "sdks/go/providers/daytona", "sdks/go/providers/blaxel", "examples/go/modal/configuration/create-modal-sandbox", "examples/go/daytona/configuration/create-daytona-sandbox", "examples/go/modal/configuration/create-modal-with-resources", "examples/go/daytona/configuration/create-daytona-with-policies", "examples/go/daytona/configuration/create-daytona-from-snapshot", "examples/go/daytona/configuration/create-daytona-from-image", @@ -64,6 +64,33 @@ var goTestModules = []string{ "examples/go/modal/process/run-modal-with-cwd", "examples/go/modal/process/run-modal-with-env", "examples/go/modal/process/run-modal-with-timeout", + "examples/go/blaxel/configuration/create-blaxel-sandbox", + "examples/go/blaxel/configuration/create-blaxel-with-resources", + "examples/go/blaxel/configuration/create-blaxel-with-labels", + "examples/go/blaxel/configuration/create-blaxel-with-ttl", + "examples/go/blaxel/configuration/create-blaxel-idle", + "examples/go/blaxel/configuration/create-blaxel-client-timeout", + "examples/go/blaxel/configuration/create-blaxel-with-ports", + "examples/go/blaxel/configuration/create-blaxel-with-allowlists", + "examples/go/blaxel/errors/handle-blaxel-errors", + "examples/go/blaxel/lifecycle/get-blaxel-sandbox", + "examples/go/blaxel/lifecycle/refresh-blaxel-sandbox", + "examples/go/blaxel/lifecycle/delete-blaxel-sandbox", + "examples/go/blaxel/lifecycle/close-blaxel-handle", + "examples/go/blaxel/filesystem/copy-file-from-blaxel", + "examples/go/blaxel/filesystem/copy-file-into-blaxel", + "examples/go/blaxel/filesystem/list-blaxel-directory", + "examples/go/blaxel/filesystem/make-blaxel-directory", + "examples/go/blaxel/filesystem/read-blaxel-file", + "examples/go/blaxel/filesystem/remove-blaxel-file", + "examples/go/blaxel/filesystem/stat-blaxel-file", + "examples/go/blaxel/filesystem/write-blaxel-file", + "examples/go/blaxel/process/run-blaxel-check-exit", + "examples/go/blaxel/process/run-blaxel-command", + "examples/go/blaxel/process/run-blaxel-shell-command", + "examples/go/blaxel/process/run-blaxel-with-cwd", + "examples/go/blaxel/process/run-blaxel-with-env", + "examples/go/blaxel/process/run-blaxel-with-timeout", } var typescriptTestModules = []struct { @@ -74,6 +101,7 @@ var typescriptTestModules = []struct { {"sdks/typescript/sandbox", true}, {"sdks/typescript/providers/daytona", true}, {"sdks/typescript/providers/modal", true}, + {"sdks/typescript/providers/blaxel", true}, } func typescriptModules(root string) ([]struct { diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx new file mode 100644 index 00000000..589afd96 --- /dev/null +++ b/website/content/docs/blaxel.mdx @@ -0,0 +1,240 @@ +--- +title: Blaxel +description: Install, authenticate, and create a Blaxel sandbox. +--- + +Use Blaxel when you want named image creates, get/refresh by name, filesystem, blocking process runs, and delete. + +## Install + + + + +```sh +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel +``` + + + + +```sh +go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest +go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest +``` + + + + +## Create + +Set `BL_API_KEY` and `BL_WORKSPACE`. Create needs a Blaxel-valid `name` +(required; blank or whitespace-only is rejected; lowercase alphanumeric with +single hyphens, ≤49 characters), an image, and client `region`. + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}); + +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(sandbox.id); // same as create name; use with get / BL_SANDBOX_NAME +``` + +`timeout` is milliseconds. Prefer `await using`; otherwise call +`await client.close()` when finished. Use one Blaxel configuration per process; +a client with different credentials or workspace is rejected. + + + + +```go +package main + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + panic(err) + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + }) + if err != nil { + panic(err) + } + fmt.Println(instance.ID()) // same as create name; use with Get / BL_SANDBOX_NAME +} +``` + +Optional strings use `sandbox.Value`. + + + + +Omit `auth` and `scope.workspace` to use `BL_API_KEY` and `BL_WORKSPACE` from +the environment. `region` is still required for create. `endpoint` is not +supported. Closing the client does not delete the sandbox. + +## Configuration + + + + +| Setting | Notes | +| -------------- | ------------------------------------------------------- | +| API key | `auth.apiKey` | +| Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | +| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | +| Region | `region` — required for create | +| Endpoint | Unsupported | + +```ts +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}); +``` + + + + +| Setting | Notes | +| --------- | -------------------------------------------------- | +| API key | `Auth` with `APIKey` | +| Workspace | `Scope.Workspace` — or `BL_WORKSPACE` when omitted | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) +``` + + + + +Only API key auth and workspace scope are supported. Full rules: +[Configuration](/docs/configuration). + + + Use one Blaxel configuration per process. A client with different credentials + or workspace is rejected. + + +## What works + +| Area | Notes | +| --------- | --------------------------------------------------------------------------------------------------------------- | +| Create | Name + image; memory, labels, env, ports, outbound domains, TTL/idle | +| Volumes | Not available on create | +| Open | Look up or refresh by sandbox name | +| Lifecycle | Delete or close — no start, stop, pause, archive, terminate, or attach | +| Delete | Succeeds as `deleted` / `terminal`; omit `wait` may `Conflict` on quick recreate; `wait: true` waits until gone | +| State | Idle → paused; active → running | +| Errors | Name taken on create → AlreadyExists; delete still in progress → Conflict; bad TTL/idle → InvalidArgument | +| Files | Shared filesystem methods — [Filesystem](/docs/filesystem) | +| Process | `run` waits for the command — [Run a command](/docs/process) | + +Pass memory in MiB, or omit it for Blaxel’s default. Kit converts it to +Blaxel’s decimal MB field and rounds up so the request is not under-provisioned. +CPU and disk overrides are not supported. Runtime and isolation are not +supported. + + + Labels on `info()` appear only when Blaxel reports them. + + + + Labels on `Info()` appear only when Blaxel reports them. + + +Default ports are HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not +supported. Ready/Started/Submitted wait and idle disable are not supported. + +Set a client or create timeout for how long create may wait. With a 1–50s +timeout, a timed-out create frees the name. + + + If Blaxel’s edge returns HTTP 504 during create, the client may keep waiting + until the sandbox is ready or your create timeout ends. A timeout does not + always mean the sandbox was never created—call `get` by name before + recreating. + + + + If Blaxel’s edge returns HTTP 504 during create, the call returns a timeout as + soon as that response arrives. A timeout does not always mean the sandbox was + never created—call `Get` by name before recreating. + + +## Related + +- Labels, ports, TTL/idle — [Create](/docs/create) +- get / refresh / delete — [Lifecycle](/docs/lifecycle) + + + + +Example: [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) + + + + +Example: [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) + + + diff --git a/website/content/docs/concepts.mdx b/website/content/docs/concepts.mdx index 5474d76a..dee6d2c5 100644 --- a/website/content/docs/concepts.mdx +++ b/website/content/docs/concepts.mdx @@ -3,7 +3,7 @@ title: Concepts description: Client, handle, filesystem, process, providers, concurrency, and close. --- -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. +Sandbox Kit is an SDK. Construct a client with a provider and config, then create, look up, and operate on sandboxes through that client. ## Client @@ -51,6 +51,25 @@ await using client = new Client({ }); ``` + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, // default client deadline (ms) +}); +``` + @@ -111,6 +130,36 @@ if err != nil { } ``` + + + + +```go +import ( + "os" + "strings" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) +if err != nil { + return err +} +``` + @@ -118,7 +167,9 @@ if err != nil { ## Sandbox handle -`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. +Creating a sandbox returns a handle. To reopen an existing sandbox, use the +provider’s open operation—see [Lifecycle](/docs/lifecycle). Use the handle for +lifecycle, filesystem, and process calls. @@ -143,6 +194,18 @@ const sandbox = await client.create({ console.log(sandbox.id); ``` + + + + +```ts +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(sandbox.id); // same as create name +``` + @@ -178,6 +241,23 @@ if err != nil { fmt.Println(instance.ID()) ``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, +}) +if err != nil { + return err +} +fmt.Println(instance.ID()) // same as create name +``` + @@ -259,7 +339,7 @@ fmt.Println(result) 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. +The client calls Daytona, Modal, or Blaxel directly. @@ -300,6 +380,26 @@ await using client = new Client({ Setup: [Modal](/docs/modal). + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION || "us-pdx-1", +}); +``` + +Setup: [Blaxel](/docs/blaxel). + @@ -348,6 +448,33 @@ client, err := sandbox.NewClient(modal.New(), &sandbox.Config{ Setup: [Modal](/docs/modal). + + + + +```go +import ( + "os" + "strings" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) +``` + +Setup: [Blaxel](/docs/blaxel). + @@ -355,7 +482,9 @@ Setup: [Modal](/docs/modal). ## Capabilities -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. +Unsupported calls return an error. Check a handle’s capabilities before a +lifecycle method that might be unavailable. A supported call can still fail if +the provider rejects that sandbox. @@ -382,6 +511,21 @@ if (!sandbox.supports(LifecycleCapability.Terminate)) { } ``` + + + + +```ts +import { LifecycleCapability } from "@sandbox-kit/sandbox"; + +if (!sandbox.supports(LifecycleCapability.Delete)) { + throw new Error(`sandbox ${sandbox.id} cannot be deleted`); +} +if (!sandbox.supports(LifecycleCapability.Refresh)) { + throw new Error(`sandbox ${sandbox.id} cannot be refreshed`); +} +``` + @@ -409,6 +553,21 @@ if !instance.Supports(sandbox.LifecycleCapabilityTerminate) { } ``` + + + + +```go +import "github.com/sandbox-kit/kit/sdks/go/sandbox" + +if !instance.Supports(sandbox.LifecycleCapabilityDelete) { + return fmt.Errorf("sandbox %s cannot be deleted", instance.ID()) +} +if !instance.Supports(sandbox.LifecycleCapabilityRefresh) { + return fmt.Errorf("sandbox %s cannot be refreshed", instance.ID()) +} +``` + @@ -462,6 +621,19 @@ await Promise.all([ ]); ``` + + + + +```ts +// Parallel work: another handle via get(name) 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"] }), +]); +``` + @@ -550,6 +722,32 @@ for range 2 { } ``` + + + + +```go +// Parallel work: another handle via Get(name) 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 + } +} +``` + diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 22951eca..1a428ac6 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -38,19 +38,20 @@ if err != nil { -| 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 | +| Setting | Purpose | +| ----------- | -------------------------------------------------------------------------------- | +| Provider | Provider constructor | +| Credentials | API key, token pair, bearer token, or OAuth refresh token; omit for env creds | +| Scope | App, environment, organization, project, or workspace | +| Endpoint | Provider endpoint URL override | +| Region | Target or default placement | +| Timeout | Default deadline for calls that omit their own | Provider setup: [Daytona](/docs/daytona). Provider setup: [Modal](/docs/modal). +Provider setup: [Blaxel](/docs/blaxel). ## Client @@ -96,6 +97,25 @@ await using client = new Client({ }); ``` + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}); +``` + @@ -156,6 +176,36 @@ if region := os.Getenv("MODAL_REGION"); region != "" { client, err := sandbox.NewClient(modal.New(), config) ``` + + + + +```go +import ( + "os" + "strings" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +if region := os.Getenv("BL_REGION"); region != "" { + config.Region = sandbox.Value(region) +} +client, err := sandbox.NewClient(blaxel.New(), config) +``` + @@ -287,9 +337,70 @@ client, err := sandbox.NewClient(modal.New(), config) + + + + + +| Setting | What to pass | +| -------------- | ------------------------------------------------------- | +| API key | `auth.apiKey.key` | +| Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | +| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | +| Region | `region` — required for create | +| Endpoint | Unsupported | + +In TypeScript, use one Blaxel configuration per process. A second client with +different credentials or workspace is rejected before it can replace the +process-wide SDK configuration. + +```ts +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}); +``` + + + + +| Setting | What to pass | +| --------- | -------------------------------------------------- | +| API key | `APIKey` | +| Workspace | `Scope.Workspace` — or `BL_WORKSPACE` when omitted | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) +``` + + + + +Only API key auth and workspace scope are supported. + + + ## Timeouts -`timeout` on the client is the default deadline. Create and lifecycle calls can override it. +The client timeout 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. @@ -326,6 +437,25 @@ const sandbox = await client.create( ); ``` + + + + +```ts +await using client = new Client({ + provider: blaxel(), + timeout: 120_000, // default for calls that omit timeout +}); + +const sandbox = await client.create( + { + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { timeout: 60_000 }, // override for this call; 0 = no deadline +); +``` + @@ -373,6 +503,29 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Timeout: sandbox.Duration(2 * time.Minute), // default deadline +}) +if err != nil { + return err +} + +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), // override; 0 = no deadline + }, +}) +``` + @@ -384,6 +537,16 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ rejected. + + Set a client or create timeout for how long create may wait. Ready, Started, + and Submitted wait are not supported. + + + Use one Blaxel configuration per process. A second client with different + credentials or workspace is rejected. + + + ## Credentials Construction does not contact the provider. Bad credentials fail on the first create or open. See [Errors](/docs/errors). @@ -423,6 +586,24 @@ const config = { await using client = new Client(config); ``` + + + + +```ts +const workspace = process.env.BL_WORKSPACE?.trim(); + +const config = { + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}; + +await using client = new Client(config); +``` + @@ -461,6 +642,24 @@ config := &sandbox.Config{ client, err := sandbox.NewClient(modal.New(), config) ``` + + + + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) +``` + diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index cd36e918..9419c83e 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -9,6 +9,7 @@ Create returns a sandbox handle. Build the client first. Provider setup: [Daytona](/docs/daytona). Provider setup: [Modal](/docs/modal). +Provider setup: [Blaxel](/docs/blaxel). Omitted fields keep provider defaults. Unsupported fields fail before the provider call. @@ -41,6 +42,21 @@ const sandbox = await client.create( ); ``` + + + + +```ts +const sandbox = await client.create( + { + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + }, + { timeout: 60_000 }, // milliseconds +); +``` + @@ -82,6 +98,25 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), + }, +}) +``` + @@ -232,6 +267,67 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ + + +| | Blaxel | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Source | Registry image and Blaxel-valid `name` required (blank/whitespace rejected; lowercase alphanumeric with single hyphens, ≤49) | +| CPU | Derived from memory; CPU overrides are not supported | +| Memory | Memory value (Blaxel treats the value as MB) | +| Disk | Cannot be set | +| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable / ephemeral siblings not supported | +| Placement | Client `region` required; placement on the request is not supported | +| Ports | Default → HTTP; plain → TCP; TLS → TLS; Http2Tls not supported; 80/443/8080 reserved | +| Wait | Default or Scheduled wait only; a 1–50s create timeout frees the name on timeout | +| Runtime | Not supported | +| Isolation | Not supported | + +Volumes are not available on create. + + + + +```ts +import { PolicyMode } from "@sandbox-kit/sandbox"; + +await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + lifetime: { + maximumLifetime: 24 * 60 * 60_000, + idleTerminate: { mode: PolicyMode.After, after: 60_000 }, + }, +}); +``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(24 * time.Hour), + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(time.Minute), + }, + }, +}) +``` + + + + + + ## Example @@ -284,6 +380,28 @@ const sandbox = await client.create({ `PolicyMode.After` starts the idle timer. `PolicyMode.Disabled` turns idle termination off. + + + + +```ts +import { PolicyMode } from "@sandbox-kit/sandbox"; + +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + lifetime: { + maximumLifetime: 24 * 60 * 60_000, + idleTerminate: { mode: PolicyMode.After, after: 60_000 }, + }, + network: { + ports: [{ port: 3000 }], + outboundDomains: ["example.com"], + }, +}); +``` + @@ -358,6 +476,39 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +import ( + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(24 * time.Hour), + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(time.Minute), + }, + }, + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{{Port: 3000}}, + OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"example.com"}}, + }, +}) +``` + @@ -458,6 +609,31 @@ See [Examples](/docs/examples). + + +Create needs a Blaxel-valid `name` (blank or whitespace-only is rejected), an +image, and client `region`. The returned id is that name. + +Set a create timeout for how long create may wait. With 1–50 seconds, a +timed-out create frees the name so you can retry. + + + If Blaxel’s edge returns HTTP 504 during create, the client may keep waiting + until the sandbox is ready or your create timeout ends. A timeout does not + always mean the sandbox was never created—call `get` by name before + recreating. + + + + If Blaxel’s edge returns HTTP 504 during create, the call returns a timeout as + soon as that response arrives. A timeout does not always mean the sandbox was + never created—call `Get` by name before recreating. + + +See [Examples](/docs/examples). + + + ## Result Metadata from info is a **copy**. Mutating it does not change the sandbox. Reported CPU and memory can differ from what you requested. diff --git a/website/content/docs/daytona.mdx b/website/content/docs/daytona.mdx index ff85951c..4832d52d 100644 --- a/website/content/docs/daytona.mdx +++ b/website/content/docs/daytona.mdx @@ -3,7 +3,8 @@ title: Daytona description: Install, authenticate, and create a Daytona sandbox. --- -Use Daytona when you want default snapshots, verified `get`, and start / stop / pause / archive. +Use Daytona when you want default snapshots, verified sandbox lookup, and start, +stop, pause, or archive operations. ## Install @@ -175,8 +176,8 @@ Default endpoint: `https://app.daytona.io/api`. Remote URLs need HTTPS; HTTP is | Area | Notes | | --------- | ----------------------------------------------------------- | | Create | Default snapshot, named snapshot, or image (with resources) | -| Open | `get` — verified. `attach` is unsupported | -| Lifecycle | `refresh`, `start`, `stop`, `pause`, `archive`, `delete` | +| Open | Verified lookup. Attach is unsupported | +| Lifecycle | Refresh, start, stop, pause, archive, and delete | | Files | Filesystem methods — [Filesystem](/docs/filesystem) | | Process | `run` — [Run a command](/docs/process) | diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index e1ed9b58..9aea3f38 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -7,21 +7,31 @@ Failed calls surface a typed error with `kind`, `provider`, `operation`, `messag 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` | Credentials were rejected | -| `PermissionDenied` | The account cannot perform this operation | -| `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 | +| Kind | What happened | +| -------------------- | ---------------------------------------------------- | +| `InvalidArgument` | A request field is invalid | +| `Unsupported` | This provider does not support 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 returned an unusable response | +| `Internal` | Unclassified failure | +| `FailedPrecondition` | Client or sandbox is not in a usable state | +| `Unknown` | Does not match a known category | ## Invalid config -Bad client config fails before any provider call. TypeScript throws; Go returns `(*Client, error)`. +Bad client config fails before any provider call. + +It throws. +It returns `(*Client, error)`. @@ -100,6 +110,12 @@ try { case ErrorKind.NotFound: console.error("Sandbox or file path not found."); break; + case ErrorKind.AlreadyExists: + console.error("Named resource already exists."); + break; + case ErrorKind.Conflict: + console.error("Current state rejects this operation."); + break; case ErrorKind.RateLimited: case ErrorKind.ResourceExhausted: console.error("Provider rate or resource limit reached."); @@ -142,6 +158,10 @@ if errors.As(err, &detail) { fmt.Println("Your account cannot perform this operation.") case sandbox.ErrorKindNotFound: fmt.Println("Sandbox or file path not found.") + case sandbox.ErrorKindAlreadyExists: + fmt.Println("Named resource already exists.") + case sandbox.ErrorKindConflict: + fmt.Println("Current state rejects this operation.") case sandbox.ErrorKindRateLimited, sandbox.ErrorKindResourceExhausted: fmt.Println("Provider rate or resource limit reached.") case sandbox.ErrorKindCanceled: @@ -218,8 +238,6 @@ try { // 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; @@ -236,8 +254,6 @@ if err != nil { 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()) } } @@ -297,3 +313,59 @@ _ = instance See [Examples](/docs/examples). + + + +If the name is already taken on create, you get `AlreadyExists`. If a delete is +still finishing, you get `Conflict` — wait, then `get` before you recreate that +name. Lifetime below Blaxel’s floors (≥5m max lifetime, ≥1m idle) or a +non-whole-second duration is `InvalidArgument`. Unsupported create fields +(runtime, isolation, idle disable, …) are `Unsupported`. When Blaxel returns a +control-plane code, read it on the typed error. + +Use `providerCode`. +Use `ProviderCode`. + + + + +```ts +try { + await client.create(request); +} catch (error) { + if (!(error instanceof SandboxError)) throw error; + if (error.kind === ErrorKind.AlreadyExists) { + console.error("Name already taken:", error.message); + } else if (error.kind === ErrorKind.Conflict) { + console.error("Name still conflicting; wait and get before recreate."); + } + throw error; +} +``` + + + + +```go +instance, err := client.Create(ctx, opts) +if err != nil { + var detail *sandbox.Error + if errors.As(err, &detail) { + switch detail.Kind { + case sandbox.ErrorKindAlreadyExists: + fmt.Println("Name already taken:", detail.Message) + case sandbox.ErrorKindConflict: + fmt.Println("Name still conflicting; wait and Get before recreate.") + } + } + return err +} +_ = instance +``` + + + + +See [Examples](/docs/examples). + + diff --git a/website/content/docs/examples.mdx b/website/content/docs/examples.mdx index 97f6cc9a..8cb927bb 100644 --- a/website/content/docs/examples.mdx +++ b/website/content/docs/examples.mdx @@ -36,6 +36,17 @@ cp .env.example .env # set MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, MODAL_APP_NAME npm install && npm run build && npm start ``` + + + + +```sh +git clone https://github.com/sandbox-kit/kit.git +cd kit/examples/typescript/blaxel/configuration/create-blaxel-sandbox +cp .env.example .env # set BL_API_KEY, BL_WORKSPACE, BL_REGION (or us-pdx-1) +npm install && npm run build && npm start +``` + @@ -63,6 +74,17 @@ cp .env.example .env # set MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, MODAL_APP_NAME go run . ``` + + + + +```sh +git clone https://github.com/sandbox-kit/kit.git +cd kit/examples/go/blaxel/configuration/create-blaxel-sandbox +cp .env.example .env # set BL_API_KEY, BL_WORKSPACE, BL_REGION (or us-pdx-1) +go run . +``` + @@ -399,3 +421,148 @@ go run . + + + +## Catalog + +### Create + + + + +| Example | Project | +| ------- | ------- | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | +| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-resources) | +| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-labels) | +| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-ttl) | +| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-idle) | +| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-client-timeout) | +| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-ports) | +| Outbound domains | [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists) | + + + + +| Example | Project | +| ------- | ------- | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) | +| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-resources) | +| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-labels) | +| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-ttl) | +| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-idle) | +| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-client-timeout) | +| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-ports) | +| Outbound domains | [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-allowlists) | + + + + +### Process + + + + +| Example | Project | +| ------- | ------- | +| Run command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-command) | +| Shell command | [run-blaxel-shell-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-shell-command) | +| Run with cwd | [run-blaxel-with-cwd](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-with-cwd) | +| Run with env | [run-blaxel-with-env](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-with-env) | +| Run with timeout | [run-blaxel-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-with-timeout) | +| Check exit | [run-blaxel-check-exit](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-check-exit) | + + + + +| Example | Project | +| ------- | ------- | +| Run command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-command) | +| Shell command | [run-blaxel-shell-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-shell-command) | +| Run with cwd | [run-blaxel-with-cwd](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-with-cwd) | +| Run with env | [run-blaxel-with-env](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-with-env) | +| Run with timeout | [run-blaxel-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-with-timeout) | +| Check exit | [run-blaxel-check-exit](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-check-exit) | + + + + +### Filesystem + + + + +| Example | Project | +| ------- | ------- | +| Read file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/read-blaxel-file) | +| Write file | [write-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/write-blaxel-file) | +| Copy in | [copy-file-into-blaxel](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/copy-file-into-blaxel) | +| Copy out | [copy-file-from-blaxel](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/copy-file-from-blaxel) | +| Directory | [make-blaxel-directory](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/make-blaxel-directory) | +| List | [list-blaxel-directory](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/list-blaxel-directory) | +| Stat | [stat-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/stat-blaxel-file) | +| Remove | [remove-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/remove-blaxel-file) | + + + + +| Example | Project | +| ------- | ------- | +| Read file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/read-blaxel-file) | +| Write file | [write-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/write-blaxel-file) | +| Copy in | [copy-file-into-blaxel](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/copy-file-into-blaxel) | +| Copy out | [copy-file-from-blaxel](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/copy-file-from-blaxel) | +| Directory | [make-blaxel-directory](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/make-blaxel-directory) | +| List | [list-blaxel-directory](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/list-blaxel-directory) | +| Stat | [stat-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/stat-blaxel-file) | +| Remove | [remove-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/remove-blaxel-file) | + + + + +### Lifecycle + + + + +| Example | Project | +| ------- | ------- | +| Get | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | +| Refresh | [refresh-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox) | +| Close handle | [close-blaxel-handle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/close-blaxel-handle) | + + + + +| Example | Project | +| ------- | ------- | +| Get | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | +| Refresh | [refresh-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | +| Close handle | [close-blaxel-handle](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/close-blaxel-handle) | + + + + +### Errors + + + + +| Example | Project | +| ------- | ------- | +| Errors | [handle-blaxel-errors](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/errors/handle-blaxel-errors) | + + + + +| Example | Project | +| ------- | ------- | +| Errors | [handle-blaxel-errors](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/errors/handle-blaxel-errors) | + + + + + diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index f3ac43a0..9a6ba6b1 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -5,6 +5,8 @@ description: Read, write, copy, mkdir, list, stat, and remove paths inside a san Use the filesystem methods on an open handle. + + | Method | Purpose | | --------------------------------------- | ------------------------ | | [`readFile`](#read-a-file) | Read file bytes | @@ -16,6 +18,23 @@ Use the filesystem methods on an open handle. | [`stat`](#read-metadata) | Path metadata | | [`remove`](#delete-a-file-or-directory) | Delete a path | + + + + +| Method | Purpose | +| --------------------------------------- | ------------------------ | +| [`ReadFile`](#read-a-file) | Read file bytes | +| [`WriteFile`](#write-a-file) | Replace or create a file | +| [`CopyFromLocal`](#copy-a-file-in) | Local → sandbox | +| [`CopyToLocal`](#copy-a-file-out) | Sandbox → local | +| [`MakeDirectory`](#create-a-directory) | Create a directory | +| [`ListDirectory`](#list-a-directory) | List one directory | +| [`Stat`](#read-metadata) | Path metadata | +| [`Remove`](#delete-a-file-or-directory) | Delete a path | + + + You need a sandbox from [create](/docs/create) or [lifecycle](/docs/lifecycle). ## Paths and bytes @@ -25,7 +44,15 @@ You need a sandbox from [create](/docs/create) or [lifecycle](/docs/lifecycle). | 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` and `writeFile` transfer bytes—encode and decode text in your app. Copy methods use files on disk. + + `readFile` and `writeFile` transfer bytes—encode and decode text in your app. + Copy methods use files on disk. + + + + `ReadFile` and `WriteFile` transfer bytes—encode and decode text in your app. + Copy methods use files on disk. + @@ -112,6 +139,33 @@ data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileR + + + + + +Timeouts are milliseconds. `1500` ends the call at 1.5s. + +```ts +await sandbox.filesystem.readFile("/tmp/note.txt", { timeout: 1_500 }); +``` + + + + +Every Go file call uses the context deadline. + +```go +data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileReadOptions{ + Timeout: sandbox.Duration(1500 * time.Millisecond), +}) +``` + + + + + + ## Read a file Returns the bytes of one file. A missing file is not found. A directory path fails at the provider. @@ -219,6 +273,11 @@ err := instance.Filesystem().CopyToLocal(ctx, "/tmp/note.txt", "note.txt", nil) parent must exist and the new directory must not. + + Missing parents are created when `parents` is omitted or true. `parents: + false` is not supported. + + @@ -314,6 +373,11 @@ fmt.Println(entries) A symlink result describes the symlink when Modal reports one. + + Directories return type directory. Files return type file, with size and + permissions when available. `name` and `path` are whatever Blaxel reports. + + diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx index feb585a2..22dcadcd 100644 --- a/website/content/docs/index.mdx +++ b/website/content/docs/index.mdx @@ -39,6 +39,14 @@ npm install @sandbox-kit/sandbox @sandbox-kit/daytona npm install @sandbox-kit/sandbox @sandbox-kit/modal ``` + + + + +```sh +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel +``` + @@ -60,6 +68,15 @@ go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest ``` + + + + +```sh +go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest +go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest +``` + @@ -73,6 +90,11 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest Set a Modal token pair and app name ([Modal](/docs/modal)), then: + + Set `BL_API_KEY`, `BL_WORKSPACE`, and a client `region` + ([Blaxel](/docs/blaxel)), then: + + @@ -121,6 +143,31 @@ const sandbox = await client.create({ console.log(sandbox.id); ``` + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const workspace = process.env.BL_WORKSPACE?.trim(); + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + ...(workspace ? { scope: { workspace } } : {}), + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, // default client deadline (ms) +}); + +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(sandbox.id); // same as create name +``` + @@ -211,6 +258,55 @@ func main() { } ``` + + + + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Println(instance.ID()) // same as create name +} +``` + @@ -220,6 +316,7 @@ func main() { Provider setup: [Daytona](/docs/daytona). Provider setup: [Modal](/docs/modal). +Provider setup: [Blaxel](/docs/blaxel). ## 3. Run a command @@ -293,9 +390,9 @@ err = instance.Filesystem().WriteFile( Sandbox paths are absolute. More: [Filesystem](/docs/filesystem). - `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). + Closing a client or handle only releases 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 @@ -306,5 +403,6 @@ Sandbox paths are absolute. More: [Filesystem](/docs/filesystem). | 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) | +| Blaxel setup | [Blaxel](/docs/blaxel) | | 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 40a9da20..13646787 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -3,7 +3,8 @@ title: Lifecycle description: get, attach, start, stop, pause, archive, delete, terminate, and close. --- -Operate on a handle from `create`, or reopen one with `get` / `attach`. Unsupported calls fail before the provider runs. +Operate on a handle from creation, or reopen an existing sandbox. Unsupported +calls return an error. @@ -22,10 +23,10 @@ Check support with `Supports`. | Operation | Support | | ------------------------------ | ------------------------------------------ | -| Open existing | [`get`](#open-a-sandbox) (provider lookup) | +| Open existing | [Look up](#open-a-sandbox) (provider lookup) | | start / stop / pause / archive | [Supported](#stop-start-pause-and-archive) | -| Destroy | [`delete`](#delete) | -| Local disconnect | [`close`](#close-the-client) | +| 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). @@ -35,11 +36,25 @@ Daytona may still reject a supported op for a given sandbox. Snippets assume a c | Operation | Support | | ---------------- | ------------------------------------------------- | -| Open existing | [`attach`](#open-a-sandbox) (id only) | -| Destroy | [`terminate`](#terminate) or create-time lifetime | -| Local disconnect | [`close`](#close-the-client) | +| 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). +Start, stop, pause, archive, and delete are rejected in Kit. Snippets assume a +constructed client — [Modal](/docs/modal). + + + + + +| Operation | Support | +| ------------------------------ | ----------------------------------------- | +| Open existing | [Look up](#open-a-sandbox) (by name) | +| start / stop / pause / archive | Unsupported | +| Destroy | [Delete](#delete) | +| Local disconnect | [Close](#close-the-client) | + +Snippets assume a constructed client — [Blaxel](/docs/blaxel). @@ -47,7 +62,15 @@ Daytona may still reject a supported op for a given sandbox. Snippets assume a c -`get` loads a sandbox that already exists. `refresh` loads it again. `attach` is not available. + + `get` loads a sandbox that already exists. `refresh` loads it again. `attach` + is not available. + + + + `Get` loads a sandbox that already exists. `Refresh` loads it again. `Attach` + is not available. + @@ -85,7 +108,15 @@ 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. + + `attach` builds a handle from an id. It does not ask Modal whether that + sandbox exists. `get` and `refresh` are not available. + + + + `Attach` builds a handle from an id. It does not ask Modal whether that + sandbox exists. `Get` and `Refresh` are not available. + @@ -112,11 +143,57 @@ if err != nil { + + + + `get` loads a sandbox that already exists by **name** (the id is the create + name). `refresh` loads it again. `attach` is not available. + + + + `Get` loads a sandbox that already exists by **name** (the id is the create + name). `Refresh` loads it again. `Attach` is not available. + + +Idle sandboxes show as paused; active ones as running. + + + + +```ts +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const info = await sandbox.refresh(); +console.log(info); +``` + + + + +```go +import "os" + +instance, err := client.Get(ctx, os.Getenv("BL_SANDBOX_NAME")) +if err != nil { + return err +} +if err := instance.Refresh(ctx); err != nil { + return err +} +fmt.Println(instance.Info()) +``` + + + + + + ## 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. +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. @@ -180,7 +257,7 @@ Daytona decides whether that sandbox can pause, archive, or stop. An ephemeral s ## Delete -`delete` removes a Daytona sandbox. +Delete removes a Daytona sandbox. @@ -217,11 +294,59 @@ if err := instance.Delete(ctx, &sandbox.DeleteOptions{ + + +## Delete + +Delete removes a Blaxel sandbox. On success, its state is deleted and terminal. +Omit the wait option to return when deletion is accepted; set it to wait until +removal finishes before recreating. + + + + +```ts +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const info = await sandbox.delete({ wait: true, timeout: 60_000 }); +console.log(info.state, info.terminal); +``` + + + + +```go +import ( + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +instance, err := client.Get(ctx, os.Getenv("BL_SANDBOX_NAME")) +if err != nil { + return err +} +if err := instance.Delete(ctx, &sandbox.DeleteOptions{ + Wait: sandbox.Value(true), + Timeout: sandbox.Duration(time.Minute), +}); err != nil { + return err +} +info := instance.Info() +fmt.Println(info.State, info.GetTerminal()) +``` + + + + + + ## Terminate -`terminate` finishes a Modal sandbox. A maximum lifetime or idle timeout set at [create](/docs/create) also ends it. +Terminate finishes a Modal sandbox. A maximum lifetime or idle timeout set at +[create](/docs/create) also ends it. @@ -299,23 +424,51 @@ defer client.Close(context.Background()) `Sandbox.Close` does not stop or delete the sandbox. Later calls on that handle still reach the provider. + + Handle `close` does not delete the sandbox. It keeps running until you + `delete` it or a lifetime policy ends it. + + ## Read the state -`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). +The handle stores the lifecycle state. 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. + + After a lifecycle call, call `refresh` (or `get` again) before assuming the + sandbox is stopped or gone. + + + 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. + + Use the info returned by the call, or `info()` on the handle. There is no + `refresh` or `get` on Modal. + + + Use the info returned by the call, or `Info()` on the handle. There is no + `Refresh` or `Get` on Modal. + + + + + + After `delete`, use `wait: true` when you need removal to finish before + recreate. Use `refresh` or `get` for fresh metadata while the sandbox exists. + + + After `Delete`, use `Wait: sandbox.Value(true)` when you need removal to + finish before recreate. Use `Refresh` or `Get` for fresh metadata while the + sandbox exists. + @@ -336,6 +489,15 @@ const refreshed = await sandbox.refresh(); console.log(refreshed); ``` + + + + +```ts +const refreshed = await sandbox.refresh(); +console.log(refreshed); +``` + @@ -358,6 +520,17 @@ if err := instance.Refresh(ctx); err != nil { fmt.Println(instance.Info()) ``` + + + + +```go +if err := instance.Refresh(ctx); err != nil { + return err +} +fmt.Println(instance.Info()) +``` + diff --git a/website/content/docs/meta.json b/website/content/docs/meta.json index 77885484..4ffd4b2c 100644 --- a/website/content/docs/meta.json +++ b/website/content/docs/meta.json @@ -6,6 +6,7 @@ "---Providers---", "daytona", "modal", + "blaxel", "---Guides---", "configuration", "create", diff --git a/website/content/docs/modal.mdx b/website/content/docs/modal.mdx index 1a24beb6..e92c4dba 100644 --- a/website/content/docs/modal.mdx +++ b/website/content/docs/modal.mdx @@ -233,8 +233,8 @@ Full rules: [Configuration](/docs/configuration). | 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 | +| Open | Attach by id. Lookup and refresh are unsupported | +| Lifecycle | Terminate. Start, stop, pause, archive, and delete are unsupported | | Files | Filesystem methods — [Filesystem](/docs/filesystem) | | Process | `run` — [Run a command](/docs/process) | diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index 30edebab..10edbe30 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -29,13 +29,13 @@ 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. 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) | +| Setting | Rules | +| ----------------- | ------------------------------------------------------------------------- | +| Program or shell | Set exactly one. A request with both or neither is rejected. | +| Shell command | A blank command is rejected after trimming. | +| Working directory | Absolute when set. Relative, blank, or NUL is rejected. | +| Environment | Applies only to this call. Omit for the provider default. | +| Timeout | See [Timeouts](#timeouts-and-cancel) | @@ -78,7 +78,7 @@ fmt.Println(result) | You pass | On Daytona | | ---------------------- | --------------------------------------------------------------- | -| `argv` | POSIX-quoted shell string | +| `argv` | One quoted shell command string | | `shell` | shell string as-is | | omit / zero timeout | no command limit (`0`, so Daytona does not use its 10s default) | | fractional (`1500` ms) | rejected (`timeout`) | @@ -90,7 +90,7 @@ Daytona returns stdout only; `stderr` is empty. stdout may include merged stream ```ts const result = await sandbox.process.run({ - argv: ["echo", "hello"], // sent as a POSIX-quoted shell string + argv: ["echo", "hello"], // sent as one quoted shell command string }); // result: { exitCode, stdout, stderr } — stderr often empty on Daytona console.log(result); @@ -101,7 +101,7 @@ console.log(result); ```go result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ - Argv: []string{"echo", "hello"}, // sent as a POSIX-quoted shell string + Argv: []string{"echo", "hello"}, // sent as one quoted shell command string }) if err != nil { return err @@ -153,17 +153,58 @@ fmt.Println(result) + + +| You pass | What happens | +| ---------------------- | ----------------------------------------------------- | +| `argv` | Sent as one shell command string | +| `shell` | Run as-is | +| omit / zero timeout | Wait until the command ends | +| positive timeout | Cap how long the command may run (whole seconds only) | +| fractional (`1500` ms) | Not supported | + +You get `stdout` and `stderr`. `run` waits until the command ends. Abort stops +the wait only — the remote command may keep running. + + + + +```ts +const result = await sandbox.process.run({ + argv: ["echo", "hello"], // sent as one quoted shell command string +}); +// result: { exitCode, stdout, stderr } +console.log(result); +``` + + + + +```go +result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "hello"}, // sent as one quoted shell command string +}) +// result: ExitCode, Stdout, Stderr +fmt.Println(result) +``` + + + + + + A non-zero exit code is **not** an SDK error. ## Timeouts and cancel | Setting | Meaning | | -------------- | ----------------------------------------------------------------- | -| Omit `timeout` | Use the client default deadline | +| Omit timeout | Use the client default deadline | | `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. +Omit or zero also means no separate provider process time limit (see the +provider tables above). diff --git a/website/content/docs/sdk/client.mdx b/website/content/docs/sdk/client.mdx index dd6812fb..b7fd70f3 100644 --- a/website/content/docs/sdk/client.mdx +++ b/website/content/docs/sdk/client.mdx @@ -3,7 +3,28 @@ 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. +Creates sandboxes and reopens them. + + + Reopen with [`get`](#get) by sandbox id. + Reopen with [`Get`](#get) by sandbox id. + + + + Reopen with [`attach`](#attach) by sandbox id. It does not check that the + sandbox exists. + + + Reopen with [`Attach`](#attach) by sandbox id. It does not check that the + sandbox exists. + + + + Reopen with [`get`](#get) by create name. Blaxel id is the name. + Reopen with [`Get`](#get) by create name. Blaxel id is the name. + + +Closing the client only drops the local connection—cloud sandboxes keep running. ## Construction @@ -14,7 +35,7 @@ Creates sandboxes and reopens them by id (`get` or `attach`). Closing the client new Client(config: ClientConfig) ``` -Validates config and initializes the selected provider. Throws on invalid config. +Creates a client for the selected provider. Throws if config is invalid. **Parameters** @@ -46,7 +67,7 @@ await using client = new Client({ func NewClient(provider Provider, config *Config) (*Client, error) ``` -Validates config and initializes the provider. Invalid config returns a nil client and an error. +Creates a client for the selected provider. Invalid config returns a nil client and an error. **Parameters** @@ -104,7 +125,17 @@ defer client.Close(context.Background()) create(request?: CreateOptions, options?: OperationOptions): Promise ``` -Creates one sandbox and returns its handle. Omit the request (or pass `{}`) to use provider defaults. +Creates one sandbox and returns its handle. + + + Omit the request (or pass `{}`) to use Daytona's default snapshot. + + + Pass an `image` source. Empty options are rejected. + + + Pass a Blaxel-valid `name`, an `image` source, and client `region`. + **Parameters** @@ -141,6 +172,21 @@ const sandbox = await client.create( console.log(sandbox.id); ``` + + + + +```ts +const sandbox = await client.create( + { + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { timeout: 60_000 }, // deadline for this call (ms) +); +console.log(sandbox.id); +``` + @@ -150,7 +196,17 @@ console.log(sandbox.id); func (c *Client) Create(ctx context.Context, request *CreateOptions) (*Sandbox, error) ``` -Creates one sandbox and returns its handle. A nil request uses provider defaults. +Creates one sandbox and returns its handle. + + + A nil request uses Daytona's default snapshot. + + + Pass an image source. A nil request is rejected. + + + Pass a Blaxel-valid `Name`, an image source, and client `Region`. + **Parameters** @@ -193,6 +249,22 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), // deadline for this call + }, +}) +``` + @@ -202,7 +274,7 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ ### get -Looks up a sandbox by id and returns a handle the provider has verified. +Looks up a sandbox by id and returns a handle. The provider confirms that sandbox exists. @@ -226,10 +298,7 @@ get(id: string, options?: OperationOptions): Promise ```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); +console.log(sandbox.info()); ``` @@ -254,8 +323,45 @@ func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) ```go instance, err := client.Get(ctx, knownID) -// Info(): Id, Provider, Name, ProviderState, Region, Isolation, -// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal +fmt.Println(instance.Info()) +``` + + + + + + + + +### get + +Looks up a sandbox by **name**. The id is the create name. + + + + +```ts +get(id: string, options?: OperationOptions): Promise +``` + +**Example** + +```ts +const sandbox = await client.get(knownName); +console.log(sandbox.info()); +``` + + + + +```go +func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) +``` + +**Example** + +```go +instance, err := client.Get(ctx, knownName) fmt.Println(instance.Info()) ``` @@ -268,7 +374,7 @@ 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. +Builds a handle from a sandbox id. It does not check that the sandbox exists. diff --git a/website/content/docs/sdk/configuration.mdx b/website/content/docs/sdk/configuration.mdx index fcca0720..7f23fb17 100644 --- a/website/content/docs/sdk/configuration.mdx +++ b/website/content/docs/sdk/configuration.mdx @@ -28,7 +28,7 @@ type Config = { | `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 | +| `scope?` | [`Scope`](#scope) | App, environment, organization, project, or workspace | | `timeout?` | `number` | Default deadline (ms) for calls that omit their own timeout | **Example** @@ -47,6 +47,13 @@ await using client = new Client({ omits its own timeout, Daytona still uses a **60s** default on its side. + + Set client or create `timeout` for how long create may wait. `region` is + required for create. `endpoint` is not supported. Use one Blaxel + configuration per process; a client with different credentials or workspace + is rejected. + + @@ -67,7 +74,7 @@ Pass `config` to `NewClient(provider, config)`. | `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 | +| `Scope` | App, environment, organization, project, or workspace | | `Timeout` | Default client deadline via `sandbox.Duration` | **Example** @@ -87,6 +94,11 @@ client, err := sandbox.NewClient(provider, &sandbox.Config{ omits its own timeout, Daytona still uses a **60s** default on its side. + + Set client or create `Timeout` for how long create may wait. `Region` is + required for create. `Endpoint` is not supported. + + @@ -147,22 +159,24 @@ type AuthInput = -| 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 | +| `workspace` | Blaxel workspace name (required for Blaxel create) | -| 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 | +| `Workspace` | Blaxel workspace name (required for Blaxel create) | diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 672adb69..682c2d28 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -3,7 +3,7 @@ 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. +Request body for [`Client.create`](/docs/sdk/client#create). Omitted fields keep provider defaults. Unsupported fields are rejected. ## CreateOptions @@ -33,7 +33,7 @@ Durations on this type are **milliseconds**. Allowlists under `network` are plai | Field | Description | | --------------- | ------------------------------------------------------- | -| `name` | Display name. Explicit empty string asks for unnamed. | +| `name` | Sandbox name. See provider notes. | | `source` | Image, snapshot, warm pool, or provider default | | `runtime` | Language hint, user, working directory, entrypoint, PTY | | `isolation` | Isolation class | @@ -48,6 +48,22 @@ Durations on this type are **milliseconds**. Allowlists under `network` are plai | `readiness` | Probe when waiting until ready | | `observability` | Verbose logging / telemetry | + + Daytona treats `name` as a display name. Omit it to keep the provider default. + An explicit empty string asks for unnamed. + + + + Modal treats `name` as a display name. Omit it to keep the provider default. + An explicit empty string asks for unnamed. + + + + `name` is required (blank or whitespace-only is rejected) and is the sandbox + id. Use lowercase letters, digits, and single hyphens, at most 49 characters. + Look it up with `get(name)`. + + **Example** @@ -71,6 +87,18 @@ const sandbox = await client.create({ }); ``` + + + + +```ts +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, +}); +``` + @@ -99,7 +127,7 @@ Durations use `sandbox.Duration`. Optional numbers use `sandbox.Value`. | Field | Description | | --------------- | ------------------------------------------------------- | -| `Name` | Display name. Explicit empty string asks for unnamed. | +| `Name` | Sandbox name. See provider notes. | | `Source` | Image, snapshot, warm pool, or provider default | | `Runtime` | Language hint, user, working directory, entrypoint, PTY | | `Isolation` | Isolation class | @@ -114,6 +142,22 @@ Durations use `sandbox.Duration`. Optional numbers use `sandbox.Value`. | `Readiness` | Probe when waiting until ready | | `Observability` | Verbose logging / telemetry | + + Daytona treats `Name` as a display name. Omit it to keep the provider default. + An explicit empty string asks for unnamed. + + + + Modal treats `Name` as a display name. Omit it to keep the provider default. + An explicit empty string asks for unnamed. + + + + `Name` is required (blank or whitespace-only is rejected) and is the sandbox + id. Use lowercase letters, digits, and single hyphens, at most 49 characters. + Look it up with `Get(name)`. + + **Example** @@ -146,6 +190,22 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, +}) +``` + @@ -153,7 +213,7 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ ## Source -Set exactly one variant. Unsupported variants fail before the provider call. +Set exactly one variant. Unsupported variants are rejected. @@ -191,6 +251,14 @@ Set exactly one variant. Unsupported variants fail before the provider call. and `idleTerminate` only. + + Requires `name`, an `image` source, and client `region`. Ports 80, 443, and + 8080 are reserved. Default port transport is HTTP; plain is TCP; TLS is TLS. + HTTP/2 over TLS is not supported. Runtime and isolation are not supported. Set + create timeout for how long create may wait (1–50s frees the name on + timeout). Labels on `info()` appear only when Blaxel reports them. + + ## RuntimeConfig @@ -205,6 +273,10 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `entrypoint` | Entrypoint argv | | `pty` | Request a PTY when supported | + + Runtime fields are not supported on create. + + @@ -217,6 +289,10 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `Entrypoint` | Entrypoint argv | | `Pty` | Request a PTY when supported | + + Runtime fields are not supported on create. + + @@ -233,6 +309,10 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `memoryLimitMib` | Optional memory ceiling | | `diskMib` | Requested disk in MiB | + + Pass `memoryMib` only. CPU and disk overrides are not supported. + + @@ -244,6 +324,10 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `MemoryLimitMib` | Optional memory ceiling | | `DiskMib` | Requested disk in MiB | + + Pass `MemoryMib` only. CPU and disk overrides are not supported. + + @@ -296,6 +380,12 @@ Set exactly one variant. Unsupported variants fail before the provider call. only. Ephemeral, idle stop/pause, and stopped archive/delete are rejected. + + Use `maximumLifetime` (≥5m) and `idleTerminate` (≥1m with `PolicyMode.After`). + Disabling idle terminate is not supported. Ephemeral, idle stop/pause, and + stopped archive/delete are rejected. + + @@ -320,6 +410,12 @@ Set exactly one variant. Unsupported variants fail before the provider call. only. Ephemeral, idle stop/pause, and stopped archive/delete are rejected. + + Use `MaximumLifetime` (≥5m) and `IdleTerminate` (≥1m with `PolicyModeAfter`). + Disabling idle terminate is not supported. Ephemeral, idle stop/pause, and + stopped archive/delete are rejected. + + @@ -366,6 +462,11 @@ Set exactly one variant. Unsupported variants fail before the provider call. positive timeout. `queueTimeout` is unsupported. + + Set `timeout` for how long create may wait (1–50s frees the name on timeout). + `queueTimeout` is not supported. `waitFor` only `Default` or `Scheduled`. + + @@ -386,6 +487,11 @@ Set exactly one variant. Unsupported variants fail before the provider call. positive timeout. `QueueTimeout` is unsupported. + + Set `Timeout` for how long create may wait (1–50s frees the name on timeout). + `QueueTimeout` is not supported. `WaitFor` only `Default` or `Scheduled`. + + @@ -401,6 +507,8 @@ Set either a TCP port or a command. | `tcpPort` | Port `1`–`65535` | | `command.argv` | Command argv that must succeed | +Readiness probes are not supported. + @@ -409,6 +517,8 @@ Set either a TCP port or a command. | `TcpPort` | Port `1`–`65535` | | `Command` with `Argv` | Command argv that must succeed | +Readiness probes are not supported. + @@ -430,6 +540,11 @@ Set either a TCP port or a command. | `customDomain` | Custom domain when supported | | `ports` | Published ports (`port`, optional `transport`) | + + Use ports (not 80, 443, or 8080) and outbound domains. Default transport is + HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not supported. + + @@ -446,6 +561,11 @@ Set either a TCP port or a command. | `CustomDomain` | Custom domain when supported | | `Ports` | Published ports (`Port`, optional `Transport`) | + + Use ports (not 80, 443, or 8080) and outbound domains. Default transport is + HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not supported. + + @@ -460,6 +580,10 @@ Set either a TCP port or a command. | `workloadIdentity` | Request a workload identity when supported | | `secrets` | Provider-managed secret references (`reference`, injection, env var name) | + + Security fields are not supported on create. + + @@ -469,6 +593,10 @@ Set either a TCP port or a command. | `WorkloadIdentity` | Request a workload identity when supported | | `Secrets` | Provider-managed secret references (`Reference`, injection, env var name) | + + Security fields are not supported on create. + + diff --git a/website/content/docs/sdk/enums.mdx b/website/content/docs/sdk/enums.mdx index b79e3bd3..cbd89ea0 100644 --- a/website/content/docs/sdk/enums.mdx +++ b/website/content/docs/sdk/enums.mdx @@ -62,6 +62,11 @@ How far provisioning wait-for waits before create returns. | `Started` | Return once the sandbox has started | | `Ready` | Return once readiness succeeds (probe + positive timeout when required) | + + Use only `Default` or `Scheduled`. Ready, Started, and Submitted are not + supported. + + ## EgressMode Outbound network policy on create. @@ -84,6 +89,13 @@ Transport for a published port. | `Http2Tls` | HTTP/2 over TLS | | `Plain` | Unencrypted / plain transport | + + Modal requires an explicit transport (`Default` is rejected). + + + `Default` is HTTP. `Plain` is TCP. `Tls` is TLS. `Http2Tls` is not supported. + + ## IsolationKind Sandbox isolation class. diff --git a/website/content/docs/sdk/filesystem.mdx b/website/content/docs/sdk/filesystem.mdx index 90055e93..dc0062e6 100644 --- a/website/content/docs/sdk/filesystem.mdx +++ b/website/content/docs/sdk/filesystem.mdx @@ -260,6 +260,11 @@ Creates the directory at `path`. An omitted `parents` flag is `true` (create mis filesystem calls. + + Missing parents are created when `parents` is omitted or `true`. `parents: + false` is not supported. + + @@ -289,6 +294,11 @@ Creates the directory at `path`. An omitted `Parents` flag is true. parent must already exist. + + Missing parents are created when `Parents` is omitted or true. `Parents: + false` is not supported. + + @@ -364,6 +374,12 @@ const info = await sandbox.filesystem.stat("/tmp/note.txt"); console.log(info); ``` + + You get `type` (file or directory), `size`, and usually `permissions`, + `owner`, `group`, and `modifiedAt`. `name` and `path` are whatever Blaxel + reports. + + @@ -385,6 +401,12 @@ info, err := instance.Filesystem().Stat(ctx, "/tmp/note.txt", nil) fmt.Println(info) ``` + + You get `Type` (file or directory), `Size`, and usually `Permissions`, + `Owner`, `Group`, and `ModifiedAt`. `Name` and `Path` are whatever Blaxel + reports. + + diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index bc0eac08..7b5dd05f 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -113,7 +113,7 @@ Outcome of a finished command. Daytona returns stdout only; `stderr` is empty (stdout may include merged - streams). `argv` is sent as a POSIX-quoted shell string. Omit/zero timeout + streams). `argv` is sent as one quoted shell command string. Omit/zero timeout sends `0` so Daytona does not apply its 10s command default. @@ -123,6 +123,14 @@ Outcome of a finished command. explicit 0). + +You get `stdout` and `stderr`. `argv` becomes one shell command string. + +Omit or zero `timeout` to wait until the command ends. A positive timeout must +be whole seconds and caps how long the command may run. Abort stops the wait +only — the remote command may keep running. + + @@ -134,7 +142,7 @@ Outcome of a finished command. 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 + streams). Argv is sent as one quoted shell command string. Omit/zero Timeout sends 0 so Daytona does not apply its 10s command default. @@ -144,5 +152,13 @@ Outcome of a finished command. explicit 0). + +You get Stdout and Stderr. Argv becomes one shell command string. + +Omit or zero Timeout to wait until the command ends. A positive Timeout must be +whole seconds and caps how long the command may run. Cancel stops the wait +only — the remote command may keep running. + + diff --git a/website/content/docs/sdk/sandbox-info.mdx b/website/content/docs/sdk/sandbox-info.mdx index 47fbe0de..f2ad73e7 100644 --- a/website/content/docs/sdk/sandbox-info.mdx +++ b/website/content/docs/sdk/sandbox-info.mdx @@ -19,20 +19,25 @@ Returned by info and many lifecycle methods. Values are a **copy**; mutating the -| 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 | +| Field | Description | +| ------------------ | -------------------------------------------------------- | +| `id` | Sandbox identifier | +| `provider` | Provider identifier | +| `name` | Display name when reported | +| `providerState` | Provider-specific status string; prefer portable `state` | +| `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 | + + + `labels` is set only when Blaxel returns them — not copied from the create + request. + **Example** @@ -46,20 +51,25 @@ 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 | +| Field | Description | +| ------------------ | -------------------------------------------------------- | +| `Id` | Sandbox identifier | +| `Provider` | Provider identifier | +| `Name` | Display name when reported | +| `ProviderState` | Provider-specific status string; prefer portable `State` | +| `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 | + + + `Labels` is set only when Blaxel returns them — not copied from the create + request. + **Example** diff --git a/website/content/docs/sdk/sandbox.mdx b/website/content/docs/sdk/sandbox.mdx index 0b1173b5..126af7cc 100644 --- a/website/content/docs/sdk/sandbox.mdx +++ b/website/content/docs/sdk/sandbox.mdx @@ -3,7 +3,10 @@ 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. +Handle returned when you create or reopen a sandbox. Lifecycle methods that +return info also update the handle. Closing only drops local resources—the cloud +sandbox keeps running. Check capabilities before a lifecycle method that might +be unavailable. ## Properties @@ -123,6 +126,16 @@ if (!sandbox.supports(LifecycleCapability.Terminate)) { } ``` + + + + +```ts +if (!sandbox.supports(LifecycleCapability.Delete)) { + throw new Error(`cannot delete ${sandbox.id}`); +} +``` + @@ -165,6 +178,16 @@ if !instance.Supports(sandbox.LifecycleCapabilityTerminate) { } ``` + + + + +```go +if !instance.Supports(sandbox.LifecycleCapabilityDelete) { + return fmt.Errorf("cannot delete %s", instance.ID()) +} +``` + @@ -428,6 +451,89 @@ If you omit the timeout (or pass `0`), Daytona still uses its own **60s** defaul + + +### Refresh + + + + +```ts +refresh(options?: OperationOptions): Promise +``` + +Reloads metadata from Blaxel by sandbox name. + + + + +```go +func (s *Sandbox) Refresh(ctx context.Context) error +``` + + + + +### Delete + + + + +```ts +delete(options?: DeleteCallOptions): Promise +``` + +Deletes the Blaxel sandbox. On success, `state` is `deleted` and `terminal` is true. Omit `wait` to return when Blaxel accepts the delete. Pass `wait: true` to wait until removal finishes. + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------- | ------------------------------------------------- | +| `options?` | `DeleteCallOptions` | `OperationOptions` plus optional `wait?: boolean` | + +**Returns** + +`Promise` — also stored on the handle + +```ts +const info = await sandbox.delete({ wait: true, timeout: 60_000 }); +console.log(info.state, info.terminal); +``` + + + + +```go +func (s *Sandbox) Delete(ctx context.Context, options *DeleteOptions) error +``` + +Deletes the Blaxel sandbox. On success, `Info().State` is `Deleted` and `Terminal` is true. Omit `Wait` to return when Blaxel accepts the delete. Pass `Wait: true` to wait until removal finishes. Read the outcome with `Info()`. + +**Parameters** + +| Name | Type | Description | +| --------- | ----------------- | -------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `options` | `*DeleteOptions` | `Timeout`, optional `Wait` | + +**Returns** + +`error` + +```go +err := instance.Delete(ctx, &sandbox.DeleteOptions{ + Wait: sandbox.Value(true), + Timeout: sandbox.Duration(time.Minute), +}) +info := instance.Info() +fmt.Println(info.State, info.GetTerminal()) +``` + + + + + + ### Terminate @@ -493,7 +599,8 @@ fmt.Println(result) -`timeout` / `Timeout` caps how long this call waits. It is not sent to Modal as a separate terminate setting. +The call timeout caps how long this call waits. It is not sent to Modal as a +separate terminate setting. @@ -530,7 +637,7 @@ Releases local handle resources. The cloud sandbox is unchanged. ## Operation options -Shared call options for TypeScript lifecycle and filesystem/process calls. +Shared call options for lifecycle and filesystem/process calls. @@ -546,17 +653,30 @@ 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. | +| 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. | + + + On Daytona, `wait: true` waits until deletion finishes. + + + + On Modal, `wait: true` waits until terminate finishes. + + + + Omit `wait` to return when delete is accepted. Pass `wait: true` to wait until + removal finishes. On success, `state` is `deleted` and `terminal` is true. + -Lifecycle option messages carry `Timeout *durationpb.Duration` (via `sandbox.Duration`). `StopOptions` adds `Force *bool`. `TerminateOptions` and `DeleteOptions` add `Wait *bool`. +Use `sandbox.Duration` for timeouts. `StopOptions` adds `Force`. `TerminateOptions` and `DeleteOptions` add `Wait`. | Field | Description | | --------- | -------------------------------------------------------------------------- | @@ -564,5 +684,19 @@ Lifecycle option messages carry `Timeout *durationpb.Duration` (via `sandbox.Dur | `Force` | On stop: `Value(true)` requests an immediate stop. | | `Wait` | On terminate/delete: wait until finished. | + + On Daytona, `Wait` waits until deletion finishes. + + + + On Modal, `Wait` waits until terminate finishes. + + + + Omit `Wait` to return when delete is accepted. Pass `Wait: true` to wait until + removal finishes. On success, `State` is `Deleted` and `Terminal` is true — + read it with `Info()`. + + diff --git a/website/src/components/provider-context.tsx b/website/src/components/provider-context.tsx index b3d36068..c7c55244 100644 --- a/website/src/components/provider-context.tsx +++ b/website/src/components/provider-context.tsx @@ -159,7 +159,7 @@ export function ProviderSelect({ className }: { className?: string }) { } /** Pages that are already scoped to one provider — no page-level selector. */ -const PROVIDER_SCOPED_PAGES = new Set(["daytona", "modal"]); +const PROVIDER_SCOPED_PAGES = new Set(["daytona", "modal", "blaxel"]); export function DocsProviderSelect({ path }: { path: string }) { const slug = diff --git a/website/src/lib/providers.ts b/website/src/lib/providers.ts index 4a65eee5..38a0d1de 100644 --- a/website/src/lib/providers.ts +++ b/website/src/lib/providers.ts @@ -1,6 +1,7 @@ export const DOC_PROVIDERS = [ { id: "daytona", label: "Daytona" }, { id: "modal", label: "Modal" }, + { id: "blaxel", label: "Blaxel" }, ] as const; export type DocProviderId = (typeof DOC_PROVIDERS)[number]["id"]; @@ -24,20 +25,20 @@ export const PROVIDER_SCOPED_HEADINGS: Record< > = { lifecycle: { "stop-start-pause-and-archive": ["daytona"], - delete: ["daytona"], + delete: ["daytona", "blaxel"], terminate: ["modal"], }, client: { - get: ["daytona"], + get: ["daytona", "blaxel"], attach: ["modal"], }, sandbox: { - refresh: ["daytona"], + refresh: ["daytona", "blaxel"], start: ["daytona"], stop: ["daytona"], pause: ["daytona"], archive: ["daytona"], - delete: ["daytona"], + delete: ["daytona", "blaxel"], terminate: ["modal"], }, };