diff --git a/sandbox/index.md b/sandbox/index.md index 37d07803..7773bc9b 100644 --- a/sandbox/index.md +++ b/sandbox/index.md @@ -1,6 +1,6 @@ --- title: Sandbox Overview and Capabilities -description: Explore Sandbox creation, commands, files, pause and resume, and lifecycle events through the experimental Playground, with clear capability and resource boundaries. +description: Create sandboxes with the E2B SDK or SandBase Console, inspect status, logs, and metrics, and explore the separate experimental Playground. --- # Sandbox overview and capabilities @@ -9,7 +9,9 @@ Sandbox provides isolated environments for running commands, executing code, and [Quickstart / E2B SDK](./quickstart) ยท [Open Playground (Experimental)](./playground) -## A five-minute walkthrough +Use the [official E2B SDK](./quickstart) to integrate Sandbox into your application. Sign in to the [Sandbox Console](https://www.sandbase.ai/console/sandboxes) to create sandboxes and view instance status, logs, and metrics. See the [Sandbox product overview](https://www.sandbase.ai/landing/sandbox) for an introduction. The walkthrough and support table below describe the separate Experimental Playground. + +## A five-minute Playground walkthrough 1. Enter an [API key](/getting-started/api-keys) with Sandbox permissions. Connecting only reads templates and instances. 2. Choose an available template, acknowledge the resource notice, and create a short-lived instance. diff --git a/sandbox/quickstart.md b/sandbox/quickstart.md index a360da01..ec58b35d 100644 --- a/sandbox/quickstart.md +++ b/sandbox/quickstart.md @@ -13,6 +13,8 @@ import SandboxPromo from '../.vitepress/theme/SandboxPromo.vue' Use the official E2B SDK with your SandBase API key to create an isolated sandbox, run a command, and read and write files. This guide targets **E2B JavaScript and Python SDK 2.51.0**. +For browser-based creation and observation, sign in to the [Sandbox Console](https://www.sandbase.ai/console/sandboxes) to create an instance and view its status, logs, and metrics. Continue with the SDK steps below for application integration, or read the [Sandbox product overview](https://www.sandbase.ai/landing/sandbox). + ## 1. Configure the official SDK [Create a SandBase API key in the Console](https://www.sandbase.ai/console/keys), then configure the standard E2B environment variables. The official SDK reads these automatically in both JavaScript and Python. diff --git a/scripts/public-sandbox-page-links.mjs b/scripts/public-sandbox-page-links.mjs new file mode 100644 index 00000000..080778d6 --- /dev/null +++ b/scripts/public-sandbox-page-links.mjs @@ -0,0 +1,53 @@ +import assert from 'node:assert/strict' + +// These are published product pages, not API endpoints. Match the complete href +// in Markdown, HTML or a compiled href property; never strip URL prefixes. +const approvedPage = String.raw`https:\/\/www\.sandbase\.ai\/(?:console\/sandboxes|landing\/sandbox)` +const markdownHref = new RegExp(String.raw`(\]\()${approvedPage}(\))`, 'g') +const renderedHref = new RegExp(String.raw`(\bhref["']?\s*[:=]\s*)(\\?["'])${approvedPage}\2`, 'g') + +export const sandboxAPIPath = /\/(?:v1\/)?sandboxes?(?:\/|%7b|\{|:|\b)/i + +export function maskApprovedSandboxPageHrefs(content) { + return content + .replace(markdownHref, '$1/approved-product-page$2') + .replace(renderedHref, '$1$2/approved-product-page$2') +} + +// Both source and built validators import this module, so these regressions run +// in the existing CI commands without a separate test script. +for (const url of [ + 'https://www.sandbase.ai/console/sandboxes', + 'https://www.sandbase.ai/landing/sandbox', +]) { + for (const wrap of [ + value => `[Product](${value})`, + value => `Product`, + value => `Product`, + value => `({href:"${value}"})`, + value => `({"href":"${value}"})`, + value => String.raw`Product`, + ]) { + assert.doesNotMatch(maskApprovedSandboxPageHrefs(wrap(url)), sandboxAPIPath) + // An approved link must not exempt another API path in the same page. + assert.match(maskApprovedSandboxPageHrefs(`${wrap(url)} GET /v1/sandboxes`), sandboxAPIPath) + for (const suffix of ['/api', '/v1/sandboxes', '?next=/v1/sandboxes', '#/sandboxes']) { + const source = wrap(url + suffix) + assert.equal(maskApprovedSandboxPageHrefs(source), source) + if (url.includes('/sandboxes') || suffix.includes('/sandboxes')) { + assert.match(maskApprovedSandboxPageHrefs(source), sandboxAPIPath) + } + } + } +} +for (const source of [ + 'GET /sandboxes', 'POST /v1/sandboxes', 'GET /sandboxes/{id}/metrics', + '[API](https://api.sandbase.ai/sandboxes)', + '[API](https://www.sandbase.ai/v1/sandboxes)', + 'API', + 'API', + 'Not an approved absolute href', + 'fetch("https://www.sandbase.ai/console/sandboxes")', +]) { + assert.match(maskApprovedSandboxPageHrefs(source), sandboxAPIPath) +} diff --git a/scripts/validate-built-public-surface.mjs b/scripts/validate-built-public-surface.mjs index c71fabd8..295c1f5a 100644 --- a/scripts/validate-built-public-surface.mjs +++ b/scripts/validate-built-public-surface.mjs @@ -1,3 +1,4 @@ +import { maskApprovedSandboxPageHrefs, sandboxAPIPath } from './public-sandbox-page-links.mjs' import assert from 'node:assert/strict' import { existsSync, readFileSync, readdirSync } from 'node:fs' import path from 'node:path' @@ -40,7 +41,7 @@ assert.match(legacySeedance, //, 'L const forbidden = [ [/\/v1\/generations(?:\/|\b)/i, 'withdrawn generation API'], [/\/v1\/blog\/assets(?:\/|\b)/i, 'internal Blog publishing storage API'], - [/\/(?:v1\/)?sandboxes?(?:\/|%7b|\{|:|\b)/i, 'Sandbox API'], + [sandboxAPIPath, 'Sandbox API'], [/\/events\/webhooks(?:\/|\b)/i, 'Sandbox event webhook API'], [/\/v1\/endpoints\/[A-Za-z0-9_{}%.-]+\/mcp\b/i, 'Endpoint MCP transport'], [/\/v1\/endpoint_runtime_profiles\b/i, 'Endpoint runtime-profile discovery'], @@ -86,7 +87,7 @@ function inspect(directory) { // The experimental documentation routes and their dedicated lazy UI chunk are public. // Keep all former Sandbox API reference pages and unrelated bundles protected. if (/^assets\/(?:chunks\/)?SandboxPlayground\.[^/]+\.js$/.test(relative)) continue - const withoutGuideLinks = content.replace(/\/(?:docs\/)?sandbox\/(?:(?:playground|quickstart|index)(?:\.html)?)?(?=["'<\s?#])/g, '/experimental-guide') + const withoutGuideLinks = maskApprovedSandboxPageHrefs(content).replace(/\/(?:docs\/)?sandbox\/(?:(?:playground|quickstart|index)(?:\.html)?)?(?=["'<\s?#])/g, '/experimental-guide') assert.doesNotMatch(withoutGuideLinks, pattern, `${relative} must not publish the hidden ${label}`) continue } diff --git a/scripts/validate-public-api-surface.mjs b/scripts/validate-public-api-surface.mjs index afc37a27..c89ed3f7 100644 --- a/scripts/validate-public-api-surface.mjs +++ b/scripts/validate-public-api-surface.mjs @@ -1,3 +1,4 @@ +import { maskApprovedSandboxPageHrefs, sandboxAPIPath } from './public-sandbox-page-links.mjs' import assert from 'node:assert/strict' import { readdirSync, readFileSync } from 'node:fs' import path from 'node:path' @@ -1131,7 +1132,7 @@ function inspectPublishedSources(directory) { } // Only the explicitly published experimental Sandbox guide/playground may link this section. if (!['sandbox/index.md', 'sandbox/playground.md'].includes(relative)) { - assert.doesNotMatch(content, /\/(?:v1\/)?sandboxes?(?:\/|\{|:|\b)/i, `${relative} must not expose sandbox API paths`) + assert.doesNotMatch(maskApprovedSandboxPageHrefs(content), sandboxAPIPath, `${relative} must not expose sandbox API paths`) } if (!['api-reference/endpoints/index.md', 'api-reference/endpoints/mcp.md', 'agents/services.md'].includes(relative)) { assert.doesNotMatch(content, /\/v1\/endpoints\/[^\s`"']+\/mcp\b/i, `${relative} must not expose Endpoint MCP transport`)