Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ jobs:
--build-arg COPILOT_API_VERSION="${{ steps.versions.outputs.copilot_api_version }}" \
.

- name: Build core and rust images via Makefile
run: |
make build-core build-rust-image \
IMAGE_NAME=deva-smoke \
TAG=ci \
CORE_TAG=ci-core \
RUST_TAG=ci-rust \
CLAUDE_CODE_VERSION="${{ steps.versions.outputs.claude_code_version }}" \
CODEX_VERSION="${{ steps.versions.outputs.codex_version }}" \
GEMINI_CLI_VERSION="${{ steps.versions.outputs.gemini_cli_version }}" \
ATLAS_CLI_VERSION="${{ steps.versions.outputs.atlas_cli_version }}" \
COPILOT_API_VERSION="${{ steps.versions.outputs.copilot_api_version }}"

- name: Install and launch each agent without a TTY
shell: bash
run: |
Expand All @@ -90,6 +103,50 @@ jobs:
deva.sh codex -Q -- --version
deva.sh gemini -Q -- --version

- name: Smoke Claude --chrome mount assembly
shell: bash
run: |
set -euo pipefail
tmp_root="$(mktemp -d)"
bridge_dir="$tmp_root/claude-mcp-browser-bridge-$(id -un)"
profile_dir="$tmp_root/chrome/Default"

mkdir -p "$bridge_dir"
chmod 700 "$bridge_dir"
mkdir -p "$profile_dir/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn/1.0.0"

dry_run="$(
DEVA_DOCKER_IMAGE=deva-smoke \
DEVA_DOCKER_TAG=ci \
DEVA_CHROME_PROFILE_PATH="$profile_dir" \
DEVA_HOST_CHROME_BRIDGE_DIR="$bridge_dir" \
./deva.sh claude --debug --dry-run -- --chrome 2>&1
)"

printf '%s\n' "$dry_run"
grep -F -- "$bridge_dir:/deva-host-chrome-bridge" <<<"$dry_run"
grep -F -- "$profile_dir/Extensions:/home/deva/.config/google-chrome/Default/Extensions:ro" <<<"$dry_run"

- name: Smoke Chrome bridge entrypoint symlink
shell: bash
run: |
set -euo pipefail
tmp_root="$(mktemp -d)"
bridge_dir="$tmp_root/claude-mcp-browser-bridge-$(id -un)"

mkdir -p "$bridge_dir"
chmod 700 "$bridge_dir"

docker run --rm \
-e DEVA_AGENT=claude \
-e DEVA_UID="$(id -u)" \
-e DEVA_GID="$(id -g)" \
-e DEVA_CHROME_HOST_BRIDGE=1 \
-e DEVA_CHROME_HOST_BRIDGE_DIR=/deva-host-chrome-bridge \
-v "$bridge_dir:/deva-host-chrome-bridge" \
deva-smoke:ci \
bash -lc 'link="/tmp/claude-mcp-browser-bridge-$(id -un)"; test -L "$link"; test "$(readlink "$link")" = "/deva-host-chrome-bridge"'

docs:
name: Docs Build
runs-on: ubuntu-latest
Expand Down
45 changes: 16 additions & 29 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,10 @@ ENV NPM_CONFIG_FETCH_RETRIES=5 \
NPM_CONFIG_FETCH_RETRY_FACTOR=2 \
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=10000

# Final stage with shell setup
FROM tools AS final
# Stable agent base: user, shell, and shared runtimes.
# Keep volatile agent package installs out of this stage so downstream
# images can inherit it without rebuilding on every late-stage change.
FROM tools AS agent-base

# Create non-root user for agent execution
# Using 1001 as default to avoid conflicts with ubuntu user (usually 1000)
Expand Down Expand Up @@ -208,43 +210,28 @@ RUN echo 'export ZSH="$HOME/.oh-my-zsh"' > "$DEVA_HOME/.zshrc" && \
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
$DEVA_HOME/.local/bin/uv python install 3.14t

# Final image: install volatile agent packages on top of the stable base.
FROM agent-base AS final

# Declare ARGs immediately before usage to minimize cache invalidation
ARG CLAUDE_CODE_VERSION
ARG CODEX_VERSION
ARG GEMINI_CLI_VERSION=latest
ARG CLAUDE_CODE_VERSION=2.1.81
ARG CODEX_VERSION=0.116.0
ARG GEMINI_CLI_VERSION=0.35.0

