diff --git a/README.md b/README.md index 1f90841..22b67be 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,14 @@ Choose the installation method that works best for your use case. Install skill with: -``` +```bash npx skills add rivet-dev/skills -s sandbox-agent ``` +```bash +bunx skills add rivet-dev/skills -s sandbox-agent +``` + ### TypeScript SDK Import the SDK directly into your Node or browser application. Full type safety and streaming support. @@ -77,6 +81,12 @@ Import the SDK directly into your Node or browser application. Full type safety npm install sandbox-agent ``` +```bash +bun add sandbox-agent +# Optional: allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()). +bun pm trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64 +``` + **Setup** Local (embedded mode): @@ -154,6 +164,12 @@ Install the CLI wrapper (optional but convenient): npm install -g @sandbox-agent/cli ``` +```bash +# Allow Bun to run postinstall scripts for native binaries. +bun add -g @sandbox-agent/cli +bun pm -g trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64 +``` + Create a session and send a message: ```bash @@ -168,6 +184,10 @@ You can also use npx like: npx sandbox-agent --help ``` +```bash +bunx sandbox-agent --help +``` + [CLI documentation](https://sandboxagent.dev/docs/cli) ### Inspector diff --git a/docs/ai/skill.mdx b/docs/ai/skill.mdx index bb48c2c..b06e657 100644 --- a/docs/ai/skill.mdx +++ b/docs/ai/skill.mdx @@ -13,9 +13,18 @@ https://rivet.dev/docs/skill.md To add it to an agent using the Skills CLI: -``` -npx skills add rivet-dev/skills -s sandbox-agent -``` + + + ```bash + npx skills add rivet-dev/skills -s sandbox-agent + ``` + + + ```bash + bunx skills add rivet-dev/skills -s sandbox-agent + ``` + + If you run a reverse proxy in front of the docs, make sure `/skill.md` and `/.well-known/skills/*` are forwarded to Mintlify. diff --git a/docs/deploy/local.mdx b/docs/deploy/local.mdx index e70a14f..5fafb50 100644 --- a/docs/deploy/local.mdx +++ b/docs/deploy/local.mdx @@ -15,11 +15,20 @@ curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh sandbox-agent server --no-token --host 127.0.0.1 --port 2468 ``` -Or with npm: +Or with npm or Bun: -```bash -npx sandbox-agent server --no-token --host 127.0.0.1 --port 2468 -``` + + + ```bash + npx sandbox-agent server --no-token --host 127.0.0.1 --port 2468 + ``` + + + ```bash + bunx sandbox-agent server --no-token --host 127.0.0.1 --port 2468 + ``` + + ## With the TypeScript SDK diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 4902344..f2a1f1b 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -6,9 +6,18 @@ icon: "rocket" - ```bash - npx skills add rivet-dev/skills -s sandbox-agent - ``` + + + ```bash + npx skills add rivet-dev/skills -s sandbox-agent + ``` + + + ```bash + bunx skills add rivet-dev/skills -s sandbox-agent + ``` + + @@ -88,6 +97,14 @@ icon: "rocket" ``` + + Run without installing globally. + + ```bash + bunx @sandbox-agent/cli server --no-token --host 0.0.0.0 --port 2468 + ``` + + Install globally, then run. @@ -97,17 +114,41 @@ icon: "rocket" ``` - - If you're running from source instead of the installed CLI. + + Install globally, then run. ```bash - cargo run -p sandbox-agent -- server --no-token --host 0.0.0.0 --port 2468 + bun add -g @sandbox-agent/cli + # Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()). + bun pm -g trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64 + sandbox-agent server --no-token --host 0.0.0.0 --port 2468 + ``` + + + + + For local development, use `SandboxAgent.start()` to automatically spawn and manage the server as a subprocess. + + ```bash + npm install sandbox-agent + ``` + + ```typescript + import { SandboxAgent } from "sandbox-agent"; + + const client = await SandboxAgent.start(); ``` - + For local development, use `SandboxAgent.start()` to automatically spawn and manage the server as a subprocess. + ```bash + bun add sandbox-agent + # Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()). + bun pm trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64 + ``` + ```typescript import { SandboxAgent } from "sandbox-agent"; @@ -116,6 +157,14 @@ icon: "rocket" This installs the binary and starts the server for you. No manual setup required. + + + If you're running from source instead of the installed CLI. + + ```bash + cargo run -p sandbox-agent -- server --no-token --host 0.0.0.0 --port 2468 + ``` + Binding to `0.0.0.0` allows the server to accept connections from any network interface, which is required when running inside a sandbox where clients connect remotely. diff --git a/docs/sdks/typescript.mdx b/docs/sdks/typescript.mdx index 7ebc165..7cd2ef1 100644 --- a/docs/sdks/typescript.mdx +++ b/docs/sdks/typescript.mdx @@ -9,9 +9,20 @@ client for sessions, events, and agent operations. ## Install -```bash -npm install sandbox-agent -``` + + + ```bash + npm install sandbox-agent + ``` + + + ```bash + bun add sandbox-agent + # Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()). + bun pm trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64 + ``` + + ## Create a client diff --git a/justfile b/justfile index d426bb4..2b3dd6b 100644 --- a/justfile +++ b/justfile @@ -46,3 +46,7 @@ check: [group('dev')] fmt: cargo fmt --all + +[group('dev')] +dev-docs: + cd docs && pnpm dlx mintlify dev diff --git a/package.json b/package.json index 66b5e57..a8566b6 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "build": "turbo run build", "dev": "turbo run dev --parallel", "generate": "turbo run generate", - "typecheck": "turbo run typecheck", - "dev:docs": "cd docs/ && pnpm dlx mintlify dev" + "typecheck": "turbo run typecheck" }, "devDependencies": { "turbo": "^2.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f384d73..321e588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -288,6 +288,10 @@ importers: version: 5.9.3 sdks/cli: + dependencies: + '@sandbox-agent/cli-shared': + specifier: workspace:* + version: link:../cli-shared optionalDependencies: '@sandbox-agent/cli-darwin-arm64': specifier: workspace:* @@ -306,6 +310,18 @@ importers: specifier: ^3.0.0 version: 3.2.4(@types/debug@4.1.12)(@types/node@25.1.0) + sdks/cli-shared: + devDependencies: + '@types/node': + specifier: ^22.0.0 + version: 22.19.7 + tsup: + specifier: ^8.0.0 + version: 8.5.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + typescript: + specifier: ^5.7.0 + version: 5.9.3 + sdks/cli/platforms/darwin-arm64: {} sdks/cli/platforms/darwin-x64: {} @@ -315,6 +331,10 @@ importers: sdks/cli/platforms/win32-x64: {} sdks/typescript: + dependencies: + '@sandbox-agent/cli-shared': + specifier: workspace:* + version: link:../cli-shared devDependencies: '@types/node': specifier: ^22.0.0 diff --git a/research/detect-sandbox.md b/research/detect-sandbox.md index 3c9961f..d081f1f 100644 --- a/research/detect-sandbox.md +++ b/research/detect-sandbox.md @@ -567,7 +567,7 @@ console.log(`Running on: ${provider.provider} (${provider.confidence} confidence Several open-source projects implement cloud detection patterns: - **cloud-detect** (Python, `pip install cloud-detect`): Detects AWS, GCP, Azure, Alibaba, DigitalOcean, Oracle via filesystem + metadata -- **cloud-detect-js** (Node, `npm install cloud-detect-js`): JavaScript port with similar capabilities +- **cloud-detect-js** (Node, `npm install cloud-detect-js` or `bun add cloud-detect-js`): JavaScript port with similar capabilities - **banzaicloud/satellite** (Go): Uses two-tier detection with sysfs first, then metadata fallback - **OpenTelemetry Resource Detectors**: Production-grade detectors across Node.js, Python, Go — use `@opentelemetry/resource-detector-aws`, `@opentelemetry/resource-detector-gcp`, etc. diff --git a/sdks/cli-shared/package.json b/sdks/cli-shared/package.json new file mode 100644 index 0000000..896c536 --- /dev/null +++ b/sdks/cli-shared/package.json @@ -0,0 +1,30 @@ +{ + "name": "@sandbox-agent/cli-shared", + "version": "0.1.4-rc.7", + "description": "Shared helpers for sandbox-agent CLI and SDK", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/rivet-dev/sandbox-agent" + }, + "type": "module", + "scripts": { + "build": "tsup", + "typecheck": "tsc --noEmit" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "devDependencies": { + "@types/node": "^22.0.0", + "tsup": "^8.0.0", + "typescript": "^5.7.0" + } +} diff --git a/sdks/cli-shared/src/index.ts b/sdks/cli-shared/src/index.ts new file mode 100644 index 0000000..3696364 --- /dev/null +++ b/sdks/cli-shared/src/index.ts @@ -0,0 +1,63 @@ +export type InstallCommandBlock = { + label: string; + commands: string[]; +}; + +export type NonExecutableBinaryMessageOptions = { + binPath: string; + trustPackages: string; + bunInstallBlocks: InstallCommandBlock[]; + genericInstallCommands?: string[]; +}; + +export function isBunRuntime(): boolean { + if (typeof process?.versions?.bun === "string") return true; + const userAgent = process?.env?.npm_config_user_agent || ""; + return userAgent.includes("bun/"); +} + +const PERMISSION_ERRORS = new Set(["EACCES", "EPERM", "ENOEXEC"]); + +export function isPermissionError(error: unknown): boolean { + if (!error || typeof error !== "object") return false; + const code = (error as { code?: unknown }).code; + return typeof code === "string" && PERMISSION_ERRORS.has(code); +} + +export function formatNonExecutableBinaryMessage( + options: NonExecutableBinaryMessageOptions, +): string { + const { binPath, trustPackages, bunInstallBlocks, genericInstallCommands } = + options; + + const lines = [`sandbox-agent binary is not executable: ${binPath}`]; + + if (isBunRuntime()) { + lines.push( + "Allow Bun to run postinstall scripts for native binaries and reinstall:", + ); + for (const block of bunInstallBlocks) { + lines.push(`${block.label}:`); + for (const command of block.commands) { + lines.push(` ${command}`); + } + } + lines.push(`Or run: chmod +x "${binPath}"`); + return lines.join("\n"); + } + + lines.push( + "Postinstall scripts for native packages did not run, so the binary was left non-executable.", + ); + if (genericInstallCommands && genericInstallCommands.length > 0) { + lines.push("Reinstall with scripts enabled:"); + for (const command of genericInstallCommands) { + lines.push(` ${command}`); + } + } else { + lines.push("Reinstall with scripts enabled for:"); + lines.push(` ${trustPackages}`); + } + lines.push(`Or run: chmod +x "${binPath}"`); + return lines.join("\n"); +} diff --git a/sdks/cli-shared/tsconfig.json b/sdks/cli-shared/tsconfig.json new file mode 100644 index 0000000..a274d38 --- /dev/null +++ b/sdks/cli-shared/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/sdks/cli-shared/tsup.config.ts b/sdks/cli-shared/tsup.config.ts new file mode 100644 index 0000000..644948a --- /dev/null +++ b/sdks/cli-shared/tsup.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs"], + dts: true, + clean: true, + sourcemap: true, +}); diff --git a/sdks/cli/bin/sandbox-agent b/sdks/cli/bin/sandbox-agent index 9374244..93cac38 100755 --- a/sdks/cli/bin/sandbox-agent +++ b/sdks/cli/bin/sandbox-agent @@ -1,7 +1,40 @@ #!/usr/bin/env node const { execFileSync } = require("child_process"); +const { + formatNonExecutableBinaryMessage, + isPermissionError, +} = require("@sandbox-agent/cli-shared"); +const fs = require("fs"); const path = require("path"); +const TRUST_PACKAGES = + "@sandbox-agent/cli-linux-x64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64"; + +function printExecutableHint(binPath) { + console.error( + formatNonExecutableBinaryMessage({ + binPath, + trustPackages: TRUST_PACKAGES, + bunInstallBlocks: [ + { + label: "Project install", + commands: [ + `bun pm trust ${TRUST_PACKAGES}`, + "bun add @sandbox-agent/cli", + ], + }, + { + label: "Global install", + commands: [ + `bun pm -g trust ${TRUST_PACKAGES}`, + "bun add -g @sandbox-agent/cli", + ], + }, + ], + }), + ); +} + const PLATFORMS = { "darwin-arm64": "@sandbox-agent/cli-darwin-arm64", "darwin-x64": "@sandbox-agent/cli-darwin-x64", @@ -19,7 +52,30 @@ if (!pkg) { try { const pkgPath = require.resolve(`${pkg}/package.json`); const bin = process.platform === "win32" ? "sandbox-agent.exe" : "sandbox-agent"; - execFileSync(path.join(path.dirname(pkgPath), "bin", bin), process.argv.slice(2), { stdio: "inherit" }); + const binPath = path.join(path.dirname(pkgPath), "bin", bin); + if (process.platform !== "win32") { + try { + fs.accessSync(binPath, fs.constants.X_OK); + } catch (error) { + try { + fs.chmodSync(binPath, 0o755); + } catch (chmodError) { + if (isPermissionError(chmodError)) { + printExecutableHint(binPath); + } + console.error(`Failed to make ${binPath} executable.`); + throw chmodError; + } + } + } + try { + execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" }); + } catch (execError) { + if (isPermissionError(execError)) { + printExecutableHint(binPath); + } + throw execError; + } } catch (e) { if (e.status !== undefined) process.exit(e.status); throw e; diff --git a/sdks/cli/package.json b/sdks/cli/package.json index 6001f31..79faa85 100644 --- a/sdks/cli/package.json +++ b/sdks/cli/package.json @@ -13,6 +13,9 @@ "scripts": { "test": "vitest run" }, + "dependencies": { + "@sandbox-agent/cli-shared": "workspace:*" + }, "devDependencies": { "vitest": "^3.0.0" }, diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index b6cde13..2076c42 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -16,6 +16,9 @@ "import": "./dist/index.js" } }, + "dependencies": { + "@sandbox-agent/cli-shared": "workspace:*" + }, "files": [ "dist" ], diff --git a/sdks/typescript/src/spawn.ts b/sdks/typescript/src/spawn.ts index 5a48437..c879786 100644 --- a/sdks/typescript/src/spawn.ts +++ b/sdks/typescript/src/spawn.ts @@ -1,5 +1,9 @@ import type { ChildProcess } from "node:child_process"; import type { AddressInfo } from "node:net"; +import { + formatNonExecutableBinaryMessage, + isPermissionError, +} from "@sandbox-agent/cli-shared"; export type SandboxAgentSpawnLogMode = "inherit" | "pipe" | "silent"; @@ -28,6 +32,9 @@ const PLATFORM_PACKAGES: Record = { "win32-x64": "@sandbox-agent/cli-win32-x64", }; +const TRUST_PACKAGES = + "@sandbox-agent/cli-linux-x64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64"; + export function isNodeRuntime(): boolean { return typeof process !== "undefined" && !!process.versions?.node; } @@ -66,6 +73,31 @@ export async function spawnSandboxAgent( throw new Error("sandbox-agent binary not found. Install @sandbox-agent/cli or set SANDBOX_AGENT_BIN."); } + if (process.platform !== "win32") { + try { + fs.accessSync(binaryPath, fs.constants.X_OK); + } catch (error) { + if (isPermissionError(error)) { + throw new Error( + formatNonExecutableBinaryMessage({ + binPath: binaryPath, + trustPackages: TRUST_PACKAGES, + bunInstallBlocks: [ + { + label: "Project install", + commands: [ + `bun pm trust ${TRUST_PACKAGES}`, + "bun add sandbox-agent", + ], + }, + ], + }), + ); + } + throw error; + } + } + const stdio = logMode === "inherit" ? "inherit" : logMode === "silent" ? "ignore" : "pipe"; const args = ["server", "--host", bindHost, "--port", String(port), "--token", token]; const child = spawn(binaryPath, args, {