From 42f5bea9db4161295ec2826c375f95ae5a9040dc Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: Mon, 8 Jun 2026 01:05:30 +0530 Subject: [PATCH 1/6] Add npm trusted publishing, staged publishing, and package READMEs - Switch release workflow to OIDC trusted publishing (no NPM_TOKEN secret) - Replace direct npm publish with npm stage publish so packages need explicit approval before going live - Remove publish.yml (replaced by npm stage in release.yml) and stage.yml - Drop tarballs and vmm binary from GitHub release assets (fetched from npm) - Add README for each workspace package Requires each package to be configured with this workflow as a trusted publisher on npmjs.com before the release workflow will succeed. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish.yml | 55 ------------------- .github/workflows/release.yml | 25 ++++++--- packages/cli/README.md | 99 +++++++++++++++++++++++++++++++++++ packages/ec2/README.md | 49 +++++++++++++++++ packages/providers/README.md | 46 ++++++++++++++++ packages/qemu/README.md | 45 ++++++++++++++++ packages/vmm/README.md | 64 ++++++++++++++++++++++ 7 files changed, 322 insertions(+), 61 deletions(-) delete mode 100644 .github/workflows/publish.yml create mode 100644 packages/cli/README.md create mode 100644 packages/ec2/README.md create mode 100644 packages/providers/README.md create mode 100644 packages/qemu/README.md create mode 100644 packages/vmm/README.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index a3c8bdf..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Publish - -on: - release: - types: [released] - workflow_dispatch: - inputs: - tag: - description: "Release tag to publish (e.g. v0.1.0)" - required: true - -permissions: - contents: read - -jobs: - publish: - name: Publish to npm - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write # for npm provenance - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ github.event.release.tag_name || inputs.tag }} - persist-credentials: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: 22 - package-manager-cache: false - registry-url: https://registry.npmjs.org - - - name: Download release tarballs - env: - GH_TOKEN: ${{ github.token }} - RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }} - run: | - mkdir -p dist - gh release download "$RELEASE_TAG" \ - --pattern "*.tgz" \ - --dir dist - - - name: Publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - for tarball in dist/*.tgz; do - pkg=$(tar -xOf "./$tarball" package/package.json | python3 -c 'import sys,json; p=json.load(sys.stdin); print(p["name"] + "@" + p["version"])') - if npm view "$pkg" version > /dev/null 2>&1; then - echo "Skipping $pkg (already published)" - else - npm publish "./$tarball" --access public --provenance - fi - done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7255343..71ed531 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,12 +8,18 @@ on: permissions: contents: read +# Prerequisite: each npm package must have this workflow configured as a +# trusted publisher on npmjs.com (package → Settings → Trusted Publishers). +# Packages are submitted to npm staging here; run `npm stage approve ` +# with 2FA to make them live. + jobs: release: - name: Build & Pre-release + name: Build, Stage & Release runs-on: macos-latest permissions: contents: write + id-token: write # for OIDC trusted publishing + provenance steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: @@ -24,6 +30,10 @@ jobs: with: node-version: 22 package-manager-cache: false + registry-url: https://registry.npmjs.org + + - name: Upgrade npm (staged publishing requires npm ≥ 11.15.0, Node ≥ 22.14.0) + run: npm install -g npm@latest - run: npm ci @@ -57,16 +67,19 @@ jobs: - name: Pack npm tarballs run: npm pack --workspaces - - name: Create pre-release + - name: Stage to npm + run: | + for tarball in ./*.tgz; do + npm stage publish "$tarball" --access public --provenance + done + + - name: Create GitHub release env: GH_TOKEN: ${{ github.token }} run: | gh release create "$GITHUB_REF_NAME" \ - --prerelease \ --title "$GITHUB_REF_NAME" \ - --generate-notes \ - packages/vmm/dist/vmm \ - ./*.tgz + --generate-notes - name: Cleanup keychain if: always() diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..fea8b7e --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,99 @@ +# sandboxctl + +A CLI tool that spins up a Linux sandbox VM on your machine for local development. Powered by QEMU and Ubuntu cloud images — no Docker, no VirtualBox, no cloud account required. + +## Requirements + +- **macOS or Linux** (Windows support is experimental) +- **QEMU** — `brew install qemu` (macOS) / `sudo apt install qemu-system qemu-utils` (Linux) +- **Node.js** 18+ + +## Quickstart + +```sh +npx sandboxctl +``` + +Launches the setup wizard: checks prerequisites, configures `sandbox.json`, and optionally boots the VM. + +Or install globally: + +```sh +npm install -g sandboxctl +sandboxctl # setup wizard +sandboxctl ssh # shell inside the VM +``` + +## Commands + +| Command | Description | +|---|---| +| `sandboxctl` | Interactive setup wizard | +| `sandboxctl init` | Configure `sandbox.json` | +| `sandboxctl start` | Build (if needed) and boot the VM | +| `sandboxctl stop` | Gracefully shut down the VM | +| `sandboxctl destroy` | Delete the VM and all associated files | +| `sandboxctl status` | Show name, status, SSH port, and uptime | +| `sandboxctl ssh` | Open an interactive SSH session | +| `sandboxctl send` | Sync project files host → VM | +| `sandboxctl receive` | Sync files VM → host | +| `sandboxctl forward [port]` | Forward a port (`` or `:`) | +| `sandboxctl doctor` | Check required dependencies | + +## Configuration — `sandbox.json` + +```json +{ + "ubuntu": "24.04", + "vm": { + "cpus": 4, + "memory": "4G", + "disk": "20G" + }, + "packages": { + "nodejs": { "enabled": true, "version": "22" }, + "python": { "enabled": true } + }, + "send": { + "remotePath": "/home/ubuntu/my-project" + }, + "ports": [{ "host": 3000, "guest": 3000, "protocol": "tcp" }] +} +``` + +### Supported packages + +| Package | Versioned | +|---|---| +| Node.js | yes | +| Bun | yes | +| Python 3 | no | +| Java (OpenJDK) | yes | +| Go | yes | +| Ruby | no | +| PHP | no | +| Swift | yes | + +## Providers + +sandboxctl supports multiple VM backends via the `provider` field in `sandbox.json`: + +| Provider | Description | Platform | +|---|---|---| +| `local` (default) | QEMU — works everywhere | macOS, Linux | +| `vmm` | Apple Virtualization framework | macOS only | +| `ec2` | AWS EC2 | any | + +## Agent Skill + +sandboxctl ships an agent skill for Claude Code, Cursor, Codex, and other AI coding assistants: + +```sh +npx skills add inputforge/sandboxctl +``` + +Ask your assistant: _"create a sandbox for this project"_ and it will run the full setup wizard inline. + +## License + +MIT diff --git a/packages/ec2/README.md b/packages/ec2/README.md new file mode 100644 index 0000000..4d27fd8 --- /dev/null +++ b/packages/ec2/README.md @@ -0,0 +1,49 @@ +# @inputforge/sandboxctl-ec2 + +sandboxctl VM provider backed by AWS EC2. + +## Overview + +This package implements the `VmProvider` interface using AWS EC2. It provisions an Ubuntu EC2 instance, configures SSH access, and runs the same cloud-init provisioning as the local QEMU provider — so the sandbox behaves identically regardless of where it runs. + +## Requirements + +- AWS credentials configured (`~/.aws/credentials`, environment variables, or IAM role) +- Permissions to create/terminate EC2 instances, manage key pairs, and describe SSM parameters + +## Configuration + +Set `provider: "ec2"` in `sandbox.json` and optionally tune EC2-specific settings: + +```json +{ + "provider": "ec2", + "ec2": { + "region": "us-east-1", + "instanceType": "t4g.medium", + "arch": "arm64", + "sshCidr": "0.0.0.0/0" + } +} +``` + +| Field | Default | Description | +|---|---|---| +| `region` | `AWS_DEFAULT_REGION` / `us-east-1` | AWS region | +| `instanceType` | `t4g.medium` | EC2 instance type | +| `arch` | `arm64` | AMI architecture (`arm64` or `amd64`) | +| `sshCidr` | `0.0.0.0/0` | CIDR allowed for SSH ingress | + +Ubuntu AMI IDs are resolved automatically via SSM Parameter Store (`/aws/service/canonical/...`). + +## Usage + +```ts +import { createEc2Provider } from "@inputforge/sandboxctl-ec2"; + +const provider = createEc2Provider(); +``` + +## License + +MIT diff --git a/packages/providers/README.md b/packages/providers/README.md new file mode 100644 index 0000000..df56f10 --- /dev/null +++ b/packages/providers/README.md @@ -0,0 +1,46 @@ +# @inputforge/sandboxctl-providers + +Shared TypeScript types and interfaces for sandboxctl VM providers. + +## Overview + +This package defines the contracts that all sandboxctl VM provider implementations must satisfy. It contains no runtime code — only type definitions. + +## Interfaces + +### `VmProvider` + +The core interface every provider must implement: + +| Method | Description | +|---|---| +| `isSupported()` | Returns `false` if the provider cannot run on this host (e.g. vmm on Linux) | +| `checkPrereqs()` | Throws if required binaries are missing | +| `reportPrereqs()` | Returns structured prerequisite check results | +| `isInitialized(name)` | Fast synchronous check — filesystem existence, no I/O | +| `isRunning(name)` | Async liveness probe — PID check, API call, etc. | +| `start(config, name, snapshot, reporter)` | Full VM lifecycle: provision + boot → returns SSH endpoint | +| `stop(name, reporter)` | Gracefully shut down the VM | +| `destroy(name, reporter)` | Delete VM and all associated files | + +### `SandboxConfig` + +Structured representation of `sandbox.json` — passed to `start()`. + +### `ProviderReporter` + +Progress reporting interface passed into provider methods for spinner, progress bar, step, and log output. + +## Usage + +```ts +import type { VmProvider, SandboxConfig, ProviderReporter } from "@inputforge/sandboxctl-providers"; + +class MyProvider implements VmProvider { + // implement interface +} +``` + +## License + +MIT diff --git a/packages/qemu/README.md b/packages/qemu/README.md new file mode 100644 index 0000000..800c777 --- /dev/null +++ b/packages/qemu/README.md @@ -0,0 +1,45 @@ +# @inputforge/sandboxctl-qemu + +sandboxctl VM provider backed by QEMU. Works on macOS and Linux. + +## Overview + +This package implements the `VmProvider` interface using QEMU with Ubuntu cloud images. It handles image download, QCOW2 overlay disk creation, cloud-init seed generation, and SSH-based provisioning. + +## Requirements + +- QEMU: `brew install qemu` (macOS) or `sudo apt install qemu-system qemu-utils` (Linux/Debian) +- Node.js 18+ + +## Usage + +This package is loaded automatically by the `sandboxctl` CLI when `provider` is set to `"local"` (the default) or unset. You do not need to import it directly unless building a custom integration. + +```ts +import { createQemuProvider } from "@inputforge/sandboxctl-qemu"; + +const provider = createQemuProvider(); +``` + +### Platform config + +```ts +import { getPlatformConfig } from "@inputforge/sandboxctl-qemu"; +import type { PlatformConfig } from "@inputforge/sandboxctl-qemu"; + +const config: PlatformConfig = getPlatformConfig(); +``` + +`PlatformConfig` exposes the QEMU binary names and default machine type for the current host architecture (arm64 / amd64). + +## How it works + +1. Downloads the Ubuntu cloud image (cached in `~/.local/share/sandboxctl/images/`) +2. Creates a QCOW2 overlay disk at the configured size +3. Builds a cloud-init seed ISO with your SSH public key and install script +4. Boots QEMU in the background with host-forwarded SSH and guest ports +5. Waits for SSH and cloud-init provisioning to complete + +## License + +MIT diff --git a/packages/vmm/README.md b/packages/vmm/README.md new file mode 100644 index 0000000..5b41ea0 --- /dev/null +++ b/packages/vmm/README.md @@ -0,0 +1,64 @@ +# @inputforge/sandboxctl-vmm + +sandboxctl VM provider backed by the `vmm` Swift binary using Apple's Virtualization framework. + +## Overview + +This package implements the `VmProvider` interface using a native macOS Swift binary (`vmm`) that wraps the Apple Virtualization framework. It provides hardware-accelerated Linux VMs on Apple Silicon and Intel Macs — faster boot times and lower overhead than QEMU. + +**macOS only.** The package declares `"os": ["darwin"]` and is a no-op on other platforms. + +## Requirements + +- macOS 13 (Ventura) or later +- Apple Silicon or Intel Mac +- The `vmm` binary is bundled in `dist/vmm` after building + +## Usage + +This package is loaded automatically by the `sandboxctl` CLI when `provider` is set to `"vmm"`. You do not need to import it directly unless building a custom integration. + +```ts +import { createVmmProvider } from "@inputforge/sandboxctl-vmm"; + +const provider = createVmmProvider(); +``` + +## Configuration + +Set `provider: "vmm"` in `sandbox.json` and optionally configure boot mode: + +```json +{ + "provider": "vmm", + "vmm": { + "boot": "efi" + } +} +``` + +| Field | Default | Description | +|---|---|---| +| `boot` | `"efi"` | Boot mode: `"efi"` (standard) or `"linux"` (direct kernel boot) | + +## How it works + +1. Downloads Ubuntu cloud image, kernel, and initrd (cached in `~/.local/share/sandboxctl/`) +2. Converts the QCOW2 image to a raw disk +3. Builds a cloud-init seed ISO +4. Spawns the `vmm` binary, which creates the VM via the Apple Virtualization framework +5. Waits for SSH and provisioning to complete + +## Building + +The `vmm` Swift binary is built as part of `npm run build` on macOS: + +```sh +npm run build +``` + +This runs `tsc` and then `make -C src-native sign` to compile and codesign the Swift binary. + +## License + +MIT From 7f9345c99d0fd16e3f57200c6f1443ce96d1b1c5 Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: Mon, 8 Jun 2026 08:45:30 +0530 Subject: [PATCH 2/6] Fix CodeRabbit review: pin npm/node versions, nullglob, correct docs - Pin node-version to 22.14.0 and npm to 11.15.0 for reproducible builds - Add shopt -s nullglob before tarball loop to handle empty glob - Fix vmm README: default boot mode is "linux" not "efi" - Fix ec2 README: createEc2Provider() requires (providerConfig, arch) args - Fix markdown formatting across all README files (ultracite fix) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/release.yml | 7 +++-- packages/cli/README.md | 54 +++++++++++++++++------------------ packages/ec2/README.md | 17 ++++++----- packages/providers/README.md | 26 ++++++++++------- packages/vmm/README.md | 6 ++-- 5 files changed, 59 insertions(+), 51 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71ed531..5276e65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,12 +28,12 @@ jobs: - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version: 22.14.0 package-manager-cache: false registry-url: https://registry.npmjs.org - - name: Upgrade npm (staged publishing requires npm ≥ 11.15.0, Node ≥ 22.14.0) - run: npm install -g npm@latest + - name: Upgrade npm (staged publishing requires npm ≥ 11.15.0) + run: npm install -g npm@11.15.0 - run: npm ci @@ -69,6 +69,7 @@ jobs: - name: Stage to npm run: | + shopt -s nullglob for tarball in ./*.tgz; do npm stage publish "$tarball" --access public --provenance done diff --git a/packages/cli/README.md b/packages/cli/README.md index fea8b7e..3ad82c2 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -26,19 +26,19 @@ sandboxctl ssh # shell inside the VM ## Commands -| Command | Description | -|---|---| -| `sandboxctl` | Interactive setup wizard | -| `sandboxctl init` | Configure `sandbox.json` | -| `sandboxctl start` | Build (if needed) and boot the VM | -| `sandboxctl stop` | Gracefully shut down the VM | -| `sandboxctl destroy` | Delete the VM and all associated files | -| `sandboxctl status` | Show name, status, SSH port, and uptime | -| `sandboxctl ssh` | Open an interactive SSH session | -| `sandboxctl send` | Sync project files host → VM | -| `sandboxctl receive` | Sync files VM → host | +| Command | Description | +| --------------------------- | ---------------------------------------------- | +| `sandboxctl` | Interactive setup wizard | +| `sandboxctl init` | Configure `sandbox.json` | +| `sandboxctl start` | Build (if needed) and boot the VM | +| `sandboxctl stop` | Gracefully shut down the VM | +| `sandboxctl destroy` | Delete the VM and all associated files | +| `sandboxctl status` | Show name, status, SSH port, and uptime | +| `sandboxctl ssh` | Open an interactive SSH session | +| `sandboxctl send` | Sync project files host → VM | +| `sandboxctl receive` | Sync files VM → host | | `sandboxctl forward [port]` | Forward a port (`` or `:`) | -| `sandboxctl doctor` | Check required dependencies | +| `sandboxctl doctor` | Check required dependencies | ## Configuration — `sandbox.json` @@ -63,26 +63,26 @@ sandboxctl ssh # shell inside the VM ### Supported packages -| Package | Versioned | -|---|---| -| Node.js | yes | -| Bun | yes | -| Python 3 | no | -| Java (OpenJDK) | yes | -| Go | yes | -| Ruby | no | -| PHP | no | -| Swift | yes | +| Package | Versioned | +| -------------- | --------- | +| Node.js | yes | +| Bun | yes | +| Python 3 | no | +| Java (OpenJDK) | yes | +| Go | yes | +| Ruby | no | +| PHP | no | +| Swift | yes | ## Providers sandboxctl supports multiple VM backends via the `provider` field in `sandbox.json`: -| Provider | Description | Platform | -|---|---|---| -| `local` (default) | QEMU — works everywhere | macOS, Linux | -| `vmm` | Apple Virtualization framework | macOS only | -| `ec2` | AWS EC2 | any | +| Provider | Description | Platform | +| ----------------- | ------------------------------ | ------------ | +| `local` (default) | QEMU — works everywhere | macOS, Linux | +| `vmm` | Apple Virtualization framework | macOS only | +| `ec2` | AWS EC2 | any | ## Agent Skill diff --git a/packages/ec2/README.md b/packages/ec2/README.md index 4d27fd8..90f6fdc 100644 --- a/packages/ec2/README.md +++ b/packages/ec2/README.md @@ -27,12 +27,12 @@ Set `provider: "ec2"` in `sandbox.json` and optionally tune EC2-specific setting } ``` -| Field | Default | Description | -|---|---|---| -| `region` | `AWS_DEFAULT_REGION` / `us-east-1` | AWS region | -| `instanceType` | `t4g.medium` | EC2 instance type | -| `arch` | `arm64` | AMI architecture (`arm64` or `amd64`) | -| `sshCidr` | `0.0.0.0/0` | CIDR allowed for SSH ingress | +| Field | Default | Description | +| -------------- | ---------------------------------- | ------------------------------------- | +| `region` | `AWS_DEFAULT_REGION` / `us-east-1` | AWS region | +| `instanceType` | `t4g.medium` | EC2 instance type | +| `arch` | `arm64` | AMI architecture (`arm64` or `amd64`) | +| `sshCidr` | `0.0.0.0/0` | CIDR allowed for SSH ingress | Ubuntu AMI IDs are resolved automatically via SSM Parameter Store (`/aws/service/canonical/...`). @@ -41,7 +41,10 @@ Ubuntu AMI IDs are resolved automatically via SSM Parameter Store (`/aws/service ```ts import { createEc2Provider } from "@inputforge/sandboxctl-ec2"; -const provider = createEc2Provider(); +const provider = createEc2Provider( + { region: "us-east-1", instanceType: "t4g.medium", sshCidr: "0.0.0.0/0" }, + "arm64" +); ``` ## License diff --git a/packages/providers/README.md b/packages/providers/README.md index df56f10..1559339 100644 --- a/packages/providers/README.md +++ b/packages/providers/README.md @@ -12,16 +12,16 @@ This package defines the contracts that all sandboxctl VM provider implementatio The core interface every provider must implement: -| Method | Description | -|---|---| -| `isSupported()` | Returns `false` if the provider cannot run on this host (e.g. vmm on Linux) | -| `checkPrereqs()` | Throws if required binaries are missing | -| `reportPrereqs()` | Returns structured prerequisite check results | -| `isInitialized(name)` | Fast synchronous check — filesystem existence, no I/O | -| `isRunning(name)` | Async liveness probe — PID check, API call, etc. | -| `start(config, name, snapshot, reporter)` | Full VM lifecycle: provision + boot → returns SSH endpoint | -| `stop(name, reporter)` | Gracefully shut down the VM | -| `destroy(name, reporter)` | Delete VM and all associated files | +| Method | Description | +| ----------------------------------------- | --------------------------------------------------------------------------- | +| `isSupported()` | Returns `false` if the provider cannot run on this host (e.g. vmm on Linux) | +| `checkPrereqs()` | Throws if required binaries are missing | +| `reportPrereqs()` | Returns structured prerequisite check results | +| `isInitialized(name)` | Fast synchronous check — filesystem existence, no I/O | +| `isRunning(name)` | Async liveness probe — PID check, API call, etc. | +| `start(config, name, snapshot, reporter)` | Full VM lifecycle: provision + boot → returns SSH endpoint | +| `stop(name, reporter)` | Gracefully shut down the VM | +| `destroy(name, reporter)` | Delete VM and all associated files | ### `SandboxConfig` @@ -34,7 +34,11 @@ Progress reporting interface passed into provider methods for spinner, progress ## Usage ```ts -import type { VmProvider, SandboxConfig, ProviderReporter } from "@inputforge/sandboxctl-providers"; +import type { + VmProvider, + SandboxConfig, + ProviderReporter, +} from "@inputforge/sandboxctl-providers"; class MyProvider implements VmProvider { // implement interface diff --git a/packages/vmm/README.md b/packages/vmm/README.md index 5b41ea0..c7055b8 100644 --- a/packages/vmm/README.md +++ b/packages/vmm/README.md @@ -37,9 +37,9 @@ Set `provider: "vmm"` in `sandbox.json` and optionally configure boot mode: } ``` -| Field | Default | Description | -|---|---|---| -| `boot` | `"efi"` | Boot mode: `"efi"` (standard) or `"linux"` (direct kernel boot) | +| Field | Default | Description | +| ------ | --------- | --------------------------------------------------------------- | +| `boot` | `"linux"` | Boot mode: `"linux"` (direct kernel boot) or `"efi"` (standard) | ## How it works From ff019494fa4690ee91682b980eaca3a5e9433e13 Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: Mon, 8 Jun 2026 08:52:40 +0530 Subject: [PATCH 3/6] Restructure CLI README to follow root README, drop development section Co-Authored-By: Claude Sonnet 4.6 --- packages/cli/README.md | 136 ++++++++++++++++++++++++++++++----------- 1 file changed, 99 insertions(+), 37 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 3ad82c2..6a8f1ec 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -5,18 +5,32 @@ A CLI tool that spins up a Linux sandbox VM on your machine for local developmen ## Requirements - **macOS or Linux** (Windows support is experimental) -- **QEMU** — `brew install qemu` (macOS) / `sudo apt install qemu-system qemu-utils` (Linux) +- **macOS or Linux**: QEMU — `brew install qemu` / `sudo apt install qemu-system qemu-utils` - **Node.js** 18+ +## Agent Skill + +sandboxctl ships an agent skill for Claude Code, Cursor, Codex, and other AI coding assistants. It analyzes your project and writes a `sandbox.json` for you — detecting runtimes, port requirements, and appropriate VM sizing automatically. + +Install it with: + +```sh +npx skills add inputforge/sandboxctl +``` + +Once installed, ask your assistant: _"create a sandbox for this project"_ and it will run the full setup wizard inline. + ## Quickstart +Run in any project directory: + ```sh npx sandboxctl ``` -Launches the setup wizard: checks prerequisites, configures `sandbox.json`, and optionally boots the VM. +This launches a setup wizard that checks prerequisites, walks you through configuration, and optionally boots the VM — all in one session. -Or install globally: +Or install globally for day-to-day use: ```sh npm install -g sandboxctl @@ -26,22 +40,55 @@ sandboxctl ssh # shell inside the VM ## Commands -| Command | Description | -| --------------------------- | ---------------------------------------------- | -| `sandboxctl` | Interactive setup wizard | -| `sandboxctl init` | Configure `sandbox.json` | -| `sandboxctl start` | Build (if needed) and boot the VM | -| `sandboxctl stop` | Gracefully shut down the VM | -| `sandboxctl destroy` | Delete the VM and all associated files | -| `sandboxctl status` | Show name, status, SSH port, and uptime | -| `sandboxctl ssh` | Open an interactive SSH session | -| `sandboxctl send` | Sync project files host → VM | -| `sandboxctl receive` | Sync files VM → host | -| `sandboxctl forward [port]` | Forward a port (`` or `:`) | -| `sandboxctl doctor` | Check required dependencies | +``` +sandboxctl + +Commands: + init Configure sandbox.json interactively + start Build (if needed) and boot the sandbox VM + stop Gracefully shut down the VM + destroy Delete the VM and all associated files + status Show sandbox name, status, SSH port, and uptime + ssh Open an interactive SSH session into the VM + send Sync project files from host → VM (uses rsync or tar) + receive Sync files from VM → host + forward [port] Forward a port: or : + doctor Check that required dependencies are installed +``` + +### No-argument wizard + +Running `sandboxctl` with no arguments starts the setup wizard: + +1. **Prerequisite check** — verifies the required backend (Lima on macOS, QEMU on Linux) is installed and prints the install command if not +2. **Configuration** — runs the `init` prompts; if `sandbox.json` already exists all fields are pre-populated with current values so you can press Enter to keep them +3. **Boot offer** — asks whether to start the VM immediately after saving config + +### `doctor` + +Checks that the required binaries are present for your platform and reports the install command for anything missing: + +```sh +sandboxctl doctor +``` + +### First `start` + +On first boot, the tool will: + +1. Download the Ubuntu cloud image (cached in `~/.local/share/sandboxctl/images/`) +2. Create a QCOW2 overlay disk +3. Build a cloud-init seed image with your SSH public key and install script +4. Boot the VM in the background +5. Stream the package install log until `==> Done.` +6. Rsync your project files into the VM + +Subsequent `start` calls skip provisioning and boot in seconds. ## Configuration — `sandbox.json` +Generated by `sandboxctl init` (or the wizard). You can also write it by hand: + ```json { "ubuntu": "24.04", @@ -52,7 +99,9 @@ sandboxctl ssh # shell inside the VM }, "packages": { "nodejs": { "enabled": true, "version": "22" }, - "python": { "enabled": true } + "bun": { "enabled": false }, + "python": { "enabled": true }, + "go": { "enabled": false } }, "send": { "remotePath": "/home/ubuntu/my-project" @@ -63,16 +112,39 @@ sandboxctl ssh # shell inside the VM ### Supported packages -| Package | Versioned | -| -------------- | --------- | -| Node.js | yes | -| Bun | yes | -| Python 3 | no | -| Java (OpenJDK) | yes | -| Go | yes | -| Ruby | no | -| PHP | no | -| Swift | yes | +| Package | Versioned | Default | +| -------------- | --------- | ------- | +| Node.js | yes | 22 | +| Bun | yes | 1.3.12 | +| Python 3 | no | — | +| Java (OpenJDK) | yes | 21 | +| Go | yes | 1.24.3 | +| Ruby | no | — | +| PHP | no | — | +| Swift | yes | 6.0.3 | + +### Port forwarding + +`ports` is an optional array of host↔guest port mappings. Useful for accessing a dev server running inside the VM. + +### Config change rules + +| Change | Action required | +| --------------------------- | ------------------- | +| `ubuntu` version | `destroy` + `start` | +| `packages` | `destroy` + `start` | +| `vm.disk` (grow only) | `stop` + `start` | +| `vm.cpus` / `vm.memory` | `stop` + `start` | +| `ports` / `send.remotePath` | `stop` + `start` | + +## File sync + +`send` pushes your current directory to the VM (respects `.gitignore`). It uses `rsync` when available and falls back to `tar` over SSH. + +```sh +sandboxctl send # host → VM +sandboxctl receive # VM → host +``` ## Providers @@ -84,16 +156,6 @@ sandboxctl supports multiple VM backends via the `provider` field in `sandbox.js | `vmm` | Apple Virtualization framework | macOS only | | `ec2` | AWS EC2 | any | -## Agent Skill - -sandboxctl ships an agent skill for Claude Code, Cursor, Codex, and other AI coding assistants: - -```sh -npx skills add inputforge/sandboxctl -``` - -Ask your assistant: _"create a sandbox for this project"_ and it will run the full setup wizard inline. - ## License MIT From 12e614a8c7d2e897336d3b70f1be442060b5486e Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: Mon, 8 Jun 2026 08:57:39 +0530 Subject: [PATCH 4/6] Update repository URLs to git+https format and configure public npm access for packages --- packages/cli/package.json | 3 +-- packages/ec2/package.json | 5 ++++- packages/providers/package.json | 5 ++++- packages/qemu/package.json | 5 ++++- packages/vmm/package.json | 5 ++++- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index e43caed..3864054 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -17,7 +17,7 @@ "author": "Input Forge ", "repository": { "type": "git", - "url": "https://github.com/inputforge/sandboxctl", + "url": "git+https://github.com/inputforge/sandboxctl.git", "directory": "packages/cli" }, "bin": { @@ -35,7 +35,6 @@ "@bomb.sh/args": "^0.3.1", "@clack/prompts": "^1.4.0", "@inputforge/sandboxctl-providers": "0.1.0", - "@types/node": "^22.0.0", "env-paths": "^4.0.0", "zod": "^4.4.3" }, diff --git a/packages/ec2/package.json b/packages/ec2/package.json index 73a48c7..f565d08 100644 --- a/packages/ec2/package.json +++ b/packages/ec2/package.json @@ -17,7 +17,7 @@ "author": "Input Forge ", "repository": { "type": "git", - "url": "https://github.com/inputforge/sandboxctl", + "url": "git+https://github.com/inputforge/sandboxctl.git", "directory": "packages/ec2" }, "files": [ @@ -27,6 +27,9 @@ "exports": { ".": "./dist/index.js" }, + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsc", "dev": "tsc --watch" diff --git a/packages/providers/package.json b/packages/providers/package.json index f01891c..e0e9230 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -16,7 +16,7 @@ "author": "Input Forge ", "repository": { "type": "git", - "url": "https://github.com/inputforge/sandboxctl", + "url": "git+https://github.com/inputforge/sandboxctl.git", "directory": "packages/providers" }, "files": [ @@ -26,6 +26,9 @@ "exports": { ".": "./dist/index.js" }, + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsc", "dev": "tsc --watch" diff --git a/packages/qemu/package.json b/packages/qemu/package.json index 0e9b0a9..d52a428 100644 --- a/packages/qemu/package.json +++ b/packages/qemu/package.json @@ -17,7 +17,7 @@ "author": "Input Forge ", "repository": { "type": "git", - "url": "https://github.com/inputforge/sandboxctl", + "url": "git+https://github.com/inputforge/sandboxctl.git", "directory": "packages/qemu" }, "files": [ @@ -27,6 +27,9 @@ "exports": { ".": "./dist/index.js" }, + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsc", "dev": "tsc --watch" diff --git a/packages/vmm/package.json b/packages/vmm/package.json index 6eb08fa..cb31bda 100644 --- a/packages/vmm/package.json +++ b/packages/vmm/package.json @@ -17,7 +17,7 @@ "author": "Input Forge ", "repository": { "type": "git", - "url": "https://github.com/inputforge/sandboxctl", + "url": "git+https://github.com/inputforge/sandboxctl.git", "directory": "packages/vmm" }, "files": [ @@ -30,6 +30,9 @@ "exports": { ".": "./dist/index.js" }, + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsc && if [ \"$(uname)\" = \"Darwin\" ]; then make -C src-native sign && cp src-native/.build/apple/Products/Release/vmm dist/vmm; fi", "dev": "tsc --watch" From 9de9a2a5b3ceb969499f629b989854815919e338 Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: Mon, 8 Jun 2026 09:14:29 +0530 Subject: [PATCH 5/6] Update notarize-and-promote for staged publishing; fix package.json metadata notarize-and-promote.sh: - Get vmm binary from staged npm tarball via `npm stage download` instead of GitHub release asset - Replace `gh release edit --prerelease=false` with `npm stage approve` for all staged packages matching the release version package.json (all packages): - Fix repository.url: add git+ prefix and .git suffix (npm convention) - Remove duplicate @types/node from cli dependencies (was in both deps and devDeps) - Add publishConfig: { access: "public" } to all scoped packages Co-Authored-By: Claude Sonnet 4.6 --- scripts/notarize-and-promote.sh | 44 +++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/scripts/notarize-and-promote.sh b/scripts/notarize-and-promote.sh index 291162c..e163f84 100755 --- a/scripts/notarize-and-promote.sh +++ b/scripts/notarize-and-promote.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -# Notarize the vmm binary from a pre-release and promote it to a full release. -# Promoting the release triggers the publish.yml workflow which publishes to npm. +# Notarize the vmm binary from the staged npm package and approve all staged packages. # # Usage: ./scripts/notarize-and-promote.sh [tag] # @@ -14,19 +13,48 @@ set -euo pipefail # --issuer TAG=${1:-$(git describe --tags --abbrev=0)} +VERSION=${TAG#v} SUBMISSION_FILE=".notarization-${TAG}" WORK_DIR=$(mktemp -d) trap 'rm -rf "$WORK_DIR"' EXIT NOTARY_ARGS=(--keychain-profile "sandboxctl") +# Fetch staged packages and find those matching this release version +echo "Fetching staged packages..." +STAGED=$(npm stage list --json) + +VMM_ID=$(echo "$STAGED" | python3 -c " +import sys, json +pkgs = json.load(sys.stdin) +for p in pkgs: + if p['packageName'] == '@inputforge/sandboxctl-vmm' and p['version'] == '$VERSION': + print(p['id']) + break +") + +if [[ -z "$VMM_ID" ]]; then + echo "error: no staged @inputforge/sandboxctl-vmm@$VERSION found" >&2 + echo "Run 'npm stage list' to see what is currently staged." >&2 + exit 1 +fi + +ALL_IDS=$(echo "$STAGED" | python3 -c " +import sys, json +pkgs = json.load(sys.stdin) +for p in pkgs: + if p['version'] == '$VERSION': + print(p['id']) +") + # Submit for notarization (skip if we already have a submission ID) if [[ -f "$SUBMISSION_FILE" ]]; then SUBMISSION_ID=$(cat "$SUBMISSION_FILE") echo "Resuming existing submission: $SUBMISSION_ID" else - echo "Downloading vmm binary from $TAG..." - gh release download "$TAG" --pattern "vmm" --output "$WORK_DIR/vmm" + echo "Downloading staged vmm tarball (id: $VMM_ID)..." + npm stage download "$VMM_ID" --output "$WORK_DIR/vmm.tgz" + tar -xzf "$WORK_DIR/vmm.tgz" -C "$WORK_DIR" --strip-components=2 package/dist/vmm chmod +x "$WORK_DIR/vmm" echo "Creating zip for notarization..." @@ -62,8 +90,10 @@ if [[ "$STATUS" != "Accepted" ]]; then exit 1 fi -echo "Notarization accepted! Promoting $TAG to full release..." -gh release edit "$TAG" --prerelease=false --latest +echo "Notarization accepted! Approving all staged packages for $TAG..." +while IFS= read -r id; do + npm stage approve "$id" +done <<< "$ALL_IDS" rm -f "$SUBMISSION_FILE" -echo "Done! $TAG is live. The publish workflow will push to npm shortly." +echo "Done! $TAG packages are now live on npm." From 1ba9c21a786d4c31f874979fbc96840e0713d5e5 Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: Mon, 8 Jun 2026 09:19:12 +0530 Subject: [PATCH 6/6] Bump all packages to 0.2.0 for next release Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 45 +++++++++++++++++++++++---------- packages/cli/package.json | 10 ++++---- packages/ec2/package.json | 4 +-- packages/providers/package.json | 2 +- packages/qemu/package.json | 4 +-- packages/vmm/package.json | 4 +-- 6 files changed, 44 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1a39ea..f4581bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1958,12 +1958,12 @@ }, "packages/cli": { "name": "sandboxctl", - "version": "0.1.0", + "version": "0.2.0", + "license": "MIT", "dependencies": { "@bomb.sh/args": "^0.3.1", "@clack/prompts": "^1.4.0", - "@inputforge/sandboxctl-providers": "0.1.0", - "@types/node": "^22.0.0", + "@inputforge/sandboxctl-providers": "0.2.0", "env-paths": "^4.0.0", "zod": "^4.4.3" }, @@ -1973,23 +1973,30 @@ "devDependencies": { "@types/node": "^22.0.0" }, + "engines": { + "node": ">=18.0.0" + }, "optionalDependencies": { - "@inputforge/sandboxctl-ec2": "0.1.0", - "@inputforge/sandboxctl-qemu": "0.1.0", - "@inputforge/sandboxctl-vmm": "0.1.0" + "@inputforge/sandboxctl-ec2": "0.2.0", + "@inputforge/sandboxctl-qemu": "0.2.0", + "@inputforge/sandboxctl-vmm": "0.2.0" } }, "packages/ec2": { "name": "@inputforge/sandboxctl-ec2", - "version": "0.1.0", + "version": "0.2.0", + "license": "MIT", "dependencies": { "@aws-sdk/client-ec2": "^3.817.0", "@aws-sdk/client-ssm": "^3.817.0", - "@inputforge/sandboxctl-providers": "0.1.0", + "@inputforge/sandboxctl-providers": "0.2.0", "env-paths": "^4.0.0" }, "devDependencies": { "@types/node": "^22.0.0" + }, + "engines": { + "node": ">=18.0.0" } }, "packages/lima": { @@ -2008,36 +2015,48 @@ }, "packages/providers": { "name": "@inputforge/sandboxctl-providers", - "version": "0.1.0", + "version": "0.2.0", + "license": "MIT", "devDependencies": { "@types/node": "^22.0.0" + }, + "engines": { + "node": ">=18.0.0" } }, "packages/qemu": { "name": "@inputforge/sandboxctl-qemu", - "version": "0.1.0", + "version": "0.2.0", + "license": "MIT", "dependencies": { "@gcu/iso9660": "^0.1.0", - "@inputforge/sandboxctl-providers": "0.1.0", + "@inputforge/sandboxctl-providers": "0.2.0", "env-paths": "^4.0.0" }, "devDependencies": { "@types/node": "^22.0.0" + }, + "engines": { + "node": ">=18.0.0" } }, "packages/vmm": { "name": "@inputforge/sandboxctl-vmm", - "version": "0.1.0", + "version": "0.2.0", + "license": "MIT", "os": [ "darwin" ], "dependencies": { "@gcu/iso9660": "^0.1.0", - "@inputforge/sandboxctl-providers": "0.1.0", + "@inputforge/sandboxctl-providers": "0.2.0", "env-paths": "^4.0.0" }, "devDependencies": { "@types/node": "^22.0.0" + }, + "engines": { + "node": ">=18.0.0" } } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 3864054..96bf7d8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "sandboxctl", - "version": "0.1.0", + "version": "0.2.0", "description": "Create and manage Linux sandbox VMs for development", "keywords": [ "development", @@ -34,7 +34,7 @@ "dependencies": { "@bomb.sh/args": "^0.3.1", "@clack/prompts": "^1.4.0", - "@inputforge/sandboxctl-providers": "0.1.0", + "@inputforge/sandboxctl-providers": "0.2.0", "env-paths": "^4.0.0", "zod": "^4.4.3" }, @@ -42,9 +42,9 @@ "@types/node": "^22.0.0" }, "optionalDependencies": { - "@inputforge/sandboxctl-ec2": "0.1.0", - "@inputforge/sandboxctl-qemu": "0.1.0", - "@inputforge/sandboxctl-vmm": "0.1.0" + "@inputforge/sandboxctl-ec2": "0.2.0", + "@inputforge/sandboxctl-qemu": "0.2.0", + "@inputforge/sandboxctl-vmm": "0.2.0" }, "engines": { "node": ">=18.0.0" diff --git a/packages/ec2/package.json b/packages/ec2/package.json index f565d08..2f3b255 100644 --- a/packages/ec2/package.json +++ b/packages/ec2/package.json @@ -1,6 +1,6 @@ { "name": "@inputforge/sandboxctl-ec2", - "version": "0.1.0", + "version": "0.2.0", "description": "sandboxctl VM provider backed by AWS EC2", "keywords": [ "aws", @@ -37,7 +37,7 @@ "dependencies": { "@aws-sdk/client-ec2": "^3.817.0", "@aws-sdk/client-ssm": "^3.817.0", - "@inputforge/sandboxctl-providers": "0.1.0", + "@inputforge/sandboxctl-providers": "0.2.0", "env-paths": "^4.0.0" }, "devDependencies": { diff --git a/packages/providers/package.json b/packages/providers/package.json index e0e9230..569c7cc 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -1,6 +1,6 @@ { "name": "@inputforge/sandboxctl-providers", - "version": "0.1.0", + "version": "0.2.0", "description": "Shared provider types for sandboxctl VM providers", "keywords": [ "provider", diff --git a/packages/qemu/package.json b/packages/qemu/package.json index d52a428..d53d9a9 100644 --- a/packages/qemu/package.json +++ b/packages/qemu/package.json @@ -1,6 +1,6 @@ { "name": "@inputforge/sandboxctl-qemu", - "version": "0.1.0", + "version": "0.2.0", "description": "sandboxctl VM provider backed by QEMU", "keywords": [ "linux", @@ -36,7 +36,7 @@ }, "dependencies": { "@gcu/iso9660": "^0.1.0", - "@inputforge/sandboxctl-providers": "0.1.0", + "@inputforge/sandboxctl-providers": "0.2.0", "env-paths": "^4.0.0" }, "devDependencies": { diff --git a/packages/vmm/package.json b/packages/vmm/package.json index cb31bda..696dafe 100644 --- a/packages/vmm/package.json +++ b/packages/vmm/package.json @@ -1,6 +1,6 @@ { "name": "@inputforge/sandboxctl-vmm", - "version": "0.1.0", + "version": "0.2.0", "description": "sandboxctl VM provider backed by the vmm Swift binary", "keywords": [ "apple", @@ -39,7 +39,7 @@ }, "dependencies": { "@gcu/iso9660": "^0.1.0", - "@inputforge/sandboxctl-providers": "0.1.0", + "@inputforge/sandboxctl-providers": "0.2.0", "env-paths": "^4.0.0" }, "devDependencies": {