# Record key tool versions as labels for quick inspection
LABEL org.opencontainers.image.claude_code_version=${CLAUDE_CODE_VERSION}
LABEL org.opencontainers.image.codex_version=${CODEX_VERSION}
LABEL org.opencontainers.image.gemini_cli_version=${GEMINI_CLI_VERSION}

# Install CLI tools via npm
RUN --mount=type=cache,target=/home/deva/.npm,uid=${DEVA_UID},gid=${DEVA_GID},sharing=locked \
set -eux && \
npm config set prefix "$DEVA_HOME/.npm-global" && \
npm install -g --no-audit --no-fund \
@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} \
@mariozechner/claude-trace \
@openai/codex@${CODEX_VERSION} \
@google/gemini-cli@${GEMINI_CLI_VERSION} && \
npm cache clean --force && \
"$DEVA_HOME/.npm-global/bin/claude" --version && \
"$DEVA_HOME/.npm-global/bin/codex" --version && \
"$DEVA_HOME/.npm-global/bin/gemini" --version && \
"$DEVA_HOME/.npm-global/bin/claude-trace" --help >/dev/null && \
(npm list -g --depth=0 @anthropic-ai/claude-code @openai/codex @google/gemini-cli || true)

# Volatile packages: Install at the end to avoid cascading rebuilds
ARG ATLAS_CLI_VERSION=main
ARG ATLAS_CLI_VERSION=v0.1.4

LABEL org.opencontainers.image.atlas_cli_version=${ATLAS_CLI_VERSION}

# Install atlas-cli binary + skill via upstream install.sh
# - Uses prebuilt release tarball (faster than go install)
# - Falls back to go install if no prebuilt for platform
# - Installs skill with proper structure (SKILL.md + references/)
RUN curl -fsSL "https://raw.githubusercontent.com/lroolle/atlas-cli/${ATLAS_CLI_VERSION}/install.sh" \
| bash -s -- --skill-dir $DEVA_HOME/.skills
COPY --chown=deva:deva scripts/install-agent-tooling.sh /tmp/install-agent-tooling.sh

RUN --mount=type=cache,target=/home/deva/.npm,uid=${DEVA_UID},gid=${DEVA_GID},sharing=locked \
bash /tmp/install-agent-tooling.sh && \
rm -f /tmp/install-agent-tooling.sh

USER root

Expand Down
34 changes: 34 additions & 0 deletions Dockerfile.rust
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,26 @@ FROM ${BASE_IMAGE}
LABEL org.opencontainers.image.title="deva-rust"
LABEL org.opencontainers.image.description="Rust development environment with full toolchain"

ARG CLAUDE_CODE_VERSION=2.1.81
ARG CODEX_VERSION=0.116.0
ARG GEMINI_CLI_VERSION=0.35.0
ARG ATLAS_CLI_VERSION=v0.1.4
Comment on lines +11 to +14

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Thread rust CLI version args through Dockerfile.rust builds

This stage now reinstalls Claude/Codex/Gemini from hardcoded defaults, so any build path that does not pass explicit CLAUDE_CODE_VERSION/CODEX_VERSION/GEMINI_CLI_VERSION will silently diverge from the resolved release versions. I checked .github/workflows/release.yml: the build-and-push-rust job only passes BASE_IMAGE, so rust releases can lag or downgrade tool versions relative to the base image/job metadata. Please wire the resolved version args into every rust build path (or stop reinstalling these tools in the rust stage).

Useful? React with 👍 / 👎.

ARG RUST_TOOLCHAINS="stable"
ARG RUST_TARGETS="wasm32-unknown-unknown"

LABEL org.opencontainers.image.claude_code_version=${CLAUDE_CODE_VERSION}
LABEL org.opencontainers.image.codex_version=${CODEX_VERSION}
LABEL org.opencontainers.image.gemini_cli_version=${GEMINI_CLI_VERSION}
LABEL org.opencontainers.image.atlas_cli_version=${ATLAS_CLI_VERSION}

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

ENV RUSTUP_HOME=/opt/rustup \
CARGO_HOME=/opt/cargo \
PATH=/opt/cargo/bin:$PATH

USER root

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
Expand Down Expand Up @@ -67,3 +78,26 @@ RUN echo 'export PATH="/opt/cargo/bin:$PATH"' >> "$DEVA_HOME/.zshrc" && \
echo 'alias cw="cargo watch -x check -x test -x run"' >> "$DEVA_HOME/.zshrc" && \
echo 'alias cf="cargo fmt"' >> "$DEVA_HOME/.zshrc" && \
echo 'alias cl="cargo clippy"' >> "$DEVA_HOME/.zshrc"

