Skip to content

Commit cf16070

Browse files
committed
feat: add Claude --chrome support and refactor build stages
- Extract agent-base stage for stable core image layer - Add shared install-agent-tooling.sh for main and rust images - Mount Chrome profile Extensions/ dirs for extension detection - Setup MCP browser bridge symlink in container entrypoint - Add CI smoke tests for Chrome mount and bridge setup - Update custom-images and troubleshooting docs
1 parent 5d3298f commit cf16070

9 files changed

Lines changed: 552 additions & 44 deletions

File tree

.github/workflows/ci.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ jobs:
7272
--build-arg COPILOT_API_VERSION="${{ steps.versions.outputs.copilot_api_version }}" \
7373
.
7474
75+
- name: Build core and rust images via Makefile
76+
run: |
77+
make build-core build-rust-image \
78+
IMAGE_NAME=deva-smoke \
79+
TAG=ci \
80+
CORE_TAG=ci-core \
81+
RUST_TAG=ci-rust \
82+
CLAUDE_CODE_VERSION="${{ steps.versions.outputs.claude_code_version }}" \
83+
CODEX_VERSION="${{ steps.versions.outputs.codex_version }}" \
84+
GEMINI_CLI_VERSION="${{ steps.versions.outputs.gemini_cli_version }}" \
85+
ATLAS_CLI_VERSION="${{ steps.versions.outputs.atlas_cli_version }}" \
86+
COPILOT_API_VERSION="${{ steps.versions.outputs.copilot_api_version }}"
87+
7588
- name: Install and launch each agent without a TTY
7689
shell: bash
7790
run: |
@@ -90,6 +103,50 @@ jobs:
90103
deva.sh codex -Q -- --version
91104
deva.sh gemini -Q -- --version
92105
106+
- name: Smoke Claude --chrome mount assembly
107+
shell: bash
108+
run: |
109+
set -euo pipefail
110+
tmp_root="$(mktemp -d)"
111+
bridge_dir="$tmp_root/claude-mcp-browser-bridge-$(id -un)"
112+
profile_dir="$tmp_root/chrome/Default"
113+
114+
mkdir -p "$bridge_dir"
115+
chmod 700 "$bridge_dir"
116+
mkdir -p "$profile_dir/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn/1.0.0"
117+
118+
dry_run="$(
119+
DEVA_DOCKER_IMAGE=deva-smoke \
120+
DEVA_DOCKER_TAG=ci \
121+
DEVA_CHROME_PROFILE_PATH="$profile_dir" \
122+
DEVA_HOST_CHROME_BRIDGE_DIR="$bridge_dir" \
123+
./deva.sh claude --debug --dry-run -- --chrome 2>&1
124+
)"
125+
126+
printf '%s\n' "$dry_run"
127+
grep -F -- "$bridge_dir:/deva-host-chrome-bridge" <<<"$dry_run"
128+
grep -F -- "$profile_dir/Extensions:/home/deva/.config/google-chrome/Default/Extensions:ro" <<<"$dry_run"
129+
130+
- name: Smoke Chrome bridge entrypoint symlink
131+
shell: bash
132+
run: |
133+
set -euo pipefail
134+
tmp_root="$(mktemp -d)"
135+
bridge_dir="$tmp_root/claude-mcp-browser-bridge-$(id -un)"
136+
137+
mkdir -p "$bridge_dir"
138+
chmod 700 "$bridge_dir"
139+
140+
docker run --rm \
141+
-e DEVA_AGENT=claude \
142+
-e DEVA_UID="$(id -u)" \
143+
-e DEVA_GID="$(id -g)" \
144+
-e DEVA_CHROME_HOST_BRIDGE=1 \
145+
-e DEVA_CHROME_HOST_BRIDGE_DIR=/deva-host-chrome-bridge \
146+
-v "$bridge_dir:/deva-host-chrome-bridge" \
147+
deva-smoke:ci \
148+
bash -lc 'link="/tmp/claude-mcp-browser-bridge-$(id -un)"; test -L "$link"; test "$(readlink "$link")" = "/deva-host-chrome-bridge"'
149+
93150
docs:
94151
name: Docs Build
95152
runs-on: ubuntu-latest

Dockerfile

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,10 @@ ENV NPM_CONFIG_FETCH_RETRIES=5 \
165165
NPM_CONFIG_FETCH_RETRY_FACTOR=2 \
166166
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=10000
167167

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

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

213+
# Final image: install volatile agent packages on top of the stable base.
214+
FROM agent-base AS final
215+
211216
# Declare ARGs immediately before usage to minimize cache invalidation
212-
ARG CLAUDE_CODE_VERSION
213-
ARG CODEX_VERSION
214-
ARG GEMINI_CLI_VERSION=latest
217+
ARG CLAUDE_CODE_VERSION=2.1.81
218+
ARG CODEX_VERSION=0.116.0
219+
ARG GEMINI_CLI_VERSION=0.35.0
215220

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

