diff --git a/.devcontainer/claude-sandbox/init-firewall.sh b/.devcontainer/claude-sandbox/init-firewall.sh index 6f98f4d..d1390b3 100755 --- a/.devcontainer/claude-sandbox/init-firewall.sh +++ b/.devcontainer/claude-sandbox/init-firewall.sh @@ -70,7 +70,9 @@ for domain in \ "auth.openai.com" \ "api.openai.com" \ "chatgpt.com" \ - "mcp.mdn.mozilla.net"; do + "mcp.mdn.mozilla.net" \ + "api.cloudflare.com" \ + "dash.cloudflare.com"; do echo "Resolving $domain..." ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}') if [ -z "$ips" ]; then diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 6864f17..4266b5b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -38,6 +38,7 @@ 'agent-browser', 'cli/cli', // gh — upstream .deb; apt's trixie build is frozen at 2.46.0 'github/gh-stack', // gh extension for native stacked PRs + 'cf', // Cloudflare CLI; in open beta, so npm `latest` is a prerelease 'docker/cli', // docker CLI static binary (download.docker.com). // github-tags, NOT github-releases: moby/moby tags its // releases 'docker-v29.8.0', which extractVersion cannot diff --git a/.github/workflows/build-claude-code.yml b/.github/workflows/build-claude-code.yml index ac9b42e..8df8565 100644 --- a/.github/workflows/build-claude-code.yml +++ b/.github/workflows/build-claude-code.yml @@ -80,19 +80,19 @@ jobs: matrix: include: - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" + verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && cf --version && cf cli search 'list zones' | jq -e '.[0].command' >/dev/null && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && agent-browser --version && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && test -r /etc/claude-code/.claude/skills/devcontainer-browser/SKILL.md && jq -e '.permissions.deny | index(\"Bash(agent-browser install *)\")' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_CONFIG | grep -qx /etc/agent-browser/config.json && jq -e .contentBoundaries /etc/agent-browser/config.json >/dev/null && jq -r '.claudeMd' /etc/claude-code/managed-settings.json | grep -q devcontainer-browser && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && zsh -ic '(( $+_comps[gh] && $+_comps[cf] ))' && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node && jq -r .claudeMd /etc/claude-code/managed-settings.json | grep -q cloudflare.config.ts && jq -r '.permissions.ask[]' /etc/claude-code/managed-settings.json | grep -qxF 'Bash(cf * --force*)' && printenv CF_SEND_TELEMETRY | grep -qx false && printenv WRANGLER_SEND_METRICS | grep -qx false && stat -c %U /home/node/.config/cloudflare | grep -qx node" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" + verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && cf --version && cf cli search 'list zones' | jq -e '.[0].command' >/dev/null && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && agent-browser --version && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && test -r /etc/claude-code/.claude/skills/devcontainer-browser/SKILL.md && jq -e '.permissions.deny | index(\"Bash(agent-browser install *)\")' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_CONFIG | grep -qx /etc/agent-browser/config.json && jq -e .contentBoundaries /etc/agent-browser/config.json >/dev/null && jq -r '.claudeMd' /etc/claude-code/managed-settings.json | grep -q devcontainer-browser && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && zsh -ic '(( $+_comps[gh] && $+_comps[cf] ))' && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node && jq -r .claudeMd /etc/claude-code/managed-settings.json | grep -q cloudflare.config.ts && jq -r '.permissions.ask[]' /etc/claude-code/managed-settings.json | grep -qxF 'Bash(cf * --force*)' && printenv CF_SEND_TELEMETRY | grep -qx false && printenv WRANGLER_SEND_METRICS | grep -qx false && stat -c %U /home/node/.config/cloudflare | grep -qx node" runner: ubuntu-24.04-arm arch: arm64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" + verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && cf --version && cf cli search 'list zones' | jq -e '.[0].command' >/dev/null && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && agent-browser --version && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && test -r /etc/claude-code/.claude/skills/devcontainer-browser/SKILL.md && jq -e '.permissions.deny | index(\"Bash(agent-browser install *)\")' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_CONFIG | grep -qx /etc/agent-browser/config.json && jq -e .contentBoundaries /etc/agent-browser/config.json >/dev/null && jq -r '.claudeMd' /etc/claude-code/managed-settings.json | grep -q devcontainer-browser && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && zsh -ic '(( $+_comps[gh] && $+_comps[cf] ))' && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node && jq -r .claudeMd /etc/claude-code/managed-settings.json | grep -q cloudflare.config.ts && jq -r '.permissions.ask[]' /etc/claude-code/managed-settings.json | grep -qxF 'Bash(cf * --force*)' && printenv CF_SEND_TELEMETRY | grep -qx false && printenv WRANGLER_SEND_METRICS | grep -qx false && stat -c %U /home/node/.config/cloudflare | grep -qx node" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" + verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && cf --version && cf cli search 'list zones' | jq -e '.[0].command' >/dev/null && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && agent-browser --version && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && test -r /etc/claude-code/.claude/skills/devcontainer-browser/SKILL.md && jq -e '.permissions.deny | index(\"Bash(agent-browser install *)\")' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_CONFIG | grep -qx /etc/agent-browser/config.json && jq -e .contentBoundaries /etc/agent-browser/config.json >/dev/null && jq -r '.claudeMd' /etc/claude-code/managed-settings.json | grep -q devcontainer-browser && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && zsh -ic '(( $+_comps[gh] && $+_comps[cf] ))' && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node && jq -r .claudeMd /etc/claude-code/managed-settings.json | grep -q cloudflare.config.ts && jq -r '.permissions.ask[]' /etc/claude-code/managed-settings.json | grep -qxF 'Bash(cf * --force*)' && printenv CF_SEND_TELEMETRY | grep -qx false && printenv WRANGLER_SEND_METRICS | grep -qx false && stat -c %U /home/node/.config/cloudflare | grep -qx node" runner: ubuntu-24.04-arm arch: arm64 runs-on: ${{ matrix.runner }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67964e6..7b26fe1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,12 +117,12 @@ jobs: add_image "claude-code" \ "claude-code/.devcontainer" \ "claude-code/.devcontainer/Dockerfile" \ - "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && agent-browser --version && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && test -r /etc/claude-code/.claude/skills/devcontainer-browser/SKILL.md && jq -e '.permissions.deny | index(\"Bash(agent-browser install *)\")' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_CONFIG | grep -qx /etc/agent-browser/config.json && jq -e .contentBoundaries /etc/agent-browser/config.json >/dev/null && jq -r '.claudeMd' /etc/claude-code/managed-settings.json | grep -q devcontainer-browser && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \ + "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && cf --version && cf cli search 'list zones' | jq -e '.[0].command' >/dev/null && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && agent-browser --version && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && test -r /etc/claude-code/.claude/skills/devcontainer-browser/SKILL.md && jq -e '.permissions.deny | index(\"Bash(agent-browser install *)\")' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_CONFIG | grep -qx /etc/agent-browser/config.json && jq -e .contentBoundaries /etc/agent-browser/config.json >/dev/null && jq -r '.claudeMd' /etc/claude-code/managed-settings.json | grep -q devcontainer-browser && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && zsh -ic '(( $+_comps[gh] && $+_comps[cf] ))' && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node && jq -r .claudeMd /etc/claude-code/managed-settings.json | grep -q cloudflare.config.ts && jq -r '.permissions.ask[]' /etc/claude-code/managed-settings.json | grep -qxF 'Bash(cf * --force*)' && printenv CF_SEND_TELEMETRY | grep -qx false && printenv WRANGLER_SEND_METRICS | grep -qx false && stat -c %U /home/node/.config/cloudflare | grep -qx node" \ "default" add_image "claude-code-sandbox" \ "claude-code/.devcontainer" \ "claude-code/.devcontainer/Dockerfile" \ - "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && agent-browser --version && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && test -r /etc/claude-code/.claude/skills/devcontainer-browser/SKILL.md && jq -e '.permissions.deny | index(\"Bash(agent-browser install *)\")' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_CONFIG | grep -qx /etc/agent-browser/config.json && jq -e .contentBoundaries /etc/agent-browser/config.json >/dev/null && jq -r '.claudeMd' /etc/claude-code/managed-settings.json | grep -q devcontainer-browser && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \ + "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && cf --version && cf cli search 'list zones' | jq -e '.[0].command' >/dev/null && python3 -c 'import ensurepip' && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && agent-browser --version && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && test -r /etc/claude-code/.claude/skills/devcontainer-browser/SKILL.md && jq -e '.permissions.deny | index(\"Bash(agent-browser install *)\")' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_CONFIG | grep -qx /etc/agent-browser/config.json && jq -e .contentBoundaries /etc/agent-browser/config.json >/dev/null && jq -r '.claudeMd' /etc/claude-code/managed-settings.json | grep -q devcontainer-browser && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && zsh -ic '(( $+_comps[gh] && $+_comps[cf] ))' && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node && jq -r .claudeMd /etc/claude-code/managed-settings.json | grep -q cloudflare.config.ts && jq -r '.permissions.ask[]' /etc/claude-code/managed-settings.json | grep -qxF 'Bash(cf * --force*)' && printenv CF_SEND_TELEMETRY | grep -qx false && printenv WRANGLER_SEND_METRICS | grep -qx false && stat -c %U /home/node/.config/cloudflare | grep -qx node" \ "sandbox" fi diff --git a/claude-code/.devcontainer/Dockerfile b/claude-code/.devcontainer/Dockerfile index 9ad5fc1..196a5fa 100644 --- a/claude-code/.devcontainer/Dockerfile +++ b/claude-code/.devcontainer/Dockerfile @@ -124,6 +124,7 @@ RUN mkdir -p /workspace/node_modules \ /home/node/.local/share \ /home/node/.local/state \ /home/node/.config/gh \ + /home/node/.config/cloudflare \ /home/node/.cache \ /commandhistory \ && chown -R node:node /workspace /home/node/.claude /home/node/.local \ @@ -167,13 +168,14 @@ USER node COPY --from=ohmyzsh-download --chown=node:node /omz /home/node/.oh-my-zsh # .zshrc written here, not by an installer, so nothing upstream injects env -# overrides. The wizard flag stops powerlevel10k prompting on first shell. +# overrides. The wizard flag stops powerlevel10k prompting on first shell. The gh +# plugin regenerates gh's completion on each shell start, so it never goes stale. # Single-quoted so $HOME/$ZSH reach .zshrc unexpanded. # hadolint ignore=SC2016 RUN printf '%s\n' \ 'export ZSH="$HOME/.oh-my-zsh"' \ 'ZSH_THEME="powerlevel10k/powerlevel10k"' \ - 'plugins=(git fzf)' \ + 'plugins=(git fzf gh)' \ "zstyle ':omz:update' mode disabled" \ 'POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD=true' \ 'export HISTFILE=/commandhistory/.zsh_history' \ @@ -248,6 +250,20 @@ ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium \ AGENT_BROWSER_CONFIG=/etc/agent-browser/config.json +# ── cf (Cloudflare CLI) — before Claude Code so its bumps don't reinstall cf ── +# Needs Node >= 22.18 (Bun can't load cloudflare.config.ts). No install scripts, and the +# bundled workerd (133 MB) goes: `cf dev`/--local run the project's own cf copy. +# Completion is installed the documented way; it queries cf, so upgrades don't stale it. +# renovate: datasource=npm depName=cf +ARG CF_VERSION=1.0.0-beta.13 +RUN --mount=type=cache,target=/tmp/npm-cache,uid=1000,gid=1000 \ + npm install -g --cache /tmp/npm-cache --ignore-scripts cf@${CF_VERSION} \ + && rm -rf /usr/local/share/npm-global/lib/node_modules/cf/node_modules/@cloudflare/workerd-linux-* \ + && cf complete zsh >> "$HOME/.zshrc" +# No telemetry from cf, nor from the Wrangler it delegates esbuild builds to. +ENV CF_SEND_TELEMETRY=false \ + WRANGLER_SEND_METRICS=false + # ── Claude Code CLI — after Chromium: it changes most often (see global ARG) ─ # npm, not the native installer: the installer rate-limits (429) under parallel # Docker builds. npm stays supported "for compatibility reasons" — this is that @@ -278,8 +294,8 @@ RUN --mount=type=cache,target=/tmp/npm-cache,uid=1000,gid=1000 \ # Image-policy settings at the Linux managed location (highest precedence, outside # any volume mount). Wires patch-playwright-mcp as a SessionStart hook (#98, #101), # provides the MDN MCP server (managedMcpServers needs Claude Code >= 2.1.259), and -# routes browser work to the devcontainer-browser skill (claudeMd), and denies -# agent-browser install/upgrade. +# routes browser work to the devcontainer-browser skill and Cloudflare work to cf +# (claudeMd), denies agent-browser install/upgrade, and asks before `cf ... --force`. # # The skill ships at the managed skills location, so every project gets it without a # copy in its own .claude/skills/, and it wins a name clash with a project skill. diff --git a/claude-code/.devcontainer/claude-sandbox/devcontainer.json b/claude-code/.devcontainer/claude-sandbox/devcontainer.json index cf03d1e..98935e9 100644 --- a/claude-code/.devcontainer/claude-sandbox/devcontainer.json +++ b/claude-code/.devcontainer/claude-sandbox/devcontainer.json @@ -115,6 +115,8 @@ "source=myproject-zsh-history-${localWorkspaceFolderBasename},target=/commandhistory,type=volume", // gh CLI auth/state (~/.config/gh/hosts.yml) — keeps `gh auth login` across rebuilds "source=myproject-gh-config-${localWorkspaceFolderBasename},target=/home/node/.config/gh,type=volume", + // cf CLI login (~/.config/cloudflare) — keeps `cf auth login` across rebuilds + "source=myproject-cloudflare-config-${localWorkspaceFolderBasename},target=/home/node/.config/cloudflare,type=volume", // ── Firewall script ──────────────────────────────────────────────── // The image provides iptables/ipset packages and sudo rule but NOT the script itself. // Each project provides its own script via bind mount to customize the domain allowlist. @@ -146,7 +148,7 @@ // Chromium is baked into the sandbox image (firewall blocks runtime install). // bun install and init-plugins.sh are skipped when their file is absent. Plugins // install here, before postStartCommand brings the firewall up. - "postCreateCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude /commandhistory /home/node/.config/gh && mise install && if [ -f package.json ]; then bun install; fi && if [ -f .devcontainer/init-plugins.sh ]; then bash .devcontainer/init-plugins.sh; fi", + "postCreateCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude /commandhistory /home/node/.config/gh /home/node/.config/cloudflare && mise install && if [ -f package.json ]; then bun install; fi && if [ -f .devcontainer/init-plugins.sh ]; then bash .devcontainer/init-plugins.sh; fi", // Firewall init (bind-mounted from project) + re-patch the Playwright MCP // plugin's .mcp.json. The patch is defense in depth alongside the SessionStart // hook in /etc/claude-code/managed-settings.json, which handles the case where diff --git a/claude-code/.devcontainer/devcontainer.json b/claude-code/.devcontainer/devcontainer.json index 44590ba..7fc70de 100644 --- a/claude-code/.devcontainer/devcontainer.json +++ b/claude-code/.devcontainer/devcontainer.json @@ -105,7 +105,9 @@ "source=myproject-claude-config-${localWorkspaceFolderBasename},target=/home/node/.claude,type=volume", "source=myproject-zsh-history-${localWorkspaceFolderBasename},target=/commandhistory,type=volume", // gh CLI auth/state (~/.config/gh/hosts.yml) — keeps `gh auth login` across rebuilds - "source=myproject-gh-config-${localWorkspaceFolderBasename},target=/home/node/.config/gh,type=volume" + "source=myproject-gh-config-${localWorkspaceFolderBasename},target=/home/node/.config/gh,type=volume", + // cf CLI login (~/.config/cloudflare) — keeps `cf auth login` across rebuilds + "source=myproject-cloudflare-config-${localWorkspaceFolderBasename},target=/home/node/.config/cloudflare,type=volume" ], "containerEnv": { "TZ": "${localEnv:TZ:America/Edmonton}", @@ -131,7 +133,7 @@ // bun install is skipped until the project has a package.json. // Chromium is baked into the image via apt — no playwright install step needed. // Projects' playwright.config.ts should use process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH. - "updateContentCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude /commandhistory /home/node/.config/gh && mise install && if [ -f package.json ]; then bun install; fi", + "updateContentCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude /commandhistory /home/node/.config/gh /home/node/.config/cloudflare && mise install && if [ -f package.json ]; then bun install; fi", // Re-patch the Playwright MCP plugin's .mcp.json on every start. Defense in // depth alongside the SessionStart hook in /etc/claude-code/managed-settings.json, // which handles the case where the plugin auto-updates mid-container-run. diff --git a/claude-code/.devcontainer/managed-settings.json b/claude-code/.devcontainer/managed-settings.json index 7937466..f02c52f 100644 --- a/claude-code/.devcontainer/managed-settings.json +++ b/claude-code/.devcontainer/managed-settings.json @@ -1,6 +1,10 @@ { - "claudeMd": "Browser work in this devcontainer (opening pages, UI checks, screenshots, browser measurements, browser tests): follow the `devcontainer-browser` skill. agent-browser is the default; use Playwright only for the project's committed test suites, Firefox/WebKit, or when agent-browser is missing. Never install or download a browser: Chromium is preinstalled at /usr/bin/chromium.", + "claudeMd": "Browser work in this devcontainer (opening pages, UI checks, screenshots, browser measurements, browser tests): follow the `devcontainer-browser` skill. agent-browser is the default; use Playwright only for the project's committed test suites, Firefox/WebKit, or when agent-browser is missing. Never install or download a browser: Chromium is preinstalled at /usr/bin/chromium.\n\nCloudflare: use the `cf` CLI unless the project has a Wrangler config (`wrangler.jsonc`, `wrangler.json` or `wrangler.toml`) and no `cloudflare.config.ts`. There, use Wrangler and never run `cf dev`, `cf build` or `cf deploy`: without a terminal they rewrite `package.json`, the lockfile and `vite.config.ts` without asking. Migrate first with `cf migrate --dry-run`. Find commands with `cf cli search \"\"`.", "permissions": { + "ask": [ + "Bash(cf * --force*)", + "Bash(cf * -f*)" + ], "deny": [ "Bash(agent-browser install *)", "Bash(agent-browser upgrade *)" diff --git a/claude-code/README.md b/claude-code/README.md index 827e40e..c374bac 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -16,10 +16,11 @@ Projects consume these pre-built images and control their own tool versions via | Layer | What | Why | |-------|------|-----| | OS | `node:24-trixie-slim` + system packages | Node is needed during the build (Playwright, npm globals) | -| Shell | zsh, oh-my-zsh (`git`, `fzf` plugins), powerlevel10k | Completions, git aliases and prompt integration | +| Shell | zsh, oh-my-zsh (`git`, `fzf`, `gh` plugins), powerlevel10k, `cf` completion | Completions, git aliases and prompt integration | | Tools | gh CLI, git, curl, jq, less, fzf, procps, openssh-client, python3 (+ venv) | Standard dev utilities (`openssh-client` provides `ssh`/`ssh-keygen` — enables SSH-format commit signing; `python3` runs the `security-guidance` and `claude-security` plugins) | | Mise | The tool manager itself (not the tools) | Projects run `mise install` at container creation for their tool versions | | gh-stack | `gh` extension, pinned `ARG` bumped by Renovate | Native stacked PRs (`gh stack`). Baked in because `~/.local/share/gh` is not a volume, so a runtime `gh extension install` is lost on rebuild | +| cf | npm global install, pinned `ARG` bumped by Renovate | Cloudflare CLI for the whole Cloudflare API. See [Cloudflare CLI](#cloudflare-cli-cf) | | rtk, ralphex | Pinned `ARG`s, bumped by Renovate on each GitHub release | Dev infrastructure (like Claude Code) — the image tracks the versions so projects don't have to | | Claude Code | npm global install | npm avoids rate limiting that affects the native installer in parallel CI builds | @@ -33,6 +34,23 @@ The image provides Mozilla's [MDN MCP server](https://developer.mozilla.org/en-U Requires Claude Code >= 2.1.259; earlier clients ignore the key. `claude mcp remove` refuses it, but each developer can turn it off for themselves in `/mcp` under **Managed MCPs**. Sandbox users must allowlist `mcp.mdn.mozilla.net` in their firewall script. +### Cloudflare CLI (`cf`) + +Both targets ship Cloudflare's [`cf`](https://developers.cloudflare.com/cf/) CLI, the open-beta successor to Wrangler. It covers the whole Cloudflare API and prints JSON. The `claudeMd` key in `/etc/claude-code/managed-settings.json` tells every Claude Code session to use it, except in projects that have a Wrangler config but no `cloudflare.config.ts`: there, `cf dev`, `cf build` and `cf deploy` would rewrite project files without asking. A managed `ask` rule makes Claude Code ask before any `cf` command with `--force` or `-f`, the flag `cf` requires for deletes when there's no terminal. Bypass mode skips the prompt, as it skips every prompt. The image turns telemetry off (`CF_SEND_TELEMETRY=false`, `WRANGLER_SEND_METRICS=false`). + +Sign in once per project. The template's `myproject-cloudflare-config-*` volume keeps the login (`~/.config/cloudflare`) across rebuilds, and both variants share it: + +```bash +cf auth login --no-browser # approve the printed link and code in your host browser +cf auth whoami +``` + +The sandbox shares that login, and Claude Code there may run with permission prompts skipped. For agents there, or in CI, prefer an [API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) limited to what the project needs: set `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`, and the token takes priority over the stored login. Sandbox users must allowlist `api.cloudflare.com` (API calls) and `dash.cloudflare.com` (sign-in). + +The image deletes `cf`'s bundled `workerd` runtime (133 MB), so `cf dev` and `--local` commands work only in a project that has `cf` as a dev dependency (`cf init` and `cf migrate` add it). The global `cf` then runs the project's copy, which has its own runtime. Commands that call the Cloudflare API don't need it. + +The `cloudflare` plugin (skills plus the Cloudflare MCP server) stays installed alongside `cf`: its MCP server also searches current Cloudflare docs, which `cf` doesn't. + ## Multi-platform Support Both variants are built for: @@ -47,7 +65,7 @@ Both variants are built for: ## Automatic Rebuilds -The image rebuilds automatically whenever one of its pinned tools — Claude Code, agent-browser, gh, gh-stack, rtk, or ralphex — publishes a new release: Renovate opens a version-bump PR, CI verifies it, it auto-merges, and the merge builds the image on native runners for both amd64 and arm64 (no QEMU emulation). Manual rebuilds can be triggered via the "Run workflow" button in the Actions UI. +The image rebuilds automatically whenever one of its pinned tools — Claude Code, agent-browser, cf, gh, gh-stack, rtk, or ralphex — publishes a new release: Renovate opens a version-bump PR, CI verifies it, it auto-merges, and the merge builds the image on native runners for both amd64 and arm64 (no QEMU emulation). Manual rebuilds can be triggered via the "Run workflow" button in the Actions UI. ## Quick Start @@ -62,7 +80,7 @@ Copy these to your project's `.devcontainer/`: - [`.devcontainer/docker-compose.yml`](.devcontainer/docker-compose.yml) — image reference (kept fresh by the `initializeCommand` pull in `devcontainer.json`) - [`.devcontainer/devcontainer.json`](.devcontainer/devcontainer.json) — full config with VS Code extensions, zsh shell, OXC formatter, node_modules volume isolation, and lifecycle commands -**Key settings included:** zsh + bash terminal profiles, OXC formatter (with comments for switching to Biome/Prettier), node_modules/Claude config/zsh history/gh CLI config volume mounts, env-based git config (see [Git and GitHub Authentication](#git-and-github-authentication)), and `updateContentCommand` for mise/bun setup (`bun install` is skipped until the project has a `package.json`). There is deliberately no `postCreateCommand`: see [`init-plugins.sh`](#optional-devcontainerinit-pluginssh). +**Key settings included:** zsh + bash terminal profiles, OXC formatter (with comments for switching to Biome/Prettier), node_modules/Claude config/zsh history/gh CLI config/cf login volume mounts, env-based git config (see [Git and GitHub Authentication](#git-and-github-authentication)), and `updateContentCommand` for mise/bun setup (`bun install` is skipped until the project has a `package.json`). There is deliberately no `postCreateCommand`: see [`init-plugins.sh`](#optional-devcontainerinit-pluginssh). ### Sandbox variant @@ -73,7 +91,7 @@ Copy these to your project's `.devcontainer/claude-sandbox/`: **Sandbox differences from default:** `capAdd` for iptables, setup (including `init-plugins.sh`) runs in `postCreateCommand` before `postStartCommand` brings up the firewall, `claudeCode.allowDangerouslySkipPermissions` enabled, and an optional host-injected OAuth token for standalone use (see [Sandbox Authentication](#sandbox-authentication)). -**Shared volumes:** Both variants use `${localWorkspaceFolderBasename}` in volume names, so they share node_modules, Claude config, zsh history, and gh CLI auth (`~/.config/gh`). Install packages in one variant and both benefit. Docker named volumes support multi-container access, so both can run simultaneously — just avoid running `bun install` in both at the same time. +**Shared volumes:** Both variants use `${localWorkspaceFolderBasename}` in volume names, so they share node_modules, Claude config, zsh history, gh CLI auth (`~/.config/gh`), and the `cf` login (`~/.config/cloudflare`). Install packages in one variant and both benefit. Docker named volumes support multi-container access, so both can run simultaneously — just avoid running `bun install` in both at the same time. ## Project Setup Guide @@ -135,7 +153,7 @@ To remove a plugin in your project, delete its entry from the local `init-plugin Default-deny iptables firewall. The image provides the packages and sudo rule; the project provides this script via bind mount. Customize the domain allowlist for your project. -See the [repo's own sandbox firewall script](../.devcontainer/claude-sandbox/init-firewall.sh) for a complete example. The script should: preserve Docker internal DNS rules, allow DNS/SSH/localhost, fetch GitHub IP ranges via `curl -s https://api.github.com/meta`, resolve additional allowed domains (npm, Anthropic API, VS Code marketplace, `mcp.mdn.mozilla.net` for the MDN MCP server, etc.) via `dig`, set default DROP policies, allow established connections and the ipset allowlist, then verify by confirming `example.com` is blocked and `api.github.com` is reachable. +See the [repo's own sandbox firewall script](../.devcontainer/claude-sandbox/init-firewall.sh) for a complete example. The script should: preserve Docker internal DNS rules, allow DNS/SSH/localhost, fetch GitHub IP ranges via `curl -s https://api.github.com/meta`, resolve additional allowed domains (npm, Anthropic API, VS Code marketplace, `mcp.mdn.mozilla.net` for the MDN MCP server, `api.cloudflare.com` and `dash.cloudflare.com` for `cf`, etc.) via `dig`, set default DROP policies, allow established connections and the ipset allowlist, then verify by confirming `example.com` is blocked and `api.github.com` is reachable. Mark as executable and ensure git tracks the executable bit: @@ -178,6 +196,17 @@ Both image variants bake in the official [`github/gh-stack`](https://github.com/ Copy `.claude/skills/stacked-prs/` into your project's `.claude/skills/` directory so Claude Code picks it up automatically. +### Recommended: migrate Workers projects to `cloudflare.config.ts` + +`cloudflare.config.ts` is `cf`'s typed replacement for `wrangler.jsonc`, so agents and the TypeScript language server can check bindings and routes. Migration is per project, so the image can't do it for you. Wrangler gets 18 months of maintenance once the `cf` beta ends, so start now: + +```bash +cf migrate --dry-run # preview +cf migrate # writes cloudflare.config.ts, adds cf as a devDependency +``` + +Then resolve the `TODO(@cloudflare)` comments it leaves: Durable Object migrations, Workflows, Containers and package scripts need a manual pass, and the build fails until they're done. Keep `wrangler.jsonc` while you still need Wrangler-only commands (`wrangler tail`, setting a single secret). The two tools don't read each other's config. `cf` needs Node, so don't run it with `bun --bun`. + ### Recommended: Claude Code skill for upstream sync To keep your project's `.devcontainer/` and bundled `.claude/skills/` in step with this repo, copy the [devcontainer-upstream-sync](.claude/skills/devcontainer-upstream-sync/SKILL.md) skill into your project's `.claude/skills/` directory. The skill audits drift, helps adopt missed changes, drafts upstream issues for shared bugs, and self-updates when this skill's `version:` bumps. @@ -464,10 +493,11 @@ more often than right. | `AGENT_BROWSER_VERSION` | Renovate | agent-browser | | `GH_VERSION` | Renovate | GitHub CLI — from the upstream `.deb`, not apt (trixie freezes gh at 2.46.0) | | `GH_STACK_VERSION` | Renovate | gh-stack extension (`gh stack`) | +| `CF_VERSION` | Renovate | Cloudflare CLI (`cf`), open beta; releases wait 3 days before auto-merge | | `OH_MY_ZSH_REF` | by hand | oh-my-zsh, pinned to a commit SHA | | `POWERLEVEL10K_REF` | by hand | powerlevel10k, pinned to a commit SHA | -The six Renovate-managed args carry `# renovate:` annotations in the Dockerfile; edit them by +The seven Renovate-managed args carry `# renovate:` annotations in the Dockerfile; edit them by hand only for a local build. Bumps land as auto-merged PRs — see [Automatic Rebuilds](#automatic-rebuilds). ## Building Locally / Local Fallback diff --git a/hugo-bun-node/README.md b/hugo-bun-node/README.md index c242794..1230c61 100644 --- a/hugo-bun-node/README.md +++ b/hugo-bun-node/README.md @@ -85,9 +85,11 @@ This image uses Node.js unofficial builds for Alpine Linux (musl libc). The bina ### Cloudflare Workers This image includes Node.js specifically for Cloudflare Workers development, which requires a Node.js environment for: -- Running `wrangler` CLI +- Running the `cf` CLI (Node 22.18 or later; it can't load `cloudflare.config.ts` under Bun) or `wrangler` - Building and deploying Workers -- Local development with `wrangler dev` +- Local development with `cf dev` or `wrangler dev` + +`cf` is Cloudflare's successor to Wrangler (open beta). A project moves to it by running `cf migrate`, which writes a typed `cloudflare.config.ts` next to `wrangler.jsonc`. See [Recommended: migrate Workers projects to `cloudflare.config.ts`](../claude-code/README.md#recommended-migrate-workers-projects-to-cloudflareconfigts) for the steps and caveats. ### GitHub Actions Integration @@ -147,12 +149,17 @@ node --version # Check npm version npm --version -# Install Cloudflare Wrangler -npm install -g wrangler +# Install the Cloudflare CLI (it runs on Node, never Bun) +npm install -g cf +cf auth login --no-browser + +# Projects with cloudflare.config.ts +cf dev +cf deploy -# Run Wrangler commands -wrangler dev -wrangler deploy +# Projects still on wrangler.jsonc / wrangler.toml +npx wrangler dev +npx wrangler deploy ``` ## Build Args