commit 962002aec17df13c2aeeeb1b0e8c058784d03b0b Author: Jarek Rozanski Date: Sat Oct 10 23:51:14 2026 +0200 Opencode V2 sbx agent diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2a90d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.tar +*.log +.env +.claude +.opencode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cd5d5c4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +# Pins the OpenCode CLI version inside the sandbox to OpenCode v2. +# +# docker/sandbox-templates:opencode bakes in OpenCode v1 (the `opencode-ai` +# npm package, 1.18.32 as of writing). OpenCode v2 ships as a different npm +# package, `@opencode/cli` (confirmed via `npm view @opencode/cli version` +# and by running `opencode --version` in the built image, which reports +# "opencode v2.0.26"). Both packages install a binary named `opencode` into +# the same global npm prefix, so the old one has to be removed first. +FROM docker/sandbox-templates:opencode +ARG OPENCODE_VERSION=2.0.26 +RUN npm uninstall -g opencode-ai \ + && npm install -g @opencode/cli@${OPENCODE_VERSION} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..efd650d --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# Utilities for running the OpenCode agent in a Docker Sandbox. +# Requires the `sbx` CLI: https://docs.docker.com/ai/sandboxes/install/ +# +# `sbx` doesn't have a nested sandbox-in-sandbox mode, so these targets are +# meant to be run on your host machine from this directory (where sbxenv.yaml +# lives), not from inside another sandbox. + +NAME ?= opencode-v2 +AGENT ?= opencode +IMAGE_TAG ?= opencode-v2:local +OPENCODE_VERSION ?= 2.0.26 + +.PHONY: help cli-check image build install + +help: + @echo "NAME=$(NAME) AGENT=$(AGENT) OPENCODE_VERSION=$(OPENCODE_VERSION)" + @echo "" + @echo " make image build the $(IMAGE_TAG) image pinning the opencode CLI version" + @echo " make build [OPENCODE_VERSION=...] build the image and register it with sbx as a local template" + @echo " make install install the opencode-sandbox shim to ~/.local/bin" + +cli-check: + @command -v sbx >/dev/null 2>&1 || { \ + echo "sbx CLI not found. Install it: https://docs.docker.com/ai/sandboxes/install/"; \ + exit 1; \ + } + @sbx version + +image: + docker build --build-arg OPENCODE_VERSION=$(OPENCODE_VERSION) -t $(IMAGE_TAG) . + +build: cli-check image + docker image save $(IMAGE_TAG) -o /tmp/opencode-v2.tar + sbx template load /tmp/opencode-v2.tar + +install: + sh ./install.sh + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ca5cfb --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +# opencodev2 + +Config and `make` utilities for running **OpenCode v2** inside a +[Docker Sandbox](https://docs.docker.com/ai/sandboxes/) — an isolated +environment that mounts your working directory read-write, limits network +access by policy, and keeps agent-installed tooling out of your host machine. + +## Prerequisites + +1. Install the `sbx` CLI (one-time, on your host): + + ```console + # macOS + brew trust docker/tap + brew install docker/tap/sbx + + # Ubuntu (with Docker Engine already installed) + curl -fsSL https://get.docker.com | sudo SBX=1 sh + + # Windows + winget install -h Docker.sbx + ``` + + See [docs.docker.com/ai/sandboxes/install](https://docs.docker.com/ai/sandboxes/install/) + for other platforms and system-wide installs. + +2. Authenticate and store a credential for whichever model provider OpenCode + should use (one-time, host-level secrets — not stored in this repo): + + ```console + sbx login + sbx secret set anthropic # or: openai, google, openrouter, ... + sbx secret set github # optional, for repo/PR access via `gh auth token` + ``` + +## Quick start + +```console +make build # build the image, register it with sbx as a local template +make install # install the opencode-sandbox shim (one-time) +. ~/.bashrc # or ~/.zshrc / restart your shell — whatever install.sh edited + +cd ~/some-project +opencode-sandbox # launch OpenCode v2 in a sandbox, workspace = current directory +``` + +`opencode-sandbox` works from **any** directory once installed — it's not tied +to this project directory, and it's not named `opencode` so it won't shadow a +real, locally-installed OpenCode CLI on your PATH. Re-run `make build` whenever +you bump `OPENCODE_VERSION` or change [`./Dockerfile`](./Dockerfile); +`make install` only needs to run once per machine. + +On first run, `sbx` asks you to pick a global network policy (Open / Balanced / +Locked Down). **Balanced** is a reasonable default. Adjust anytime: + +```console +sbx policy ls # show current rules +sbx policy allow network # allow an extra host +``` + +## What `make install` does + +[`install.sh`](./install.sh) is POSIX `/bin/sh` (not shell-specific) and does +two things, both idempotent — safe to re-run: + +1. Writes a shim to `~/.local/bin/opencode-sandbox`: + + ```sh + #!/bin/sh + exec sbx run -t localhost/opencode-v2:local "$@" opencode + ``` + + `-t localhost/opencode-v2:local` points at the template `make build` + registers (see below); `opencode` is the agent name; `"$@"` forwards any + extra args (e.g. `opencode-sandbox --model claude-sonnet-5`). Being a real + executable on `PATH` rather than a shell function means it works + identically under bash, zsh, fish, or anything else. + +2. If `~/.local/bin` isn't already on `PATH`, adds it — detecting your login + shell via `$SHELL` (not whatever shell happens to run this script) and + picking the right config file: `~/.zshrc` for zsh (falling back to an + existing `~/.zshenv` if that's what you already use), `~/.bash_profile` on + macOS / `~/.bashrc` elsewhere for bash, `fish_add_path` for fish (no file + to edit), or `~/.profile` otherwise. + +This pattern (shim binary + per-shell/per-OS config detection + marker-based +idempotent edit) is adapted from +[`install-shortcut.sh`](https://cloud.inputobjects.eu/forge/jrozanski/opencode-sandbox/src/branch/main/scripts/install-shortcut.sh) +in a related project. + +## Running OpenCode v2 (not the built-in v1 template) + +`sbx run opencode` on its own uses Docker's stock image, `docker/sandbox-templates:opencode`, +which bakes in **OpenCode v1** — the `opencode-ai` npm package, pinned at `1.18.32` as of +this writing (verified with +`docker run --rm --entrypoint sh docker/sandbox-templates:opencode -c 'opencode --version'`). + +OpenCode v2 is real and actively published, but as a **different npm package**: +`@opencode/cli` (currently `2.0.26` — verify with `npm view @opencode/cli version`). The +old `opencode-ai` line never became v2; it was superseded by this new package, which is +why checking `opencode-ai`'s version history makes v2 look like it doesn't exist. Both +packages install a binary named `opencode` into the same global npm prefix, so getting v2 +means removing the v1 package first, then installing the v2 one — confirmed by actually +building this and running `opencode --version` inside the result, which reports +`opencode v2.0.26`. + +[`./Dockerfile`](./Dockerfile) does the swap at the image level: + +```dockerfile +FROM docker/sandbox-templates:opencode +ARG OPENCODE_VERSION=2.0.26 +RUN npm uninstall -g opencode-ai \ + && npm install -g @opencode/cli@${OPENCODE_VERSION} +``` + +`sbx` runs each sandbox in its own isolated microVM with its own Docker +daemon/image cache, so a plain `docker build` on your host is invisible to it +— `make build` closes that gap with `docker image save` + `sbx template load`, +which registers the image as a local template `sbx run -t localhost/...` (or +`sbxenv.yaml`'s `sandboxOptions.template`) can find. + +To bump to a newer v2 release: + +```console +make build OPENCODE_VERSION=2.0.27 # or any other published @opencode/cli version +``` + +or edit the `OPENCODE_VERSION` default in the [`Makefile`](./Makefile). Avoid pinning to +`latest` long-term — once the image layer is cached, Docker won't re-resolve `latest` on +later builds, so it'll silently stop tracking new releases; use `--no-cache` or bump to +an explicit version instead. + +**Note:** [`sbxenv.yaml`](./sbxenv.yaml)'s `sandboxOptions.template` is currently set to +the bare tag `opencode-v2:local`, while the `opencode-sandbox` shim above (and `sbx +template load`'s own output) use the `localhost/opencode-v2:local` form. If running +`sbx run opencode` from inside this directory (reading `sbxenv.yaml`) doesn't find the +template, try adding the `localhost/` prefix there too. + +## Makefile targets + +```console +make image build the opencode-v2:local image (OPENCODE_VERSION=2.0.26 by default) +make build image + register it with sbx as a local template +make install install the opencode-sandbox shim to ~/.local/bin +``` + +Everything else — `sbx login`, `sbx secret set`, `sbx policy`, `sbx run`, +`sbx stop`/`rm`/`ls`, `sbx ports` — is plain `sbx`; there's no Makefile wrapper +for those. + +## Notes + +- `make build` is meant to run on your **host** machine — `sbx` has no nested + sandbox-in-sandbox mode. +- The published [CLI reference](https://docs.docker.com/reference/cli/sbx/) page is + JS-rendered and didn't fully resolve during research for this README; if a command + here doesn't match your installed `sbx` version, check `sbx --help`. diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..af1ec47 --- /dev/null +++ b/install.sh @@ -0,0 +1,88 @@ +#!/bin/sh +# Installs a global `opencode-sandbox` shim at ~/.local/bin so that command, +# run from any directory, launches OpenCode v2 from the local sbx template +# registered by `make build` — with the directory you're standing in as the +# sandbox's workspace. +# +# POSIX sh only (no bashisms) -- this may run under any login shell's `sh`, +# not necessarily the user's interactive shell. +# +# Named opencode-sandbox, not opencode, so it doesn't shadow a real, +# locally-installed opencode CLI on PATH. +# +# Pattern (shell/OS detection, marker-based idempotent PATH edit, resolving +# the login shell via $SHELL rather than the script's own running shell) +# adapted from https://cloud.inputobjects.eu/forge/jrozanski/opencode-sandbox/src/branch/main/scripts/install-shortcut.sh +set -eu + +BIN_DIR="$HOME/.local/bin" +SHIM="$BIN_DIR/opencode-sandbox" + +mkdir -p "$BIN_DIR" + +cat > "$SHIM" <<'EOF' +#!/bin/sh +exec sbx run -t localhost/opencode-v2:local "$@" opencode +EOF +chmod +x "$SHIM" +echo "==> Installed shim: $SHIM" + +# Already on PATH? Nothing left to do. +case ":$PATH:" in + *":$BIN_DIR:"*) + echo "==> $BIN_DIR is already on PATH." + exit 0 + ;; +esac + +# Resolve the user's *login* shell via $SHELL, not the shell running this +# script, so the right config file gets edited regardless of how this +# script itself was invoked. +shell_name=$(basename "${SHELL:-}") + +if [ "$shell_name" = fish ] && command -v fish >/dev/null 2>&1; then + # fish_add_path persists via a universal variable -- no config file to edit. + fish -c "fish_add_path $BIN_DIR" + echo "==> Added $BIN_DIR to PATH via fish_add_path." + exit 0 +fi + +case "$shell_name" in + zsh) + # Prefer .zshrc (the standard interactive-shell file); if it doesn't + # exist but .zshenv does, respect that existing setup instead of + # scattering PATH across two files. Only create .zshrc if neither exists. + if [ -f "$HOME/.zshrc" ]; then + rc="$HOME/.zshrc" + elif [ -f "$HOME/.zshenv" ]; then + rc="$HOME/.zshenv" + else + rc="$HOME/.zshrc" + fi + ;; + bash) + # macOS Terminal.app runs bash as a login shell, sourcing + # ~/.bash_profile (not ~/.bashrc) unless the user's profile chains to it. + case "$(uname -s)" in + Darwin) rc="$HOME/.bash_profile" ;; + *) rc="$HOME/.bashrc" ;; + esac + ;; + *) rc="$HOME/.profile" ;; +esac + +marker_begin="# >>> opencode-sandbox >>>" +marker_end="# <<< opencode-sandbox <<<" + +if [ -f "$rc" ] && grep -qF "$marker_begin" "$rc"; then + echo "==> $rc already configures PATH for opencode-sandbox; skipping." +else + touch "$rc" + { + printf '\n%s\n' "$marker_begin" + printf 'export PATH="%s:$PATH"\n' "$BIN_DIR" + printf '%s\n' "$marker_end" + } >> "$rc" + echo "==> Added $BIN_DIR to PATH in $rc." + echo "==> Restart your shell (or run: . $rc) to pick it up." +fi diff --git a/sbxenv.yaml b/sbxenv.yaml new file mode 100644 index 0000000..b15d8d3 --- /dev/null +++ b/sbxenv.yaml @@ -0,0 +1,45 @@ +schemaVersion: "1" +name: opencodev2 +agent: opencode + +# Mount this project directory into the sandbox as the primary workspace. +workspace: . + +env: {} + +# Use the image built by `make image` instead of Docker's stock opencode +# template, so the sandbox runs the OpenCode CLI version pinned in ./Dockerfile +# rather than whatever version Docker last baked into docker/sandbox-templates:opencode. +sandboxOptions: + template: opencode-v2:local +# memory: 8g +# cpus: 4 + +# Provider credentials are stored on the host (outside this file) with the +# sbx CLI, not here. Run one of these once before `make run`: +# sbx secret set anthropic +# sbx secret set openai +# sbx secret set google +# sbx secret set openrouter +# sbx secret set github --command 'gh auth token' # for PRs/repo access +# +# OpenCode Zen or another custom API key: +# sbx secret set-custom --host opencode.ai --env OPENCODE_API_KEY --value "$OPENCODE_API_KEY" +# sbx policy allow network opencode.ai:443 + +# Uncomment to expose a port the agent starts inside the sandbox (e.g. a dev +# server) on the host: +# ports: +# - sandbox: 3000 +# host: 3000 +# protocol: tcp4 + +# lifecycle.* commands (initialize/postCreate/preRemove) run on the HOST, not +# inside the sandbox, so they're for preparing the workspace on disk (cloning +# repos, host-side `npm install` into a mounted dir) — not for changing tools +# installed inside the sandbox. Use ./Dockerfile + sandboxOptions.template for +# that instead. +# lifecycle: +# postCreate: +# - command: npm install +# workdir: .