221-
# Install CLI tools via npm
222-
RUN --mount=type=cache,target=/home/deva/.npm,uid=${DEVA_UID},gid=${DEVA_GID},sharing=locked \
223-
set -eux && \
224-
npm config set prefix "$DEVA_HOME/.npm-global" && \
225-
npm install -g --no-audit --no-fund \
226-
@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} \
227-
@mariozechner/claude-trace \
228-
@openai/codex@${CODEX_VERSION} \
229-
@google/gemini-cli@${GEMINI_CLI_VERSION} && \
230-
npm cache clean --force && \
231-
"$DEVA_HOME/.npm-global/bin/claude" --version && \
232-
"$DEVA_HOME/.npm-global/bin/codex" --version && \
233-
"$DEVA_HOME/.npm-global/bin/gemini" --version && \
234-
"$DEVA_HOME/.npm-global/bin/claude-trace" --help >/dev/null && \
235-
(npm list -g --depth=0 @anthropic-ai/claude-code @openai/codex @google/gemini-cli || true)
236-
237-
# Volatile packages: Install at the end to avoid cascading rebuilds
238-
ARG ATLAS_CLI_VERSION=main
226+
ARG ATLAS_CLI_VERSION=v0.1.4
239227

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

242-
# Install atlas-cli binary + skill via upstream install.sh
243-
# - Uses prebuilt release tarball (faster than go install)
244-
# - Falls back to go install if no prebuilt for platform
245-
# - Installs skill with proper structure (SKILL.md + references/)
246-
RUN curl -fsSL "https://raw.githubusercontent.com/lroolle/atlas-cli/${ATLAS_CLI_VERSION}/install.sh" \
247-
| bash -s -- --skill-dir $DEVA_HOME/.skills
230+
COPY --chown=deva:deva scripts/install-agent-tooling.sh /tmp/install-agent-tooling.sh
231+
232+
RUN --mount=type=cache,target=/home/deva/.npm,uid=${DEVA_UID},gid=${DEVA_GID},sharing=locked \
233+
bash /tmp/install-agent-tooling.sh && \
234+
rm -f /tmp/install-agent-tooling.sh
248235

249236
USER root
250237

Dockerfile.rust

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,26 @@ FROM ${BASE_IMAGE}
88
LABEL org.opencontainers.image.title="deva-rust"
99
LABEL org.opencontainers.image.description="Rust development environment with full toolchain"
1010

11+
ARG CLAUDE_CODE_VERSION=2.1.81
12+
ARG CODEX_VERSION=0.116.0
13+
ARG GEMINI_CLI_VERSION=0.35.0
14+
ARG ATLAS_CLI_VERSION=v0.1.4
1115
ARG RUST_TOOLCHAINS="stable"
1216
ARG RUST_TARGETS="wasm32-unknown-unknown"
1317

18+
LABEL org.opencontainers.image.claude_code_version=${CLAUDE_CODE_VERSION}
19+
LABEL org.opencontainers.image.codex_version=${CODEX_VERSION}
20+
LABEL org.opencontainers.image.gemini_cli_version=${GEMINI_CLI_VERSION}
21+
LABEL org.opencontainers.image.atlas_cli_version=${ATLAS_CLI_VERSION}
22+
1423
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
1524

1625
ENV RUSTUP_HOME=/opt/rustup \
1726
CARGO_HOME=/opt/cargo \
1827
PATH=/opt/cargo/bin:$PATH
1928

29+
USER root
30+
2031
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
2132
--mount=type=cache,target=/var/lib/apt,sharing=locked \
2233
apt-get update && \
@@ -67,3 +78,26 @@ RUN echo 'export PATH="/opt/cargo/bin:$PATH"' >> "$DEVA_HOME/.zshrc" && \
6778
echo 'alias cw="cargo watch -x check -x test -x run"' >> "$DEVA_HOME/.zshrc" && \
6879
echo 'alias cf="cargo fmt"' >> "$DEVA_HOME/.zshrc" && \
6980
echo 'alias cl="cargo clippy"' >> "$DEVA_HOME/.zshrc"
81+
82+
USER $DEVA_USER
83+
84+
COPY --chown=deva:deva scripts/install-agent-tooling.sh /tmp/install-agent-tooling.sh
85+
86+
RUN --mount=type=cache,target=/home/deva/.npm,uid=${DEVA_UID},gid=${DEVA_GID},sharing=locked \
87+
bash /tmp/install-agent-tooling.sh && \
88+
rm -f /tmp/install-agent-tooling.sh
89+
90+
USER root
91+
92+
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
93+
COPY scripts/deva-bridge-tmux /usr/local/bin/deva-bridge-tmux
94+
95+
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh && \
96+
chmod 755 /usr/local/bin/deva-bridge-tmux && \
97+
chmod -R 755 /usr/local/bin/scripts || true
98+
99+
WORKDIR /root
100+
101+
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
102+
103+
CMD ["claude"]

