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.