USER $DEVA_USER

COPY --chown=deva:deva scripts/install-agent-tooling.sh /tmp/install-agent-tooling.sh

RUN --mount=type=cache,target=/home/deva/.npm,uid=${DEVA_UID},gid=${DEVA_GID},sharing=locked \
bash /tmp/install-agent-tooling.sh && \
rm -f /tmp/install-agent-tooling.sh

USER root

COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY scripts/deva-bridge-tmux /usr/local/bin/deva-bridge-tmux

RUN chmod 755 /usr/local/bin/docker-entrypoint.sh && \
chmod 755 /usr/local/bin/deva-bridge-tmux && \
chmod -R 755 /usr/local/bin/scripts || true

WORKDIR /root

ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]

CMD ["claude"]
41 changes: 32 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
IMAGE_NAME := ghcr.io/thevibeworks/deva
TAG := latest
RUST_TAG := rust
CORE_TAG := core
DOCKERFILE := Dockerfile
RUST_DOCKERFILE := Dockerfile.rust
MAIN_IMAGE := $(IMAGE_NAME):$(TAG)
RUST_IMAGE := $(IMAGE_NAME):$(RUST_TAG)
CORE_IMAGE := $(IMAGE_NAME):$(CORE_TAG)
CONTAINER_NAME := deva-$(shell basename $(PWD))-$(shell date +%s)

# Smart image detection: auto-detect available image for version checking
Expand All @@ -18,11 +20,11 @@ DETECTED_IMAGE := $(shell \
else \
echo "$(IMAGE_NAME):$(TAG)"; \
fi)
CLAUDE_CODE_VERSION := $(shell npm view @anthropic-ai/claude-code version 2>/dev/null || echo "2.0.1")
CODEX_VERSION := $(shell npm view @openai/codex version 2>/dev/null || echo "0.42.0")
GEMINI_CLI_VERSION := $(shell npm view @google/gemini-cli version 2>/dev/null || echo "latest")
ATLAS_CLI_VERSION := $(shell gh api repos/lroolle/atlas-cli/releases/latest --jq '.tag_name' 2>/dev/null || echo "v0.1.1")
COPILOT_API_VERSION := $(shell gh api repos/ericc-ch/copilot-api/branches/master --jq '.commit.sha' 2>/dev/null || echo "83cdfde17d7d3be36bd2493cc7592ff13be4928d")
CLAUDE_CODE_VERSION := $(shell npm view @anthropic-ai/claude-code version 2>/dev/null || echo "2.1.81")
CODEX_VERSION := $(shell npm view @openai/codex version 2>/dev/null || echo "0.116.0")
GEMINI_CLI_VERSION := $(shell npm view @google/gemini-cli version 2>/dev/null || echo "0.35.0")
ATLAS_CLI_VERSION := $(shell gh api repos/lroolle/atlas-cli/releases/latest --jq '.tag_name' 2>/dev/null || echo "v0.1.4")
COPILOT_API_VERSION := $(shell gh api repos/ericc-ch/copilot-api/branches/master --jq '.commit.sha' 2>/dev/null || echo "0ea08febdd7e3e055b03dd298bf57e669500b5c1")

export DOCKER_BUILDKIT := 1

Expand Down Expand Up @@ -79,17 +81,33 @@ rebuild:
@echo "✅ Rebuild completed: $(MAIN_IMAGE)"


.PHONY: build-rust
build-rust:
.PHONY: build-core
build-core:
@echo "🔨 Building stable core image..."
docker build -f $(DOCKERFILE) --target agent-base --build-arg COPILOT_API_VERSION=$(COPILOT_API_VERSION) -t $(CORE_IMAGE) .
@echo "✅ Core build completed: $(CORE_IMAGE)"
Comment on lines +84 to +88
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makefile passes COPILOT_API_VERSION as a build arg (including in build-core), but the Dockerfile checks out the Copilot API repo via COPILOT_API_COMMIT (which defaults to master). As a result, builds are not actually pinned to the SHA in COPILOT_API_VERSION, and the label can misrepresent what was built. Consider passing COPILOT_API_COMMIT=$(COPILOT_API_VERSION) (or renaming/aligning the args) so the checked-out commit matches the version you record.

Copilot uses AI. Check for mistakes.