Makefile

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
IMAGE_NAME := ghcr.io/thevibeworks/deva
33
TAG := latest
44
RUST_TAG := rust
5+
CORE_TAG := core
56
DOCKERFILE := Dockerfile
67
RUST_DOCKERFILE := Dockerfile.rust
78
MAIN_IMAGE := $(IMAGE_NAME):$(TAG)
89
RUST_IMAGE := $(IMAGE_NAME):$(RUST_TAG)
10+
CORE_IMAGE := $(IMAGE_NAME):$(CORE_TAG)
911
CONTAINER_NAME := deva-$(shell basename $(PWD))-$(shell date +%s)
1012

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

2729
export DOCKER_BUILDKIT := 1
2830

@@ -79,17 +81,33 @@ rebuild:
7981
@echo "✅ Rebuild completed: $(MAIN_IMAGE)"
8082

8183

82-
.PHONY: build-rust
83-
build-rust:
84+
.PHONY: build-core
85+
build-core:
86+
@echo "🔨 Building stable core image..."
87+
docker build -f $(DOCKERFILE) --target agent-base --build-arg COPILOT_API_VERSION=$(COPILOT_API_VERSION) -t $(CORE_IMAGE) .
88+
@echo "✅ Core build completed: $(CORE_IMAGE)"
89+
90+
.PHONY: build-rust-image
91+
build-rust-image:
8492
@echo "🔨 Building Rust Docker image..."
85-
docker build -f $(RUST_DOCKERFILE) --build-arg BASE_IMAGE=$(MAIN_IMAGE) -t $(RUST_IMAGE) .
93+
docker build -f $(RUST_DOCKERFILE) \
94+
--build-arg BASE_IMAGE=$(CORE_IMAGE) \
95+
--build-arg CLAUDE_CODE_VERSION=$(CLAUDE_CODE_VERSION) \
96+
--build-arg CODEX_VERSION=$(CODEX_VERSION) \
97+
--build-arg GEMINI_CLI_VERSION=$(GEMINI_CLI_VERSION) \
98+
--build-arg ATLAS_CLI_VERSION=$(ATLAS_CLI_VERSION) \
99+
-t $(RUST_IMAGE) .
86100
@echo "✅ Rust build completed: $(RUST_IMAGE)"
87101

102+
.PHONY: build-rust
103+
build-rust: build-core build-rust-image
104+
88105
.PHONY: build-all
89106
build-all:
90107
@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)..."
108+
@$(MAKE) build-core COPILOT_API_VERSION=$(COPILOT_API_VERSION)
91109
@$(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)
92-
@$(MAKE) build-rust BASE_IMAGE=$(MAIN_IMAGE)
110+
@$(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)
93111
@echo "✅ All images built successfully"
94112

95113
.PHONY: buildx
@@ -160,6 +178,7 @@ clean:
160178
@echo "Removing project images..."
161179
-docker rmi $(MAIN_IMAGE) 2>/dev/null || true
162180
-docker rmi $(RUST_IMAGE) 2>/dev/null || true
181+
-docker rmi $(CORE_IMAGE) 2>/dev/null || true
163182
@echo "Pruning stopped containers..."
164183
-docker container prune -f
165184
@echo "Pruning unused images..."
@@ -179,6 +198,7 @@ clean-all:
179198
@echo "Removing project images..."
180199
-docker rmi $(MAIN_IMAGE) 2>/dev/null || true
181200
-docker rmi $(RUST_IMAGE) 2>/dev/null || true
201+
-docker rmi $(CORE_IMAGE) 2>/dev/null || true
182202
@echo "Removing ALL stopped containers..."
183203
-docker container prune -af
184204
@echo "Removing ALL dangling and unused images..."
@@ -297,6 +317,7 @@ help:
297317
@echo ""
298318
@echo "Available targets:"
299319
@echo " build Build all images (auto-detects latest npm versions)"
320+
@echo " build-core Build stable core image only"
300321
@echo " build-main Build main Docker image only"
301322
@echo " build-rust Build Rust Docker image"
302323
@echo " build-all Build all images (main + rust)"
@@ -320,6 +341,7 @@ help:
320341
@echo " IMAGE_NAME Main image name (default: $(IMAGE_NAME))"
321342
@echo " TAG Docker image tag (default: $(TAG))"
322343
@echo " RUST_TAG Rust image tag (default: $(RUST_TAG))"
344+
@echo " CORE_TAG Stable core image tag (default: $(CORE_TAG))"
323345
@echo " DOCKERFILE Dockerfile to use (default: $(DOCKERFILE))"
324346
@echo " RUST_DOCKERFILE Rust Dockerfile path (default: $(RUST_DOCKERFILE))"
325347
@echo " CLAUDE_CODE_VERSION Claude CLI version (default: $(CLAUDE_CODE_VERSION))"
@@ -329,6 +351,7 @@ help:
329351
@echo ""
330352
@echo "Examples:"
331353
@echo " make build # Build all images with latest versions"
354+
@echo " make build-core # Build stable core image only"
332355
@echo " make build-main # Build main image only"
333356
@echo " make build-rust # Build Rust image only"
334357
@echo " make TAG=dev build # Build all with custom tag"

0 commit comments

Comments
 (0)