From 5c04fd4bfd2505b24b7026f5d200cb7625a8eba8 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Sun, 19 Jul 2026 14:34:12 +0100 Subject: [PATCH 1/5] Add antigravity cli for agentic development --- docker/Dockerfile | 40 +++++---- scripts/agentic-sandbox.py | 170 +++++++++++++++++++++++++++++++++++++ scripts/agentic-sandbox.sh | 10 +++ 3 files changed, 203 insertions(+), 17 deletions(-) create mode 100644 scripts/agentic-sandbox.py create mode 100755 scripts/agentic-sandbox.sh diff --git a/docker/Dockerfile b/docker/Dockerfile index 1aeb43a4..285b06bb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,6 @@ -# Dockerfile for Ubuntu 24.04 with C++ development tools. FROM ubuntu:24.04 -# Install gcc, clang and some supporting tools for downloading/installing later tools. +# Install base tools and C++ development tools. RUN apt-get update && apt-get install -y --no-install-recommends \ cmake \ curl \ @@ -21,37 +20,44 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ wget \ && rm -rf /var/lib/apt/lists/* +# Install newer CMake from kitware. +RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null \ +&& apt-add-repository -y 'deb https://apt.kitware.com/ubuntu/ noble main' \ +&& apt-get update && apt-get install -y --no-install-recommends cmake \ +&& rm -rf /var/lib/apt/lists/* + # Install bazelisk. RUN ARCH=$(dpkg --print-architecture) && \ wget https://github.com/bazelbuild/bazelisk/releases/download/v1.25.0/bazelisk-linux-${ARCH} -O /usr/local/bin/bazelisk \ && chmod +x /usr/local/bin/bazelisk \ && ln -s /usr/local/bin/bazelisk /usr/local/bin/bazel -# Create non-root user +# Create non-root user. RUN useradd -m -s /bin/bash vscode \ && mkdir -p /workspace \ && chown vscode:vscode /workspace -# Install Node.js (required for Gemini CLI) +# Install Node.js (required for Gemini CLI and Claude Code). RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* -# Install Gemini CLI globally -RUN npm install -g @google/gemini-cli - -# Switch to non-root user +# Switch to non-root user. USER vscode WORKDIR /workspace -# Set up uv and Python environment -RUN curl -LsSf https://astral.sh/uv/install.sh | sh +# Configure npm for user-local global installs. +ENV NPM_CONFIG_PREFIX=/home/vscode/.npm-global +ENV PATH="/home/vscode/.npm-global/bin:/home/vscode/.local/bin:/usr/local/bin:${PATH}" +RUN mkdir -p /home/vscode/.npm-global -# Set up environment variables -ENV PATH="/home/vscode/.local/bin:/usr/local/bin:${PATH}" -ENV UV_PROJECT_ENVIRONMENT="/home/vscode/.venv" +# Install AI agent CLIs for compatibility with existing tooling that +# may invoke `gemini` or `claude`. +RUN npm install -g @google/gemini-cli @anthropic-ai/claude-code + +# Install Antigravity CLI (installs binary as `agy` into ~/.local/bin). +RUN curl -fsSL https://antigravity.google/cli/install.sh | bash -# Pre-configure Gemini CLI -RUN mkdir -p /home/vscode/.gemini \ - && echo '{"/workspace": "TRUST_FOLDER"}' > /home/vscode/.gemini/trustedFolders.json \ - && echo '{"security": {"auth": {"selectedType": "gemini-api-key"}}}' > /home/vscode/.gemini/settings.json +# Set up uv and Python environment. +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV UV_PROJECT_ENVIRONMENT="/home/vscode/.venv" diff --git a/scripts/agentic-sandbox.py b/scripts/agentic-sandbox.py new file mode 100644 index 00000000..04df4952 --- /dev/null +++ b/scripts/agentic-sandbox.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +""" +Script to run an AI agent in a Docker sandbox. + +Supported agents: Claude Code, Antigravity CLI. +""" + +import argparse +import os +import subprocess +import sys + + +def _seed_config_file(path: str, content: bytes) -> None: + """ + Create path with content and mode 0o600. + + Skips silently if it already exists. + """ + try: + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + except FileExistsError: + return + try: + os.write(fd, content) + finally: + os.close(fd) + + +def main() -> None: + """Provide the main entry point for the agentic sandbox script.""" + parser = argparse.ArgumentParser( + description="Run an AI agent (claude or antigravity)" + " in a Docker sandbox." + ) + parser.add_argument( + "agent", + choices=["claude", "antigravity"], + help="AI agent to run inside the sandbox.", + ) + parser.add_argument( + "--update", + action="store_true", + help="Update the agent CLI inside the container before running.", + ) + parser.add_argument( + "--rebuild-docker", action="store_true", help="Rebuild the Docker image." + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable verbose logging." + ) + + args = parser.parse_args() + + def log(msg: str) -> None: + """Log a message if verbose mode is enabled.""" + if args.verbose: + print(msg) + + project_root = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], text=True + ).strip() + + image_name = "cc-protocol-sandbox" + + image_exists = ( + subprocess.run( + ["docker", "image", "inspect", image_name], + capture_output=True, + ).returncode + == 0 + ) + + if args.rebuild_docker or not image_exists: + log(f"--- Building Docker Sandbox: {image_name} ---") + subprocess.check_call( + [ + "docker", + "build", + "-t", + image_name, + "-f", + os.path.join(project_root, "docker/Dockerfile"), + project_root, + ] + ) + + log(f"--- Starting Sandboxed {args.agent.capitalize()} Session ---") + log(f"Note: Your current directory {project_root} is mounted to /workspace") + + if args.agent == "antigravity": + npm_package = None + agent_cmd = "agy" + else: + npm_package = "@anthropic-ai/claude-code" + agent_cmd = "claude --dangerously-skip-permissions" + + if args.update: + if args.agent == "antigravity": + container_cmd = ( + "curl -fsSL https://antigravity.google/cli/install.sh | bash && " + f"{agent_cmd}" + ) + else: + container_cmd = ( + f"export NPM_CONFIG_PREFIX=~/.npm-global && " + f"export PATH=~/.npm-global/bin:$PATH && " + f"npm install -g {npm_package}@latest && {agent_cmd}" + ) + else: + container_cmd = agent_cmd + + run_args = [ + "docker", + "run", + "-it", + "--rm", + "-v", + f"{project_root}:/workspace", + ] + + if args.agent == "antigravity": + antigravity_config_dir = os.path.expanduser("~/.gemini") + os.makedirs(antigravity_config_dir, mode=0o700, exist_ok=True) + # Seed default config files if missing so they are accessible inside the + # container. + # Use os.open with restrictive permissions to avoid exposing credentials to + # other local users on multi-user systems. + _seed_config_file( + os.path.join(antigravity_config_dir, "trustedFolders.json"), + b'{"/workspace": "TRUST_FOLDER"}', + ) + _seed_config_file( + os.path.join(antigravity_config_dir, "settings.json"), + b'{"selectedAuthType": "oauth-personal"}', + ) + run_args.extend(["-v", f"{antigravity_config_dir}:/home/vscode/.gemini"]) + else: + host_claude_dir = os.path.expanduser("~/.claude") + host_claude_json = os.path.expanduser("~/.claude.json") + os.makedirs(host_claude_dir, mode=0o700, exist_ok=True) + # Ensure the file exists on the host so Docker doesn't create it as a directory. + # Use os.open with restrictive permissions to avoid exposing credentials to + # other local users on multi-user systems. + if os.path.exists(host_claude_json): + if not os.path.isfile(host_claude_json): + sys.exit( + f"Expected {host_claude_json} to be a regular file, but found a " + "different filesystem object. Remove or rename it and rerun." + ) + else: + _seed_config_file(host_claude_json, b"") + run_args.extend(["-v", f"{host_claude_dir}:/home/vscode/.claude"]) + run_args.extend(["-v", f"{host_claude_json}:/home/vscode/.claude.json"]) + + if "TERM" in os.environ: + run_args.extend(["-e", f"TERM={os.environ['TERM']}"]) + if "COLORTERM" in os.environ: + run_args.extend(["-e", f"COLORTERM={os.environ['COLORTERM']}"]) + + run_args.extend([image_name, "bash", "-c", container_cmd]) + + try: + subprocess.run(run_args, check=True) + except subprocess.CalledProcessError as e: + sys.exit(e.returncode) + + +if __name__ == "__main__": + main() diff --git a/scripts/agentic-sandbox.sh b/scripts/agentic-sandbox.sh new file mode 100755 index 00000000..451ec5d4 --- /dev/null +++ b/scripts/agentic-sandbox.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -eu -o pipefail + +# Get the workspace root +WORKSPACE_ROOT=$(git rev-parse --show-toplevel) + +# Change directory to the workspace root and run the python script +cd "$WORKSPACE_ROOT" +uv run scripts/agentic-sandbox.py "$@" From 2780888fd43bde9d638fcfe71be94b2db5167ba8 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Sun, 19 Jul 2026 14:36:31 +0100 Subject: [PATCH 2/5] Cleanup changes --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 285b06bb..1335c2b6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,8 +1,8 @@ FROM ubuntu:24.04 +SHELL ["/bin/bash", "-o", "pipefail", "-c"] # Install base tools and C++ development tools. RUN apt-get update && apt-get install -y --no-install-recommends \ - cmake \ curl \ g++ \ gdb \ From 19bc98ad14f85302c465f2b39d64e45f8e809d89 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Sun, 19 Jul 2026 14:38:27 +0100 Subject: [PATCH 3/5] Cleanup changes --- docker/Dockerfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1335c2b6..04ce20ef 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,8 @@ +# Dockerfile for Ubuntu 24.04 with C++ development tools. FROM ubuntu:24.04 SHELL ["/bin/bash", "-o", "pipefail", "-c"] -# Install base tools and C++ development tools. +# Install gcc, clang and some supporting tools for downloading/installing later tools. RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ g++ \ @@ -32,17 +33,17 @@ RUN ARCH=$(dpkg --print-architecture) && \ && chmod +x /usr/local/bin/bazelisk \ && ln -s /usr/local/bin/bazelisk /usr/local/bin/bazel -# Create non-root user. +# Create non-root user RUN useradd -m -s /bin/bash vscode \ && mkdir -p /workspace \ && chown vscode:vscode /workspace -# Install Node.js (required for Gemini CLI and Claude Code). +# Install Node.js (required for Gemini CLI and Claude Code) RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* -# Switch to non-root user. +# Switch to non-root user USER vscode WORKDIR /workspace @@ -58,6 +59,6 @@ RUN npm install -g @google/gemini-cli @anthropic-ai/claude-code # Install Antigravity CLI (installs binary as `agy` into ~/.local/bin). RUN curl -fsSL https://antigravity.google/cli/install.sh | bash -# Set up uv and Python environment. +# Set up uv and Python environment RUN curl -LsSf https://astral.sh/uv/install.sh | sh ENV UV_PROJECT_ENVIRONMENT="/home/vscode/.venv" From c930def2111d9059087ccff483d4fbfa0792b4bb Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Sun, 19 Jul 2026 14:50:45 +0100 Subject: [PATCH 4/5] Remove Gemini CLI --- docker/Dockerfile | 7 +++---- scripts/gemini-sandbox.sh | 31 ------------------------------- 2 files changed, 3 insertions(+), 35 deletions(-) delete mode 100755 scripts/gemini-sandbox.sh diff --git a/docker/Dockerfile b/docker/Dockerfile index 04ce20ef..efa4ecb6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -38,7 +38,7 @@ RUN useradd -m -s /bin/bash vscode \ && mkdir -p /workspace \ && chown vscode:vscode /workspace -# Install Node.js (required for Gemini CLI and Claude Code) +# Install Node.js (required for Claude Code) RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* @@ -52,9 +52,8 @@ ENV NPM_CONFIG_PREFIX=/home/vscode/.npm-global ENV PATH="/home/vscode/.npm-global/bin:/home/vscode/.local/bin:/usr/local/bin:${PATH}" RUN mkdir -p /home/vscode/.npm-global -# Install AI agent CLIs for compatibility with existing tooling that -# may invoke `gemini` or `claude`. -RUN npm install -g @google/gemini-cli @anthropic-ai/claude-code +# Install Claude Code CLI. +RUN npm install -g @anthropic-ai/claude-code # Install Antigravity CLI (installs binary as `agy` into ~/.local/bin). RUN curl -fsSL https://antigravity.google/cli/install.sh | bash diff --git a/scripts/gemini-sandbox.sh b/scripts/gemini-sandbox.sh deleted file mode 100755 index 955c04c5..00000000 --- a/scripts/gemini-sandbox.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -# Exit on error -set -euo pipefail - -# Check if GEMINI_API_KEY is set -if [ -z "$GEMINI_API_KEY" ]; then - echo "Error: GEMINI_API_KEY environment variable is not set." - echo "Please set it before running this script:" - echo " export GEMINI_API_KEY='your_api_key_here'" - exit 1 -fi - -IMAGE_NAME="value-types-sandbox" -DOCKERFILE="docker/Dockerfile" - -# Build the image -echo "--- Building Docker Sandbox: $IMAGE_NAME ---" -docker build -t "$IMAGE_NAME" -f "$DOCKERFILE" . - -# Run the container -echo "--- Starting Sandboxed Gemini Session ---" -echo "Note: Your current directory $(pwd) is mounted to /workspace" - -docker run -it --rm \ - -v "$(pwd):/workspace" \ - -e GEMINI_API_KEY="$GEMINI_API_KEY" \ - -e TERM=${TERM:-} \ - -e COLORTERM=${COLORTERM:-} \ - "$IMAGE_NAME" \ - gemini From 27c75baeb51a5ad79e1dc1007319369592a4e379 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Sun, 19 Jul 2026 15:01:35 +0100 Subject: [PATCH 5/5] Cleanup changes --- scripts/agentic-sandbox.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/agentic-sandbox.py b/scripts/agentic-sandbox.py index 04df4952..16c105b5 100644 --- a/scripts/agentic-sandbox.py +++ b/scripts/agentic-sandbox.py @@ -160,10 +160,7 @@ def log(msg: str) -> None: run_args.extend([image_name, "bash", "-c", container_cmd]) - try: - subprocess.run(run_args, check=True) - except subprocess.CalledProcessError as e: - sys.exit(e.returncode) + subprocess.run(run_args, check=True) if __name__ == "__main__":