.PHONY: build-rust-image
build-rust-image:
@echo "🔨 Building Rust Docker image..."
docker build -f $(RUST_DOCKERFILE) --build-arg BASE_IMAGE=$(MAIN_IMAGE) -t $(RUST_IMAGE) .
docker build -f $(RUST_DOCKERFILE) \
--build-arg BASE_IMAGE=$(CORE_IMAGE) \
--build-arg CLAUDE_CODE_VERSION=$(CLAUDE_CODE_VERSION) \
--build-arg CODEX_VERSION=$(CODEX_VERSION) \
--build-arg GEMINI_CLI_VERSION=$(GEMINI_CLI_VERSION) \
--build-arg ATLAS_CLI_VERSION=$(ATLAS_CLI_VERSION) \
-t $(RUST_IMAGE) .
@echo "✅ Rust build completed: $(RUST_IMAGE)"

.PHONY: build-rust
build-rust: build-core build-rust-image

.PHONY: build-all
build-all:
@echo "🔨 Building all images with versions: Claude $(CLAUDE_CODE_VERSION), Codex $(CODEX_VERSION), Gemini $(GEMINI_CLI_VERSION), Atlas $(ATLAS_CLI_VERSION), Copilot-API $(COPILOT_API_VERSION)..."
@$(MAKE) build-core COPILOT_API_VERSION=$(COPILOT_API_VERSION)
@$(MAKE) build-main CLAUDE_CODE_VERSION=$(CLAUDE_CODE_VERSION) CODEX_VERSION=$(CODEX_VERSION) GEMINI_CLI_VERSION=$(GEMINI_CLI_VERSION) ATLAS_CLI_VERSION=$(ATLAS_CLI_VERSION) COPILOT_API_VERSION=$(COPILOT_API_VERSION)
@$(MAKE) build-rust BASE_IMAGE=$(MAIN_IMAGE)
@$(MAKE) build-rust-image CLAUDE_CODE_VERSION=$(CLAUDE_CODE_VERSION) CODEX_VERSION=$(CODEX_VERSION) GEMINI_CLI_VERSION=$(GEMINI_CLI_VERSION) ATLAS_CLI_VERSION=$(ATLAS_CLI_VERSION) COPILOT_API_VERSION=$(COPILOT_API_VERSION)
@echo "✅ All images built successfully"

.PHONY: buildx
Expand Down Expand Up @@ -160,6 +178,7 @@ clean:
@echo "Removing project images..."
-docker rmi $(MAIN_IMAGE) 2>/dev/null || true
-docker rmi $(RUST_IMAGE) 2>/dev/null || true
-docker rmi $(CORE_IMAGE) 2>/dev/null || true
@echo "Pruning stopped containers..."
-docker container prune -f
@echo "Pruning unused images..."
Expand All @@ -179,6 +198,7 @@ clean-all:
@echo "Removing project images..."
-docker rmi $(MAIN_IMAGE) 2>/dev/null || true
-docker rmi $(RUST_IMAGE) 2>/dev/null || true
-docker rmi $(CORE_IMAGE) 2>/dev/null || true
@echo "Removing ALL stopped containers..."
-docker container prune -af
@echo "Removing ALL dangling and unused images..."
Expand Down Expand Up @@ -297,6 +317,7 @@ help:
@echo ""
@echo "Available targets:"
@echo " build Build all images (auto-detects latest npm versions)"
@echo " build-core Build stable core image only"
@echo " build-main Build main Docker image only"
@echo " build-rust Build Rust Docker image"
@echo " build-all Build all images (main + rust)"
Expand All @@ -320,6 +341,7 @@ help:
@echo " IMAGE_NAME Main image name (default: $(IMAGE_NAME))"
@echo " TAG Docker image tag (default: $(TAG))"
@echo " RUST_TAG Rust image tag (default: $(RUST_TAG))"
@echo " CORE_TAG Stable core image tag (default: $(CORE_TAG))"
@echo " DOCKERFILE Dockerfile to use (default: $(DOCKERFILE))"
@echo " RUST_DOCKERFILE Rust Dockerfile path (default: $(RUST_DOCKERFILE))"
@echo " CLAUDE_CODE_VERSION Claude CLI version (default: $(CLAUDE_CODE_VERSION))"
Expand All @@ -329,6 +351,7 @@ help:
@echo ""
@echo "Examples:"
@echo " make build # Build all images with latest versions"
@echo " make build-core # Build stable core image only"
@echo " make build-main # Build main image only"
@echo " make build-rust # Build Rust image only"
@echo " make TAG=dev build # Build all with custom tag"
Expand Down
Loading
Loading