feat: rename to ddev-claude-cli, drop Codex CLI, add opt-in headless browser
This commit is contained in:
1 parent
b6d88ce4dc
commit
d8076fa5a0
8 files changed
+322
-64
No files matched your search
@@ -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.
|
||||
Executable
+46
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Executable
+55
@@ -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
@@ -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"
|
||||
Reference in new issue
Block a user