From 02bb992b119d07ece28705b2ae7176d496c45562 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Mon, 2 Feb 2026 21:12:41 -0800 Subject: [PATCH] fix: fix bun install bug (#62) * fix: fix bun install bug * refactor: consolidate executable check into assertExecutable helper - Add assertExecutable() to cli-shared that checks and attempts chmod - Simplify CLI and SDK spawn code to use the shared helper - Fix cli-shared package.json exports (.js not .mjs) - Add global install instructions to SDK error message * chore(release): update version to 0.1.6-rc.1 * fix: add cli-shared package to Dockerfiles * chore(release): update version to 0.1.6-rc.1 * fix: add cli-shared publishing to release workflow * chore(release): update version to 0.1.6-rc.1 * fix: handle already-exists error during crate publish * chore(release): update version to 0.1.6-rc.1 --- Cargo.toml | 14 +-- README.md | 22 ++++- docker/release/linux-x86_64.Dockerfile | 5 +- docker/release/macos-aarch64.Dockerfile | 5 +- docker/release/macos-x86_64.Dockerfile | 5 +- docker/release/windows.Dockerfile | 5 +- docker/runtime/Dockerfile | 5 +- docs/ai/skill.mdx | 15 ++- docs/deploy/local.mdx | 17 +++- docs/openapi.json | 2 +- docs/quickstart.mdx | 63 +++++++++++-- docs/sdks/typescript.mdx | 17 +++- justfile | 4 + package.json | 3 +- pnpm-lock.yaml | 20 ++++ scripts/release/main.ts | 9 +- scripts/release/sdk.ts | 52 +++++++++- scripts/release/update_version.ts | 5 + sdks/cli-shared/package.json | 30 ++++++ sdks/cli-shared/src/index.ts | 99 ++++++++++++++++++++ sdks/cli-shared/tsconfig.json | 15 +++ sdks/cli-shared/tsup.config.ts | 9 ++ sdks/cli/bin/sandbox-agent | 40 +++++++- sdks/cli/package.json | 5 +- sdks/cli/platforms/darwin-arm64/package.json | 2 +- sdks/cli/platforms/darwin-x64/package.json | 2 +- sdks/cli/platforms/linux-x64/package.json | 2 +- sdks/cli/platforms/win32-x64/package.json | 2 +- sdks/typescript/package.json | 5 +- sdks/typescript/src/spawn.ts | 32 +++++++ 30 files changed, 467 insertions(+), 44 deletions(-) create mode 100644 sdks/cli-shared/package.json create mode 100644 sdks/cli-shared/src/index.ts create mode 100644 sdks/cli-shared/tsconfig.json create mode 100644 sdks/cli-shared/tsup.config.ts diff --git a/Cargo.toml b/Cargo.toml index a55bdf4..a30a351 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["server/packages/*"] [workspace.package] -version = "0.1.4-rc.7" +version = "0.1.6-rc.1" edition = "2021" authors = [ "Rivet Gaming, LLC " ] license = "Apache-2.0" @@ -12,12 +12,12 @@ description = "Universal API for automatic coding agents in sandboxes. Supprots [workspace.dependencies] # Internal crates -sandbox-agent = { version = "0.1.4-rc.7", path = "server/packages/sandbox-agent" } -sandbox-agent-error = { version = "0.1.4-rc.7", path = "server/packages/error" } -sandbox-agent-agent-management = { version = "0.1.4-rc.7", path = "server/packages/agent-management" } -sandbox-agent-agent-credentials = { version = "0.1.4-rc.7", path = "server/packages/agent-credentials" } -sandbox-agent-universal-agent-schema = { version = "0.1.4-rc.7", path = "server/packages/universal-agent-schema" } -sandbox-agent-extracted-agent-schemas = { version = "0.1.4-rc.7", path = "server/packages/extracted-agent-schemas" } +sandbox-agent = { version = "0.1.6-rc.1", path = "server/packages/sandbox-agent" } +sandbox-agent-error = { version = "0.1.6-rc.1", path = "server/packages/error" } +sandbox-agent-agent-management = { version = "0.1.6-rc.1", path = "server/packages/agent-management" } +sandbox-agent-agent-credentials = { version = "0.1.6-rc.1", path = "server/packages/agent-credentials" } +sandbox-agent-universal-agent-schema = { version = "0.1.6-rc.1", path = "server/packages/universal-agent-schema" } +sandbox-agent-extracted-agent-schemas = { version = "0.1.6-rc.1", path = "server/packages/extracted-agent-schemas" } # Serialization serde = { version = "1.0", features = ["derive"] } diff --git a/README.md b/README.md index c75ff03..f5d7b77 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/docker/release/linux-x86_64.Dockerfile b/docker/release/linux-x86_64.Dockerfile index d707d20..89a8a30 100644 --- a/docker/release/linux-x86_64.Dockerfile +++ b/docker/release/linux-x86_64.Dockerfile @@ -8,6 +8,7 @@ RUN npm install -g pnpm # Copy package files for workspaces COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ +COPY sdks/cli-shared/package.json ./sdks/cli-shared/ COPY sdks/typescript/package.json ./sdks/typescript/ # Install dependencies @@ -15,9 +16,11 @@ RUN pnpm install --filter @sandbox-agent/inspector... # Copy SDK source (with pre-generated types from docs/openapi.json) COPY docs/openapi.json ./docs/ +COPY sdks/cli-shared ./sdks/cli-shared COPY sdks/typescript ./sdks/typescript -# Build SDK (just tsup, skip generate since types are pre-generated) +# Build cli-shared and SDK (just tsup, skip generate since types are pre-generated) +RUN cd sdks/cli-shared && pnpm exec tsup RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup # Copy inspector source and build diff --git a/docker/release/macos-aarch64.Dockerfile b/docker/release/macos-aarch64.Dockerfile index dcc9466..dbb173a 100644 --- a/docker/release/macos-aarch64.Dockerfile +++ b/docker/release/macos-aarch64.Dockerfile @@ -8,6 +8,7 @@ RUN npm install -g pnpm # Copy package files for workspaces COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ +COPY sdks/cli-shared/package.json ./sdks/cli-shared/ COPY sdks/typescript/package.json ./sdks/typescript/ # Install dependencies @@ -15,9 +16,11 @@ RUN pnpm install --filter @sandbox-agent/inspector... # Copy SDK source (with pre-generated types from docs/openapi.json) COPY docs/openapi.json ./docs/ +COPY sdks/cli-shared ./sdks/cli-shared COPY sdks/typescript ./sdks/typescript -# Build SDK (just tsup, skip generate since types are pre-generated) +# Build cli-shared and SDK (just tsup, skip generate since types are pre-generated) +RUN cd sdks/cli-shared && pnpm exec tsup RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup # Copy inspector source and build diff --git a/docker/release/macos-x86_64.Dockerfile b/docker/release/macos-x86_64.Dockerfile index 62d7c90..98d3a31 100644 --- a/docker/release/macos-x86_64.Dockerfile +++ b/docker/release/macos-x86_64.Dockerfile @@ -8,6 +8,7 @@ RUN npm install -g pnpm # Copy package files for workspaces COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ +COPY sdks/cli-shared/package.json ./sdks/cli-shared/ COPY sdks/typescript/package.json ./sdks/typescript/ # Install dependencies @@ -15,9 +16,11 @@ RUN pnpm install --filter @sandbox-agent/inspector... # Copy SDK source (with pre-generated types from docs/openapi.json) COPY docs/openapi.json ./docs/ +COPY sdks/cli-shared ./sdks/cli-shared COPY sdks/typescript ./sdks/typescript -# Build SDK (just tsup, skip generate since types are pre-generated) +# Build cli-shared and SDK (just tsup, skip generate since types are pre-generated) +RUN cd sdks/cli-shared && pnpm exec tsup RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup # Copy inspector source and build diff --git a/docker/release/windows.Dockerfile b/docker/release/windows.Dockerfile index 38fb3c5..ca7eb16 100644 --- a/docker/release/windows.Dockerfile +++ b/docker/release/windows.Dockerfile @@ -8,6 +8,7 @@ RUN npm install -g pnpm # Copy package files for workspaces COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ +COPY sdks/cli-shared/package.json ./sdks/cli-shared/ COPY sdks/typescript/package.json ./sdks/typescript/ # Install dependencies @@ -15,9 +16,11 @@ RUN pnpm install --filter @sandbox-agent/inspector... # Copy SDK source (with pre-generated types from docs/openapi.json) COPY docs/openapi.json ./docs/ +COPY sdks/cli-shared ./sdks/cli-shared COPY sdks/typescript ./sdks/typescript -# Build SDK (just tsup, skip generate since types are pre-generated) +# Build cli-shared and SDK (just tsup, skip generate since types are pre-generated) +RUN cd sdks/cli-shared && pnpm exec tsup RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup # Copy inspector source and build diff --git a/docker/runtime/Dockerfile b/docker/runtime/Dockerfile index 520aa79..013b7fd 100644 --- a/docker/runtime/Dockerfile +++ b/docker/runtime/Dockerfile @@ -10,6 +10,7 @@ RUN npm install -g pnpm # Copy package files for workspaces COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ +COPY sdks/cli-shared/package.json ./sdks/cli-shared/ COPY sdks/typescript/package.json ./sdks/typescript/ # Install dependencies @@ -17,9 +18,11 @@ RUN pnpm install --filter @sandbox-agent/inspector... # Copy SDK source (with pre-generated types from docs/openapi.json) COPY docs/openapi.json ./docs/ +COPY sdks/cli-shared ./sdks/cli-shared COPY sdks/typescript ./sdks/typescript -# Build SDK (just tsup, skip generate since types are pre-generated) +# Build cli-shared and SDK (just tsup, skip generate since types are pre-generated) +RUN cd sdks/cli-shared && pnpm exec tsup RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup # Copy inspector source and build 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/openapi.json b/docs/openapi.json index a003943..76c76f0 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -10,7 +10,7 @@ "license": { "name": "Apache-2.0" }, - "version": "0.1.4-rc.7" + "version": "0.1.6-rc.1" }, "servers": [ { 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 cb71157..bf338a7 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/scripts/release/main.ts b/scripts/release/main.ts index bf30ce0..66e0143 100755 --- a/scripts/release/main.ts +++ b/scripts/release/main.ts @@ -13,7 +13,7 @@ import { createGitHubRelease, validateGit, } from "./git"; -import { publishCrates, publishNpmCli, publishNpmSdk } from "./sdk"; +import { publishCrates, publishNpmCli, publishNpmCliShared, publishNpmSdk } from "./sdk"; import { updateVersion } from "./update_version"; import { assert, assertEquals, fetchGitRef, versionOrCommitToRef } from "./utils"; @@ -281,6 +281,7 @@ const STEPS = [ "run-ci-checks", "build-js-artifacts", "publish-crates", + "publish-npm-cli-shared", "publish-npm-sdk", "publish-npm-cli", "tag-docker", @@ -322,6 +323,7 @@ const PHASE_MAP: Record = { "complete-ci": [ "update-version", "publish-crates", + "publish-npm-cli-shared", "publish-npm-sdk", "publish-npm-cli", "tag-docker", @@ -595,6 +597,11 @@ async function main() { await publishCrates(releaseOpts); } + if (shouldRunStep("publish-npm-cli-shared")) { + console.log("==> Publishing NPM CLI Shared"); + await publishNpmCliShared(releaseOpts); + } + if (shouldRunStep("publish-npm-sdk")) { console.log("==> Publishing NPM SDK"); await publishNpmSdk(releaseOpts); diff --git a/scripts/release/sdk.ts b/scripts/release/sdk.ts index f5d489a..42b181d 100644 --- a/scripts/release/sdk.ts +++ b/scripts/release/sdk.ts @@ -116,12 +116,21 @@ export async function publishCrates(opts: ReleaseOpts) { try { await $({ - stdio: "inherit", + stdout: "pipe", + stderr: "pipe", cwd: cratePath, })`cargo publish --allow-dirty --no-verify`; console.log(`✅ Published ${crateName}@${opts.version}`); - } catch (err) { + } catch (err: any) { + // Check if error is because crate already exists (from a previous partial run) + if (err.stderr?.includes("already exists")) { + console.log( + `Version ${opts.version} of ${crateName} already exists on crates.io. Skipping...`, + ); + continue; + } console.error(`❌ Failed to publish ${crateName}`); + console.error(err.stderr || err.message); throw err; } @@ -133,6 +142,43 @@ export async function publishCrates(opts: ReleaseOpts) { console.log("✅ All crates published"); } +export async function publishNpmCliShared(opts: ReleaseOpts) { + const cliSharedPath = join(opts.root, "sdks/cli-shared"); + const packageJsonPath = join(cliSharedPath, "package.json"); + const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8")); + const name = packageJson.name; + + // Check if version already exists + const versionExists = await npmVersionExists(name, opts.version); + if (versionExists) { + console.log( + `Version ${opts.version} of ${name} already exists. Skipping...`, + ); + return; + } + + // Build cli-shared + console.log(`==> Building @sandbox-agent/cli-shared`); + await $({ + stdio: "inherit", + cwd: opts.root, + })`pnpm --filter @sandbox-agent/cli-shared build`; + + // Publish + console.log(`==> Publishing to NPM: ${name}@${opts.version}`); + + // Add --tag flag for release candidates + const isReleaseCandidate = opts.version.includes("-rc."); + const tag = isReleaseCandidate ? "rc" : "latest"; + + await $({ + stdio: "inherit", + cwd: cliSharedPath, + })`pnpm publish --access public --tag ${tag} --no-git-checks`; + + console.log(`✅ Published ${name}@${opts.version}`); +} + export async function publishNpmSdk(opts: ReleaseOpts) { const sdkPath = join(opts.root, "sdks/typescript"); const packageJsonPath = join(sdkPath, "package.json"); @@ -148,7 +194,7 @@ export async function publishNpmSdk(opts: ReleaseOpts) { return; } - // Build the SDK + // Build the SDK (cli-shared should already be built by publishNpmCliShared) console.log(`==> Building TypeScript SDK`); await $({ stdio: "inherit", diff --git a/scripts/release/update_version.ts b/scripts/release/update_version.ts index 90f9427..a1c0d1c 100644 --- a/scripts/release/update_version.ts +++ b/scripts/release/update_version.ts @@ -17,6 +17,11 @@ export async function updateVersion(opts: ReleaseOpts) { find: /\[workspace\.package\]\nversion = ".*"/, replace: `[workspace.package]\nversion = "${opts.version}"`, }, + { + path: "sdks/cli-shared/package.json", + find: /"version": ".*"/, + replace: `"version": "${opts.version}"`, + }, { path: "sdks/typescript/package.json", find: /"version": ".*"/, diff --git a/sdks/cli-shared/package.json b/sdks/cli-shared/package.json new file mode 100644 index 0000000..d6d28ab --- /dev/null +++ b/sdks/cli-shared/package.json @@ -0,0 +1,30 @@ +{ + "name": "@sandbox-agent/cli-shared", + "version": "0.1.6-rc.1", + "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.js", + "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..da2773f --- /dev/null +++ b/sdks/cli-shared/src/index.ts @@ -0,0 +1,99 @@ +export type InstallCommandBlock = { + label: string; + commands: string[]; +}; + +export type NonExecutableBinaryMessageOptions = { + binPath: string; + trustPackages: string; + bunInstallBlocks: InstallCommandBlock[]; + genericInstallCommands?: string[]; +}; + +export type FsSubset = { + accessSync: (path: string, mode?: number) => void; + chmodSync: (path: string, mode: number) => void; + constants: { X_OK: number }; +}; + +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"]); + +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); +} + +/** + * Checks if a binary is executable and attempts to make it executable if not. + * Returns true if the binary is (or was made) executable, false if it couldn't + * be made executable due to permission errors. Throws for other errors. + * + * Requires fs to be passed in to avoid static imports that break browser builds. + */ +export function assertExecutable(binPath: string, fs: FsSubset): boolean { + if (process.platform === "win32") { + return true; + } + + try { + fs.accessSync(binPath, fs.constants.X_OK); + return true; + } catch { + // Not executable, try to fix + } + + try { + fs.chmodSync(binPath, 0o755); + return true; + } catch (error) { + if (isPermissionError(error)) { + return false; + } + throw error; + } +} + +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..66c1aac 100755 --- a/sdks/cli/bin/sandbox-agent +++ b/sdks/cli/bin/sandbox-agent @@ -1,7 +1,38 @@ #!/usr/bin/env node const { execFileSync } = require("child_process"); +const { + assertExecutable, + formatNonExecutableBinaryMessage, +} = 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 formatHint(binPath) { + return 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 +50,14 @@ 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 (!assertExecutable(binPath, fs)) { + console.error(formatHint(binPath)); + process.exit(1); + } + + execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" }); } 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..968157b 100644 --- a/sdks/cli/package.json +++ b/sdks/cli/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-agent/cli", - "version": "0.1.4-rc.7", + "version": "0.1.6-rc.1", "description": "CLI for sandbox-agent - run AI coding agents in sandboxes", "license": "Apache-2.0", "repository": { @@ -13,6 +13,9 @@ "scripts": { "test": "vitest run" }, + "dependencies": { + "@sandbox-agent/cli-shared": "workspace:*" + }, "devDependencies": { "vitest": "^3.0.0" }, diff --git a/sdks/cli/platforms/darwin-arm64/package.json b/sdks/cli/platforms/darwin-arm64/package.json index 242e689..cd66e2b 100644 --- a/sdks/cli/platforms/darwin-arm64/package.json +++ b/sdks/cli/platforms/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-agent/cli-darwin-arm64", - "version": "0.1.4-rc.7", + "version": "0.1.6-rc.1", "description": "sandbox-agent CLI binary for macOS ARM64", "license": "Apache-2.0", "repository": { diff --git a/sdks/cli/platforms/darwin-x64/package.json b/sdks/cli/platforms/darwin-x64/package.json index c585259..02d8f00 100644 --- a/sdks/cli/platforms/darwin-x64/package.json +++ b/sdks/cli/platforms/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-agent/cli-darwin-x64", - "version": "0.1.4-rc.7", + "version": "0.1.6-rc.1", "description": "sandbox-agent CLI binary for macOS x64", "license": "Apache-2.0", "repository": { diff --git a/sdks/cli/platforms/linux-x64/package.json b/sdks/cli/platforms/linux-x64/package.json index c6f15b3..b07eb77 100644 --- a/sdks/cli/platforms/linux-x64/package.json +++ b/sdks/cli/platforms/linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-agent/cli-linux-x64", - "version": "0.1.4-rc.7", + "version": "0.1.6-rc.1", "description": "sandbox-agent CLI binary for Linux x64", "license": "Apache-2.0", "repository": { diff --git a/sdks/cli/platforms/win32-x64/package.json b/sdks/cli/platforms/win32-x64/package.json index eb2b47d..6dbb302 100644 --- a/sdks/cli/platforms/win32-x64/package.json +++ b/sdks/cli/platforms/win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-agent/cli-win32-x64", - "version": "0.1.4-rc.7", + "version": "0.1.6-rc.1", "description": "sandbox-agent CLI binary for Windows x64", "license": "Apache-2.0", "repository": { diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index b6cde13..29bb10e 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -1,6 +1,6 @@ { "name": "sandbox-agent", - "version": "0.1.4-rc.7", + "version": "0.1.6-rc.1", "description": "Universal API for automatic coding agents in sandboxes. Supprots Claude Code, Codex, OpenCode, and Amp.", "license": "Apache-2.0", "repository": { @@ -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..6323e08 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 { + assertExecutable, + formatNonExecutableBinaryMessage, +} 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 (!assertExecutable(binaryPath, fs)) { + throw new Error( + formatNonExecutableBinaryMessage({ + binPath: binaryPath, + trustPackages: TRUST_PACKAGES, + bunInstallBlocks: [ + { + label: "Project install", + commands: [ + `bun pm trust ${TRUST_PACKAGES}`, + "bun add sandbox-agent", + ], + }, + { + label: "Global install", + commands: [ + `bun pm -g trust ${TRUST_PACKAGES}`, + "bun add -g sandbox-agent", + ], + }, + ], + }), + ); + } + const stdio = logMode === "inherit" ? "inherit" : logMode === "silent" ? "ignore" : "pipe"; const args = ["server", "--host", bindHost, "--port", String(port), "--token", token]; const child = spawn(binaryPath, args, {