diff --git a/.gitignore b/.gitignore index 07d74c21..50db9728 100644 --- a/.gitignore +++ b/.gitignore @@ -39,14 +39,3 @@ npm-debug.log* # Rust Cargo.lock **/*.rs.bk - -# Agent runtime directories -.agents/ -.claude/ -.opencode/ - -# Example temp files -.tmp-upload/ - -# CLI binaries (downloaded during npm publish) -sdks/cli/platforms/*/bin/ diff --git a/docs/deploy/computesdk.mdx b/docs/deploy/computesdk.mdx index 5e07da07..fd6fd5c4 100644 --- a/docs/deploy/computesdk.mdx +++ b/docs/deploy/computesdk.mdx @@ -1,146 +1,62 @@ --- title: "ComputeSDK" -description: "Deploy the daemon using ComputeSDK's provider-agnostic sandbox API." +description: "Deploy the daemon inside a ComputeSDK sandbox." --- -[ComputeSDK](https://computesdk.com) provides a unified interface for managing sandboxes across multiple providers. Write once, deploy anywhere—switch providers by changing environment variables. +## Overview + +ComputeSDK is a meta-SDK that provides a unified interface across multiple sandbox providers (E2B, Vercel, Modal, Railway, Daytona, and more). You can switch providers by changing environment variables without modifying your code. ## Prerequisites -- `COMPUTESDK_API_KEY` from [console.computesdk.com](https://console.computesdk.com) -- Provider API key (one of: `E2B_API_KEY`, `DAYTONA_API_KEY`, `VERCEL_TOKEN`, `MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET`, `BLAXEL_API_KEY`, `CSB_API_KEY`) +- `COMPUTESDK_API_KEY` environment variable (get one at [console.computesdk.com](https://console.computesdk.com/register)) +- A provider API key (e.g., `E2B_API_KEY`, `VERCEL_TOKEN`, `DAYTONA_API_KEY`) - `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` for the coding agents ## TypeScript Example ```typescript -import { - compute, - detectProvider, - getMissingEnvVars, - getProviderConfigFromEnv, - isProviderAuthComplete, - isValidProvider, - PROVIDER_NAMES, - type ExplicitComputeConfig, - type ProviderName, -} from "computesdk"; +import { compute } from "computesdk"; import { SandboxAgent } from "sandbox-agent"; -const PORT = 3000; -const REQUEST_TIMEOUT_MS = - Number.parseInt(process.env.COMPUTESDK_TIMEOUT_MS || "", 10) || 120_000; - -/** - * Detects and validates the provider to use. - * Priority: COMPUTESDK_PROVIDER env var > auto-detection from API keys - */ -function resolveProvider(): ProviderName { - const providerOverride = process.env.COMPUTESDK_PROVIDER; - - if (providerOverride) { - if (!isValidProvider(providerOverride)) { - throw new Error( - `Unsupported provider "${providerOverride}". Supported: ${PROVIDER_NAMES.join(", ")}` - ); - } - if (!isProviderAuthComplete(providerOverride)) { - const missing = getMissingEnvVars(providerOverride); - throw new Error( - `Missing credentials for "${providerOverride}". Set: ${missing.join(", ")}` - ); - } - return providerOverride as ProviderName; - } - - const detected = detectProvider(); - if (!detected) { - throw new Error( - `No provider credentials found. Set one of: ${PROVIDER_NAMES.map((p) => getMissingEnvVars(p).join(", ")).join(" | ")}` - ); - } - return detected as ProviderName; -} - -function configureComputeSDK(): void { - const provider = resolveProvider(); - - const config: ExplicitComputeConfig = { - provider, - computesdkApiKey: process.env.COMPUTESDK_API_KEY, - requestTimeoutMs: REQUEST_TIMEOUT_MS, - }; - - // Add provider-specific config from environment - const providerConfig = getProviderConfigFromEnv(provider); - if (Object.keys(providerConfig).length > 0) { - (config as any)[provider] = providerConfig; - } - - compute.setConfig(config); -} - -configureComputeSDK(); - -// Build environment variables to pass to sandbox +// Pass API keys to the sandbox const envs: Record = {}; if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY; -// Create sandbox +// Create sandbox with sandbox-agent server pre-configured const sandbox = await compute.sandbox.create({ - envs: Object.keys(envs).length > 0 ? envs : undefined, + envs, + servers: [ + { + slug: "sandbox-agent", + install: + "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh && " + + "sandbox-agent install-agent claude && " + + "sandbox-agent install-agent codex", + start: "sandbox-agent server --no-token --host 0.0.0.0 --port 3000", + port: 3000, + environment: envs, + health_check: { + path: "/v1/health", + interval_ms: 2000, + }, + }, + ], }); -// Helper to run commands with error handling -const run = async (cmd: string, options?: { background?: boolean }) => { - const result = await sandbox.runCommand(cmd, options); - if (typeof result?.exitCode === "number" && result.exitCode !== 0) { - throw new Error(`Command failed: ${cmd} (exit ${result.exitCode})\n${result.stderr || ""}`); - } - return result; -}; - -// Install sandbox-agent -await run("curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"); - -// Install agents conditionally based on available API keys -if (envs.ANTHROPIC_API_KEY) { - await run("sandbox-agent install-agent claude"); -} -if (envs.OPENAI_API_KEY) { - await run("sandbox-agent install-agent codex"); -} - -// Start the server in the background -await run(`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`, { background: true }); - -// Get the public URL for the sandbox -const baseUrl = await sandbox.getUrl({ port: PORT }); - -// Wait for server to be ready -const deadline = Date.now() + REQUEST_TIMEOUT_MS; -while (Date.now() < deadline) { - try { - const response = await fetch(`${baseUrl}/v1/health`); - if (response.ok) { - const data = await response.json(); - if (data?.status === "ok") break; - } - } catch { - // Server not ready yet - } - await new Promise((r) => setTimeout(r, 500)); -} - // Connect to the server +const baseUrl = await sandbox.getUrl({ port: 3000 }); const client = await SandboxAgent.connect({ baseUrl }); -// Detect which agent to use based on available API keys -const agent = envs.ANTHROPIC_API_KEY ? "claude" : "codex"; +// Wait for server to be ready +await client.waitForHealth(); // Create a session and start coding -await client.createSession("my-session", { agent }); +await client.createSession("my-session", { + agent: "claude", + permissionMode: "default", +}); await client.postMessage("my-session", { message: "Summarize this repository", @@ -154,61 +70,38 @@ for await (const event of client.streamEvents("my-session")) { await sandbox.destroy(); ``` -## Supported Providers - -ComputeSDK auto-detects your provider from environment variables: - -| Provider | Environment Variables | -|----------|----------------------| -| E2B | `E2B_API_KEY` | -| Daytona | `DAYTONA_API_KEY` | -| Vercel | `VERCEL_TOKEN` or `VERCEL_OIDC_TOKEN` | -| Modal | `MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET` | -| Blaxel | `BLAXEL_API_KEY` | -| CodeSandbox | `CSB_API_KEY` | +## Provider Flexibility -## Notes - -- **Provider resolution order**: `COMPUTESDK_PROVIDER` env var takes priority, otherwise auto-detection from API keys. -- **Conditional agent installation**: Only agents with available API keys are installed, reducing setup time. -- **Command error handling**: The example validates exit codes and throws on failures for easier debugging. -- `sandbox.runCommand(..., { background: true })` keeps the server running while your app continues. -- `sandbox.getUrl({ port })` returns a public URL for the sandbox port. -- Always destroy the sandbox when you are done to avoid leaking resources. -- If sandbox creation times out, set `COMPUTESDK_TIMEOUT_MS` to a higher value (default: 120000ms). - -## Explicit Provider Selection - -To force a specific provider instead of auto-detection, set the `COMPUTESDK_PROVIDER` environment variable: +ComputeSDK automatically detects your provider based on environment variables. To switch providers, simply change which API key you set: ```bash -export COMPUTESDK_PROVIDER=e2b -``` +# Use E2B +export E2B_API_KEY=your_e2b_key -Or configure programmatically using `getProviderConfigFromEnv()`: +# Or use Vercel +export VERCEL_TOKEN=your_vercel_token +export VERCEL_TEAM_ID=your_team_id +export VERCEL_PROJECT_ID=your_project_id -```typescript -import { compute, getProviderConfigFromEnv, type ExplicitComputeConfig } from "computesdk"; - -const config: ExplicitComputeConfig = { - provider: "e2b", - computesdkApiKey: process.env.COMPUTESDK_API_KEY, - requestTimeoutMs: 120_000, -}; - -// Automatically populate provider-specific config from environment -const providerConfig = getProviderConfigFromEnv("e2b"); -if (Object.keys(providerConfig).length > 0) { - (config as any).e2b = providerConfig; -} +# Or use Daytona +export DAYTONA_API_KEY=your_daytona_key -compute.setConfig(config); +# And always set ComputeSDK key +export COMPUTESDK_API_KEY=your_computesdk_key ``` -## Direct Mode (No ComputeSDK API Key) +Your code stays the same regardless of which provider you choose. -To bypass the ComputeSDK gateway and use provider SDKs directly, see the provider-specific examples: +## Supported Providers -- [E2B](/deploy/e2b) -- [Daytona](/deploy/daytona) -- [Vercel](/deploy/vercel) +ComputeSDK supports the following providers: +- E2B +- Vercel +- Daytona +- Modal +- Railway +- Render +- Blaxel +- Namespace + +See the [ComputeSDK documentation](https://computesdk.com/docs) for provider-specific setup instructions. diff --git a/examples/computesdk/package.json b/examples/computesdk/package.json index e22b51b1..1447dcf5 100644 --- a/examples/computesdk/package.json +++ b/examples/computesdk/package.json @@ -7,8 +7,9 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "computesdk": "^2.2.0", + "dotenv": "^16.0.0", "@sandbox-agent/example-shared": "workspace:*", - "computesdk": "latest", "sandbox-agent": "workspace:*" }, "devDependencies": { diff --git a/examples/computesdk/src/computesdk.ts b/examples/computesdk/src/computesdk.ts index bc2ddc6d..9120fc96 100644 --- a/examples/computesdk/src/computesdk.ts +++ b/examples/computesdk/src/computesdk.ts @@ -1,164 +1,49 @@ -import { - compute, - detectProvider, - getMissingEnvVars, - getProviderConfigFromEnv, - isProviderAuthComplete, - isValidProvider, - PROVIDER_NAMES, - type ExplicitComputeConfig, - type ProviderName, -} from "computesdk"; -import { SandboxAgent } from "sandbox-agent"; -import { detectAgent, buildInspectorUrl, waitForHealth } from "@sandbox-agent/example-shared"; -import { fileURLToPath } from "node:url"; -import { resolve } from "node:path"; - -const PORT = 3000; -const REQUEST_TIMEOUT_MS = - Number.parseInt(process.env.COMPUTESDK_TIMEOUT_MS || "", 10) || 120_000; - -/** - * Detects and validates the provider to use. - * Priority: COMPUTESDK_PROVIDER env var > auto-detection from API keys - */ -function resolveProvider(): ProviderName { - const providerOverride = process.env.COMPUTESDK_PROVIDER; - - if (providerOverride) { - if (!isValidProvider(providerOverride)) { - throw new Error( - `Unsupported ComputeSDK provider "${providerOverride}". Supported providers: ${PROVIDER_NAMES.join(", ")}` - ); - } - if (!isProviderAuthComplete(providerOverride)) { - const missing = getMissingEnvVars(providerOverride); - throw new Error( - `Missing credentials for provider "${providerOverride}". Set: ${missing.join(", ")}` - ); - } - console.log(`Using ComputeSDK provider: ${providerOverride} (explicit)`); - return providerOverride as ProviderName; - } - - const detected = detectProvider(); - if (!detected) { - throw new Error( - `No provider credentials found. Set one of: ${PROVIDER_NAMES.map((p) => getMissingEnvVars(p).join(", ")).join(" | ")}` - ); - } - console.log(`Using ComputeSDK provider: ${detected} (auto-detected)`); - return detected as ProviderName; -} - -function configureComputeSDK(): void { - const provider = resolveProvider(); - - const config: ExplicitComputeConfig = { - provider, - computesdkApiKey: process.env.COMPUTESDK_API_KEY, - requestTimeoutMs: REQUEST_TIMEOUT_MS, - }; - - const providerConfig = getProviderConfigFromEnv(provider); - if (Object.keys(providerConfig).length > 0) { - const configWithProvider = - config as ExplicitComputeConfig & Record>; - configWithProvider[provider] = providerConfig; - } - - compute.setConfig(config); -} - -configureComputeSDK(); - -const buildEnv = (): Record => { - const env: Record = {}; - if (process.env.ANTHROPIC_API_KEY) env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; - if (process.env.OPENAI_API_KEY) env.OPENAI_API_KEY = process.env.OPENAI_API_KEY; - return env; +import "dotenv/config"; +import { compute } from "computesdk"; +import { runPrompt, waitForHealth } from "@sandbox-agent/example-shared"; + +const envs: Record = {}; +if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; +if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY; + +console.log("Creating ComputeSDK sandbox..."); +const sandbox = await compute.sandbox.create({ + envs, + servers: [ + { + slug: "sandbox-agent", + install: + "export BIN_DIR=$HOME/.local/bin && " + + "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh && " + + "export PATH=$BIN_DIR:$PATH && " + + "sandbox-agent install-agent claude && " + + "sandbox-agent install-agent codex", + start: "export PATH=$HOME/.local/bin:$PATH && sandbox-agent server --no-token --host 0.0.0.0 --port 3000", + port: 3000, + environment: envs, + health_check: { + path: "/v1/health", + interval_ms: 2000, + timeout_ms: 5000, + delay_ms: 3000, + }, + restart_policy: "on-failure", + max_restarts: 3, + }, + ], +}); + +const baseUrl = await sandbox.getUrl({ port: 3000 }); + +console.log("Waiting for server..."); +await waitForHealth({ baseUrl }); + +const cleanup = async () => { + await sandbox.destroy(); + process.exit(0); }; +process.once("SIGINT", cleanup); +process.once("SIGTERM", cleanup); -export async function setupComputeSdkSandboxAgent(): Promise<{ - baseUrl: string; - cleanup: () => Promise; -}> { - const env = buildEnv(); - - console.log("Creating ComputeSDK sandbox..."); - const sandbox = await compute.sandbox.create({ - envs: Object.keys(env).length > 0 ? env : undefined, - }); - - const run = async (cmd: string, options?: { background?: boolean }) => { - const result = await sandbox.runCommand(cmd, options); - if (typeof result?.exitCode === "number" && result.exitCode !== 0) { - throw new Error(`Command failed: ${cmd} (exit ${result.exitCode})\n${result.stderr || ""}`); - } - return result; - }; - - console.log("Installing sandbox-agent..."); - await run("curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"); - - if (env.ANTHROPIC_API_KEY) { - console.log("Installing Claude agent..."); - await run("sandbox-agent install-agent claude"); - } - - if (env.OPENAI_API_KEY) { - console.log("Installing Codex agent..."); - await run("sandbox-agent install-agent codex"); - } - - console.log("Starting server..."); - await run(`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`, { background: true }); - - const baseUrl = await sandbox.getUrl({ port: PORT }); - - console.log("Waiting for server..."); - await waitForHealth({ baseUrl }); - - const cleanup = async () => { - try { - await sandbox.destroy(); - } catch (error) { - console.warn("Cleanup failed:", error instanceof Error ? error.message : error); - } - }; - - return { baseUrl, cleanup }; -} - -export async function runComputeSdkExample(): Promise { - const { baseUrl, cleanup } = await setupComputeSdkSandboxAgent(); - - const handleExit = async () => { - await cleanup(); - process.exit(0); - }; - - process.once("SIGINT", handleExit); - process.once("SIGTERM", handleExit); - - const client = await SandboxAgent.connect({ baseUrl }); - const session = await client.createSession({ agent: detectAgent(), sessionInit: { cwd: "/home", mcpServers: [] } }); - const sessionId = session.id; - - console.log(` UI: ${buildInspectorUrl({ baseUrl, sessionId })}`); - console.log(" Press Ctrl+C to stop."); - - // Keep alive until SIGINT/SIGTERM triggers cleanup above - await new Promise(() => {}); -} - -const isDirectRun = Boolean( - process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url) -); - -if (isDirectRun) { - runComputeSdkExample().catch((error) => { - console.error(error instanceof Error ? error.message : error); - process.exit(1); - }); -} +await runPrompt(baseUrl); +await cleanup(); diff --git a/examples/computesdk/tests/computesdk.test.ts b/examples/computesdk/tests/computesdk.test.ts index 9d023a9b..8d418d8d 100644 --- a/examples/computesdk/tests/computesdk.test.ts +++ b/examples/computesdk/tests/computesdk.test.ts @@ -1,19 +1,8 @@ import { describe, it, expect } from "vitest"; import { buildHeaders } from "@sandbox-agent/example-shared"; -import { setupComputeSdkSandboxAgent } from "../src/computesdk.ts"; +import { setupComputeSDKSandboxAgent } from "../src/computesdk.ts"; -const hasModal = Boolean(process.env.MODAL_TOKEN_ID && process.env.MODAL_TOKEN_SECRET); -const hasVercel = Boolean(process.env.VERCEL_TOKEN || process.env.VERCEL_OIDC_TOKEN); -const hasProviderKey = Boolean( - process.env.BLAXEL_API_KEY || - process.env.CSB_API_KEY || - process.env.DAYTONA_API_KEY || - process.env.E2B_API_KEY || - hasModal || - hasVercel -); - -const shouldRun = Boolean(process.env.COMPUTESDK_API_KEY) && hasProviderKey; +const shouldRun = Boolean(process.env.COMPUTESDK_API_KEY); const timeoutMs = Number.parseInt(process.env.SANDBOX_TEST_TIMEOUT_MS || "", 10) || 300_000; const testFn = shouldRun ? it : it.skip; @@ -22,10 +11,10 @@ describe("computesdk example", () => { testFn( "starts sandbox-agent and responds to /v1/health", async () => { - const { baseUrl, cleanup } = await setupComputeSdkSandboxAgent(); + const { baseUrl, token, cleanup } = await setupComputeSDKSandboxAgent(); try { const response = await fetch(`${baseUrl}/v1/health`, { - headers: buildHeaders({}), + headers: buildHeaders({ token }), }); expect(response.ok).toBe(true); const data = await response.json(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e485dba..6215bb80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,8 +68,11 @@ importers: specifier: workspace:* version: link:../shared computesdk: - specifier: latest + specifier: ^2.2.0 version: 2.2.0 + dotenv: + specifier: ^16.0.0 + version: 16.6.1 sandbox-agent: specifier: workspace:* version: link:../../sdks/typescript @@ -2699,6 +2702,9 @@ packages: '@types/pg@8.16.0': resolution: {integrity: sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==} + '@types/node@25.2.0': + resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==} + '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} @@ -3254,6 +3260,10 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} @@ -7659,6 +7669,10 @@ snapshots: pg-protocol: 1.11.0 pg-types: 2.2.0 + '@types/node@25.2.0': + dependencies: + undici-types: 7.16.0 + '@types/prop-types@15.7.15': {} '@types/react-dom@18.3.7(@types/react@18.3.27)': @@ -8319,6 +8333,8 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dotenv@16.6.1: {} + dotenv@17.2.3: {} dset@3.1.4: {} @@ -10628,6 +10644,24 @@ snapshots: - tsx - yaml + vite-node@3.2.4(@types/node@25.2.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 5.4.21(@types/node@25.2.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite@5.4.21(@types/node@22.19.7): dependencies: esbuild: 0.21.5 @@ -10661,7 +10695,16 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vite@6.4.1(@types/node@25.2.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2): + vite@5.4.21(@types/node@25.2.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.56.0 + optionalDependencies: + '@types/node': 25.2.0 + fsevents: 2.3.3 + + vite@6.4.1(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -10764,6 +10807,45 @@ snapshots: - tsx - yaml + vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@25.2.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 5.4.21(@types/node@25.2.0) + vite-node: 3.2.4(@types/node@25.2.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 25.2.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vscode-languageserver-textdocument@1.0.12: {} vscode-languageserver-types@3.17.5: {}