feat: rename to ddev-claude-cli, drop Codex CLI, add opt-in headless browser

This commit is contained in:
Björn Fromme committed 2026-11-08 12:13:11 +01:00
1 parent b6d88ce4dc
commit d8076fa5a0
8 files changed
+322 -64

No files matched your search

+98 -44
View File
@@ -1,45 +1,30 @@
# ddev-codex-claude-cli
# ddev-claude-cli
`ddev-codex-claude-cli` is a DDEV add-on that runs Codex CLI and Claude Code CLI inside the DDEV `web` container while keeping runtime state under the project's `.ddev` directory on the host (bind-mounted into the container).
`ddev-claude-cli` is a DDEV add-on that runs Claude Code CLI inside the DDEV `web` container while keeping runtime state under the project's `.ddev` directory on the host (bind-mounted into the container).
> Formerly `ddev-codex-claude-cli`. See [Upgrading from ddev-codex-claude-cli](#upgrading-from-ddev-codex-claude-cli).
## What it does
- exposes `ddev codex`
- exposes `ddev claude`
- optionally exposes `ddev claude-swap` for juggling multiple Claude accounts (opt-in; see [Multiple Claude accounts](#multiple-claude-accounts))
- optionally runs a headless Chromium as a sidecar container and wires it into Claude Code as the `playwright` MCP server (opt-in; see [Headless browser](#headless-browser))
- optionally keeps read-only mirrors of sibling repos under `.ddev/claude-code/home/refs/` for Claude to grep (opt-in; see [Sibling repo mirrors](#sibling-repo-mirrors))
- installs Codex CLI lazily the first time you run `ddev codex` (or whenever the persisted binary is missing) by downloading the latest release from GitHub: `curl -fsSL https://github.com/openai/codex/releases/latest/download/install.sh | sh`
- installs Claude Code CLI during `ddev start` when the persisted binary is missing or broken by downloading the stable native build: `curl -fsSL https://claude.ai/install.sh | bash -s stable`
- keeps normal `ddev start` fast by skipping Claude Code installation when the persisted binary is already runnable
- persists each CLI's binary, HOME, and XDG paths under **project-local** storage so each DDEV project has its own profiles
- persists the CLI's binary, HOME, and XDG paths under **project-local** storage so each DDEV project has its own profile
- seeds the project-local HOME from the container home on first bootstrap for `.gitignore_global` when present
- keeps its own artifacts out of the project's git via a managed block in `.git/info/exclude`, refreshed on every `ddev start` and `ddev claude` (see [Keeping add-on files out of git](#keeping-add-on-files-out-of-git))
- always runs Codex with `--sandbox danger-full-access --ask-for-approval untrusted`
## Runtime storage
Inside the web container, Codex CLI state lives under:
${DDEV_APPROOT}/.ddev/codex-cli
Claude Code CLI state lives under:
Inside the web container, Claude Code CLI state lives under:
${DDEV_APPROOT}/.ddev/claude-code
On the host, those are your project's **`.ddev/codex-cli/`** and **`.ddev/claude-code/`** directories. They survive `ddev restart`, `ddev stop`, and `ddev poweroff` like any other project file. The same directories hold the add-on's **install scripts** (managed files) alongside runtime data created after the first start.
On the host, that is your project's **`.ddev/claude-code/`** directory. It survives `ddev restart`, `ddev stop`, and `ddev poweroff` like any other project file. The same directory holds the add-on's **install scripts** (managed files) alongside runtime data created after the first start.
Codex layout:
| Path | Role |
|------|------|
| `.ddev/codex-cli/install.sh` | Codex install script |
| `.ddev/codex-cli/bin/codex` | Codex CLI binary |
| `.ddev/codex-cli/home` | `HOME` |
| `.ddev/codex-cli/config` | `XDG_CONFIG_HOME` |
| `.ddev/codex-cli/data` | `XDG_DATA_HOME` |
| `.ddev/codex-cli/state` | `XDG_STATE_HOME` |
Claude Code layout:
Layout:
| Path | Role |
|------|------|
@@ -56,7 +41,7 @@ Authentication and settings are **per project**: sign in once per project; they
From a DDEV project root:
ddev add-on get /path/to/ddev-codex-claude-cli
ddev add-on get /path/to/ddev-claude-cli
Then restart:
@@ -64,10 +49,6 @@ Then restart:
## Usage
ddev codex
ddev codex --version
ddev claude
ddev claude --version
@@ -78,9 +59,9 @@ Then restart:
ddev claude-refs # opt-in; see "Sibling repo mirrors"
## Updates
ddev claude-browser enable # opt-in; see "Headless browser"
Codex CLI is installed lazily when `ddev codex` runs and the persisted binary is missing or broken.
## Updates
Claude Code CLI is installed automatically on `ddev start` only when the persisted binary is missing or broken. Later starts run a fast health check and skip installation when Claude is already runnable.
@@ -155,6 +136,57 @@ The first install needs outbound network access to `astral.sh`, `pypi.org`,
`claude-swap` captures whichever account is signed in at `cswap add` time, so the
initial multi-account setup is a few manual `/login` round-trips.
## Headless browser
The add-on can run a headless Chromium next to the web container so Claude can open the
site, click through it, fill in forms, read the console and network log, and take
screenshots. It uses Microsoft's [Playwright MCP](https://github.com/microsoft/playwright-mcp)
server image (`mcr.microsoft.com/playwright/mcp`) and needs no other add-on. It is **opt-in
per project**, because the extra container costs a few hundred MB of memory while the
project runs.
ddev claude-browser enable # copies the compose file into .ddev/ and restarts
ddev claude-browser status
ddev claude-browser disable # removes it again and restarts
What enabling does:
- copies `.ddev/claude-code/browser/docker-compose.claude-browser.yaml` (the template) to
`.ddev/docker-compose.claude-browser.yaml`, which adds a `claude-browser` service. The
first start builds a small layer on top of the upstream image so the browser runs as
your user; later starts reuse it.
- on that restart, and on every `ddev claude`, `install.sh` registers the service with
Claude Code at user scope as the MCP server `playwright`
(`http://claude-browser:8931/mcp`). After `disable`, the next start removes that
registration again, but only if it still points at the add-on's URL. A `playwright`
server you added yourself is never touched (and blocks the add-on's registration;
the start-up output says so).
Then, inside `ddev claude`, ask for things like "open the site and check the login form
works". `/mcp` should list `playwright` as connected. The first browser call in a session
asks for permission as usual; allow `mcp__playwright` in `/permissions` if you want it
unprompted.
How it is set up:
- The browser reaches the project at its normal URL (`https://<project>.ddev.site`) through
the DDEV router, and at `http://web` directly. It ignores certificate errors, since
DDEV's mkcert CA is not installed in the container.
- Each MCP session gets a fresh in-memory profile (`--isolated`), so several `ddev claude`
sessions can use the browser at once. Logins do not survive the session.
- The project is mounted at `/var/www/html`, the same path as in the web container. File
uploads and saved screenshots use paths Claude can open directly. Auto-named output goes
to `.ddev/claude-code/browser-output/`, which is gitignored with the rest of
`.ddev/claude-code/`.
- Port 8931 is only exposed on the project's Docker network, never published to the host.
- The image is pinned to a tested release. To use another one, set `CLAUDE_BROWSER_IMAGE` in
`.ddev/.env`, e.g. `CLAUDE_BROWSER_IMAGE=mcr.microsoft.com/playwright/mcp:v0.0.84`, then
`ddev restart`.
- While the enabled copy still has its `#ddev-generated` line, `install.sh` refreshes it from
the template after add-on upgrades and asks you to `ddev restart`. To customise the
service, delete that line from `.ddev/docker-compose.claude-browser.yaml`. Updates then
stop, and `ddev claude-browser disable` leaves the file for you to delete yourself.
## Sibling repo mirrors
If a project works alongside sibling repos on the same self-hosted Git host, Claude can be given
@@ -305,41 +337,63 @@ changing the URL or rotating the token, run `claude mcp remove <name> -s user` a
## Installed managed files
.ddev/config.codex-claude.yaml
.ddev/commands/web/codex
.ddev/config.claude-cli.yaml
.ddev/commands/web/claude
.ddev/commands/web/claude-swap
.ddev/commands/web/claude-refs
.ddev/codex-cli/install.sh
.ddev/commands/host/claude-browser
.ddev/claude-code/install.sh
.ddev/claude-code/install-swap.sh
.ddev/claude-code/refs-sync.sh
.ddev/claude-code/git-exclude.sh
.ddev/claude-code/browser-setup.sh
.ddev/claude-code/browser/docker-compose.claude-browser.yaml
Plus `.ddev/docker-compose.claude-browser.yaml` while the [headless browser](#headless-browser) is enabled.
## Keeping add-on files out of git
Every artifact the add-on drops into a project — `.ddev/claude-code/`, `.ddev/codex-cli/`, the
`commands/web/*` wrappers, `.ddev/config.codex-claude.yaml`, `.ddev/addon-metadata/ddev-codex-claude-cli`,
`.ddev/plans`, and `.ddev/claude-refs.txt` — should stay out of the project's git history.
Every artifact the add-on drops into a project — `.ddev/claude-code/`, the
command wrappers, `.ddev/config.claude-cli.yaml`, `.ddev/docker-compose.claude-browser.yaml`,
`.ddev/addon-metadata/ddev-claude-cli`, `.ddev/plans`, `.ddev/claude-refs.txt`, and the legacy
`.ddev/codex-cli/` — should stay out of the project's git history.
`.ddev/claude-code/git-exclude.sh` maintains a marker-delimited block for this in the project's
`.git/info/exclude` (not `.gitignore`): a per-clone file that every git process honours, including
git run from the host, regardless of `$HOME` or container config. `install.sh` runs it on every
`ddev start` and every `ddev claude` (and the codex installer runs the same script too), so the
`ddev start` and every `ddev claude`, so the
block always matches the add-on's current file list — nothing to copy or keep in sync by hand.
Content outside the `>>> ddev-codex-claude-cli >>>` / `<<< ddev-codex-claude-cli <<<` markers is
left untouched; only the block between them is rewritten. Personal patterns (`.DS_Store`, `.idea`,
Content outside the `>>> ddev-claude-cli >>>` / `<<< ddev-claude-cli <<<` markers is
left untouched; only the block between them is rewritten. A block left by the add-on's former
name (`ddev-codex-claude-cli`) is removed. Personal patterns (`.DS_Store`, `.idea`,
`CLAUDE.md`, …) are not part of this block — they belong in your own `~/.gitignore_global`, wired
up via `core.excludesfile` as described below, since whether a given project tracks its own
`CLAUDE.md` is not the add-on's call.
- if the container home has `.gitignore_global` (typically from host `~/.ddev/homeadditions/.gitignore_global`), it is copied into each project-local CLI `HOME` on first bootstrap; git's `core.excludesfile` is pointed at that project-local copy — this only affects git run *inside* the container, which is why the add-on's own artifacts use the `.git/info/exclude` block instead
- the first `ddev codex` in a project can take some time because it downloads the Codex CLI binary; later runs reuse `.ddev/codex-cli/bin/codex`
- if the container home has `.gitignore_global` (typically from host `~/.ddev/homeadditions/.gitignore_global`), it is copied into the project-local `HOME` on first bootstrap; git's `core.excludesfile` is pointed at that project-local copy — this only affects git run *inside* the container, which is why the add-on's own artifacts use the `.git/info/exclude` block instead
- the first `ddev start` after installing the add-on can take some time because it downloads Claude Code; later starts reuse `.ddev/claude-code/home/.local/bin/claude`
Removing the add-on deletes `.ddev/codex-cli/` and `.ddev/claude-code/` as well (local auth, config, and CLI binaries). Back up those directories first if you need to keep them. The `.git/info/exclude` block is left in place; an orphaned entry for a path that no longer exists is harmless.
Removing the add-on deletes `.ddev/claude-code/` and the browser compose file as well (local auth, config, and CLI binary). Back up that directory first if you need to keep them. The `.git/info/exclude` block is left in place; an orphaned entry for a path that no longer exists is harmless.
## Upgrading from ddev-codex-claude-cli
The add-on used to be called `ddev-codex-claude-cli` and also shipped Codex CLI
(`ddev codex`). Codex has been removed. To upgrade a project, install the new add-on over the
old one:
ddev add-on get /path/to/ddev-claude-cli
ddev restart
**Do not run `ddev add-on remove ddev-codex-claude-cli` first.** Its removal actions delete
`.ddev/claude-code/`, including your Claude login, settings, and claude-swap accounts.
Installing `ddev-claude-cli` takes over `.ddev/claude-code/` as it is. It also removes the old
add-on's managed files (`config.codex-claude.yaml`, `commands/web/codex`,
`codex-cli/install.sh`, each only while still marked `#ddev-generated`) and its
`addon-metadata` entry, so DDEV no longer lists it. `.ddev/codex-cli/`, which holds Codex
auth and config, is left in place, still gitignored, for you to back up or delete.
## macOS / Linux / WSL2
This add-on runs Codex CLI and Claude Code CLI in the DDEV container, so host OS differences are minimized as long as DDEV works normally.
This add-on runs Claude Code CLI in the DDEV container, so host OS differences are minimized as long as DDEV works normally.
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
#ddev-generated
# Keeps Claude Code's MCP config in step with the opt-in headless browser.
#
# Enabled (.ddev/docker-compose.claude-browser.yaml exists): registers the
# "playwright" MCP server at user scope, and refreshes an add-on-managed compose
# copy from the template after an add-on upgrade. Disabled: removes the
# registration, but only if it is still the one this script created.
#
# Called by install.sh with HOME already pointing at the persisted profile. Never
# fatal: a browser problem must not fail "ddev start" or "ddev claude".
set -uo pipefail
DDEV_DIR="${DDEV_APPROOT}/.ddev"
TEMPLATE="${DDEV_DIR}/claude-code/browser/docker-compose.claude-browser.yaml"
ENABLED="${DDEV_DIR}/docker-compose.claude-browser.yaml"
NAME="playwright"
URL="http://claude-browser:8931/mcp"
CLAUDE_JSON="${HOME}/.claude.json"
if ! command -v jq >/dev/null 2>&1; then
[ -f "${ENABLED}" ] && echo "claude-browser: jq missing, skipping MCP registration" >&2
exit 0
fi
# Read ~/.claude.json directly: "claude mcp get" would try to connect.
registered=0
jq -e --arg n "${NAME}" '.mcpServers[$n]' "${CLAUDE_JSON}" >/dev/null 2>&1 && registered=1
current_url="$(jq -r --arg n "${NAME}" '.mcpServers[$n].url // empty' "${CLAUDE_JSON}" 2>/dev/null)"
if [ -f "${ENABLED}" ]; then
if [ -f "${TEMPLATE}" ] && grep -q '#ddev-generated' "${ENABLED}" && ! cmp -s "${TEMPLATE}" "${ENABLED}"; then
cat "${TEMPLATE}" >"${ENABLED}"
echo "claude-browser: refreshed .ddev/docker-compose.claude-browser.yaml from the add-on; run 'ddev restart' to apply" >&2
fi
if [ "${registered}" -eq 0 ]; then
claude mcp add --transport http --scope user "${NAME}" "${URL}" >/dev/null \
&& echo "claude-browser: registered MCP server '${NAME}' (${URL})"
elif [ "${current_url}" != "${URL}" ]; then
echo "claude-browser: an MCP server named '${NAME}' already exists and is not the add-on's; leaving it alone" >&2
fi
elif [ "${registered}" -eq 1 ] && [ "${current_url}" = "${URL}" ]; then
claude mcp remove "${NAME}" -s user >/dev/null \
&& echo "claude-browser: unregistered MCP server '${NAME}'"
fi
exit 0
@@ -0,0 +1,76 @@
#ddev-generated
# Opt-in headless browser for Claude Code: Playwright MCP with headless Chromium.
#
# This is the template. It does nothing where it is: "ddev claude-browser enable"
# copies it to .ddev/docker-compose.claude-browser.yaml and restarts the project, and
# install.sh registers it with Claude Code as the "playwright" MCP server
# (http://claude-browser:8931/mcp). While the copy keeps the #ddev-generated line,
# install.sh refreshes it from this template after add-on upgrades; remove that line
# from the copy to customise it.
#
# Pick another release with CLAUDE_BROWSER_IMAGE in .ddev/.env, e.g.
# CLAUDE_BROWSER_IMAGE=mcr.microsoft.com/playwright/mcp:v0.0.84
services:
claude-browser:
image: ${CLAUDE_BROWSER_IMAGE:-mcr.microsoft.com/playwright/mcp:v0.0.83}-${DDEV_SITENAME}-built
build:
# The upstream image runs as "node" (uid 1000). Turn that user into the host user
# (same name, uid and gid) so screenshots and other files the browser writes into
# the mounted project belong to you. Same approach as ddev-selenium-standalone-chrome.
dockerfile_inline: |
ARG CLAUDE_BROWSER_IMAGE="scratch"
FROM $${CLAUDE_BROWSER_IMAGE}
USER root
ARG username
ARG uid
ARG gid
RUN set -eu; \
old_uid="$$(id -u node)"; old_gid="$$(id -g node)"; \
for u in "$$(getent passwd "$${uid}" | cut -d: -f1)" "$$(getent passwd "$${username}" | cut -d: -f1)"; do \
if [ -n "$${u}" ] && [ "$${u}" != "node" ]; then userdel "$${u}"; fi; \
done; \
for g in "$$(getent group "$${gid}" | cut -d: -f1)" "$$(getent group "$${username}" | cut -d: -f1)"; do \
if [ -n "$${g}" ] && [ "$${g}" != "node" ]; then groupdel "$${g}" || true; fi; \
done; \
groupmod -o -g "$${gid}" node; \
if ! getent group "$${username}" >/dev/null; then groupmod -n "$${username}" node; fi; \
usermod -o -u "$${uid}" -g "$${gid}" node; \
if [ "$${username}" != "node" ]; then usermod -l "$${username}" node; fi; \
find / -xdev -uid "$${old_uid}" -exec chown -h "$${uid}" {} +; \
find / -xdev -gid "$${old_gid}" -exec chgrp -h "$${gid}" {} +
USER $${uid}:$${gid}
args:
CLAUDE_BROWSER_IMAGE: ${CLAUDE_BROWSER_IMAGE:-mcr.microsoft.com/playwright/mcp:v0.0.83}
username: ${DDEV_USER}
uid: ${DDEV_UID}
gid: ${DDEV_GID}
container_name: ddev-${DDEV_SITENAME}-claude-browser
user: "${DDEV_UID}:${DDEV_GID}"
restart: "no"
# Chromium needs more than Docker's 64 MB default /dev/shm for heavier pages.
shm_size: 1gb
# Appended to the image's entrypoint (node cli.js --headless --browser chromium --no-sandbox).
command:
- --port=8931
- --host=0.0.0.0
# The port is only exposed on the project's Docker network, never published to
# the host, so the Host-header check adds nothing here.
- --allowed-hosts=*
# DDEV's mkcert CA is not in this container's trust store.
- --ignore-https-errors
# Fresh in-memory profile per session, so parallel "ddev claude" sessions never
# fight over one profile directory.
- --isolated
# Same path as in the web container, so paths in tool results work for Claude as-is.
- --output-dir=/var/www/html/.ddev/claude-code/browser-output
- --file-paths=absolute
expose:
- 8931
volumes:
# The project at the web container's path: file uploads and saved screenshots
# use paths Claude can read directly.
- "../:/var/www/html"
x-ddev:
describe-info: |
Playwright MCP: http://claude-browser:8931/mcp
Toggle with: ddev claude-browser enable|disable
+22 -10
View File
@@ -12,20 +12,24 @@
# they belong in ~/.gitignore_global, not to this add-on.
set -uo pipefail
BEGIN='# >>> ddev-codex-claude-cli >>>'
END='# <<< ddev-codex-claude-cli <<<'
BEGIN='# >>> ddev-claude-cli >>>'
END='# <<< ddev-claude-cli <<<'
# Block written by this add-on before it was renamed from ddev-codex-claude-cli; removed on sight.
LEGACY_BEGIN='# >>> ddev-codex-claude-cli >>>'
LEGACY_END='# <<< ddev-codex-claude-cli <<<'
PATTERNS=(
'.ddev/addon-metadata/ddev-codex-claude-cli/'
'.ddev/config.codex-claude.yaml'
'.ddev/addon-metadata/ddev-claude-cli/'
'.ddev/config.claude-cli.yaml'
'.ddev/commands/web/claude'
'.ddev/commands/web/claude-swap'
'.ddev/commands/web/claude-refs'
'.ddev/commands/web/codex'
'.ddev/commands/host/claude-browser'
'.ddev/docker-compose.claude-browser.yaml'
'.ddev/claude-code/'
'.ddev/codex-cli/'
'.ddev/claude-refs.txt'
'.ddev/plans/'
'.ddev/codex-cli/' # legacy: left behind by add-on versions that shipped Codex CLI
)
approot="${DDEV_APPROOT:-$(pwd)}"
@@ -50,15 +54,23 @@ block="$(
printf '%s\n' "${END}"
)"
# Existing block replaced in place, otherwise appended after a blank line.
if grep -qF "${BEGIN}" "${exclude}" && grep -qF "${END}" "${exclude}"; then
# Drop the pre-rename block first, then replace the current block in place or
# append it after a blank line.
current="$(awk -v begin="${LEGACY_BEGIN}" -v end="${LEGACY_END}" '
index($0, begin) == 1 { skip = 1; next }
skip && index($0, end) == 1 { skip = 0; eat = 1; next }
eat && $0 == "" { eat = 0; next }
{ eat = 0 }
!skip { print }
' "${exclude}")"
if grep -qF "${BEGIN}" <<<"${current}" && grep -qF "${END}" <<<"${current}"; then
updated="$(awk -v begin="${BEGIN}" -v end="${END}" -v block="${block}" '
index($0, begin) == 1 { print block; skip = 1; next }
skip && index($0, end) == 1 { skip = 0; next }
!skip { print }
' "${exclude}")"
' <<<"${current}")"
else
updated="$(cat "${exclude}"; [ -s "${exclude}" ] && printf '\n'; printf '%s\n' "${block}")"
updated="$(printf '%s' "${current}"; [ -n "${current}" ] && printf '\n\n'; printf '%s\n' "${block}")"
fi
# Only write on a real change, so "ddev start" does not touch the mtime every time.
+6
View File
@@ -80,6 +80,12 @@ if [ -x "${PERSIST_BASE}/git-exclude.sh" ]; then
"${PERSIST_BASE}/git-exclude.sh" || true
fi
# Headless browser, opt-in via "ddev claude-browser enable". Registers or removes the
# Playwright MCP server to match; local and quick, never fatal.
if [ -x "${PERSIST_BASE}/browser-setup.sh" ]; then
"${PERSIST_BASE}/browser-setup.sh" || true
fi
# Sibling-repo mirrors, opt-in via .ddev/claude-refs.txt. Setup is local and quick;
# the sync runs detached so neither ddev start nor ddev claude waits on the network,
# and refs-sync.sh throttles itself.
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
#ddev-generated
## Description: Turn the headless browser (Playwright MCP) for Claude Code on or off
## Usage: claude-browser [enable|disable|status]
## Example: "ddev claude-browser enable"
set -euo pipefail
TEMPLATE="${DDEV_APPROOT}/.ddev/claude-code/browser/docker-compose.claude-browser.yaml"
TARGET="${DDEV_APPROOT}/.ddev/docker-compose.claude-browser.yaml"
CONTAINER="ddev-${DDEV_SITENAME}-claude-browser"
case "${1:-status}" in
enable|on)
if [ ! -f "${TEMPLATE}" ]; then
echo "Missing ${TEMPLATE}; reinstall the add-on with 'ddev add-on get'." >&2
exit 1
fi
if [ -f "${TARGET}" ]; then
echo "The headless browser is already enabled (.ddev/docker-compose.claude-browser.yaml)."
exit 0
fi
cp "${TEMPLATE}" "${TARGET}"
echo "Enabled the headless browser; restarting so the claude-browser service starts ..."
ddev restart
;;
disable|off)
if [ ! -f "${TARGET}" ]; then
echo "The headless browser is not enabled."
exit 0
fi
if ! grep -q '#ddev-generated' "${TARGET}"; then
echo "${TARGET} has been customised (no #ddev-generated line); delete it yourself and run 'ddev restart'." >&2
exit 1
fi
rm -f "${TARGET}"
echo "Disabled the headless browser; restarting so the claude-browser service stops ..."
ddev restart
;;
status)
if [ -f "${TARGET}" ]; then
state="enabled"
else
state="disabled"
fi
running="unknown"
if command -v docker >/dev/null 2>&1; then
running="$(docker inspect -f '{{.State.Status}}' "${CONTAINER}" 2>/dev/null || echo "not running")"
fi
echo "Headless browser: ${state} (container ${CONTAINER}: ${running})"
;;
*)
echo "Usage: ddev claude-browser [enable|disable|status]" >&2
exit 2
;;
esac
@@ -6,4 +6,3 @@ webimage_extra_packages:
hooks:
post-start:
- exec: .ddev/claude-code/install.sh
- exec: .ddev/codex-cli/install.sh
+19 -9
View File
@@ -1,28 +1,38 @@
name: ddev-codex-claude-cli
name: ddev-claude-cli
project_files:
- config.codex-claude.yaml
- commands/web/codex
- config.claude-cli.yaml
- commands/web/claude
- commands/web/claude-swap
- commands/web/claude-refs
- codex-cli/install.sh
- commands/host/claude-browser
- claude-code/install.sh
- claude-code/install-swap.sh
- claude-code/refs-sync.sh
- claude-code/git-exclude.sh
- claude-code/browser-setup.sh
- claude-code/browser/docker-compose.claude-browser.yaml
post_install_actions:
- |
#ddev-description: Make AI CLI scripts executable
#ddev-description: Make Claude CLI scripts executable
set -eu
chmod +x commands/web/codex commands/web/claude commands/web/claude-swap commands/web/claude-refs codex-cli/install.sh claude-code/install.sh claude-code/install-swap.sh claude-code/refs-sync.sh claude-code/git-exclude.sh
chmod +x commands/web/claude commands/web/claude-swap commands/web/claude-refs commands/host/claude-browser claude-code/install.sh claude-code/install-swap.sh claude-code/refs-sync.sh claude-code/git-exclude.sh claude-code/browser-setup.sh
- |
#ddev-description: Clean up after ddev-codex-claude-cli, this add-on's former name
# Files are removed only while still marked #ddev-generated. .ddev/claude-code/ is shared
# with the old add-on and kept as is; .ddev/codex-cli/ (Codex auth and config) is kept too.
for f in config.codex-claude.yaml commands/web/codex codex-cli/install.sh; do
if [ -f "$f" ] && grep -q '#ddev-generated' "$f"; then rm -f "$f"; fi
done
# Forget the old add-on, so it is not listed twice and nobody runs its removal
# actions, which would delete .ddev/claude-code/.
rm -rf addon-metadata/ddev-codex-claude-cli
removal_actions:
- |
#ddev-description: Remove AI CLI add-on artifacts
rm -f config.codex-claude.yaml commands/web/codex commands/web/claude commands/web/claude-swap commands/web/claude-refs 2>/dev/null || true
rm -rf codex-cli 2>/dev/null || true
#ddev-description: Remove Claude CLI add-on artifacts
rm -f config.claude-cli.yaml commands/web/claude commands/web/claude-swap commands/web/claude-refs commands/host/claude-browser docker-compose.claude-browser.yaml 2>/dev/null || true
rm -rf claude-code 2>/dev/null || true
ddev_version_constraint: ">= v1.24.3"