# ddev-codex-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). ## 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)) - 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 - seeds the project-local HOME from the container home on first bootstrap for `.gitignore_global` when present - 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: ${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. 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: | Path | Role | |------|------| | `.ddev/claude-code/install.sh` | Claude Code install script | | `.ddev/claude-code/home/.local/bin/claude` | Claude Code CLI binary | | `.ddev/claude-code/home` | `HOME` | | `.ddev/claude-code/config` | `XDG_CONFIG_HOME` | | `.ddev/claude-code/data` | `XDG_DATA_HOME` | | `.ddev/claude-code/state` | `XDG_STATE_HOME` | Authentication and settings are **per project**: sign in once per project; they persist across container restarts. ## Install From a DDEV project root: ddev add-on get /path/to/ddev-codex-claude-cli Then restart: ddev restart ## Usage ddev codex ddev codex --version ddev claude ddev claude --version ddev claude update ddev claude-swap list # opt-in; see "Multiple Claude accounts" ## Updates Codex CLI is installed lazily when `ddev codex` runs and the persisted binary is missing or broken. 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. To check for and install a newer Claude Code stable release, run: ddev claude update ## Multiple Claude accounts The add-on can integrate [`claude-swap`](https://github.com/realiti4/claude-swap) (`cswap`) so one project can hold several Claude accounts and swap between them — mainly to keep working past a 5-hour or weekly rate limit. It is **opt-in**: nothing is installed during `ddev start`; the first `ddev claude-swap` call (or first `ddev claude --account …`) installs `uv` and `claude-swap` into the project-local Claude profile under `.ddev/claude-code/`. Accounts are stored **per project**, next to the rest of the Claude state: .ddev/claude-code/data/claude-swap/ They persist across restarts and are deleted when the add-on is removed. Back them up first with `ddev claude-swap export backup.cswap` if you need to keep them. ### Swapping accounts without losing your session A running `ddev claude` conversation is not tied to the auth token, and Claude Code re-reads its credentials file whenever it changes. So: 1. Terminal 1: you are in `ddev claude`, working, and hit (or approach) the limit. 2. Terminal 2 (on the host): `ddev claude-swap switch ` (or `ddev claude-swap switch --strategy best`). 3. Back in terminal 1: send the next message — it goes out on the new account, in the **same session** with all its context. If a request just failed with the limit error, resend it. To automate step 2, run `ddev claude-swap auto` in a spare terminal (or an `ddev ssh` shell); it watches usage and switches around 90%. ### One-time setup ddev claude # sign in as your first account ddev claude-swap add --alias a # capture it ddev claude # /login inside Claude Code as the next account, then exit ddev claude-swap add --alias b # capture it ddev claude-swap list # all accounts with usage and reset times ddev claude-swap status # currently active account `ddev claude-swap` forwards every argument straight to `cswap`, so any `cswap` subcommand works (`switch`, `run`, `map`, `disable`, `export`, `import`, `tui`, …). `ddev claude-swap upgrade` updates `claude-swap` to the latest release. ### Starting a session pinned to an account Separately from the hot-swap flow, you can launch a session bound to one account (with its own per-account chat history): ddev claude --account b ddev claude --account b --resume # remaining args pass straight to Claude Code Set a project default so plain `ddev claude` always uses it: echo b > .ddev/claude-code/claude-swap-account # or export DDEV_CLAUDE_SWAP_ACCOUNT=b Remove the file (or unset the variable) to go back to the normal single-account launch. ### Requirements The first install needs outbound network access to `astral.sh`, `pypi.org`, `files.pythonhosted.org`, and `github.com` (for the `uv`-managed Python 3.12). `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. ## Installed managed files .ddev/config.codex-cli.yaml .ddev/config.claude-code.yaml .ddev/commands/web/codex .ddev/commands/web/claude .ddev/commands/web/claude-swap .ddev/codex-cli/install.sh .ddev/claude-code/install.sh .ddev/claude-code/install-swap.sh ## Notes - 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` - 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` - 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 - if you do not want add-on or CLI runtime files in your repository, ignore them in git, for example: /.ddev/addon-metadata/ddev-codex-claude-cli /.ddev/config.codex-cli.yaml /.ddev/config.claude-code.yaml /.ddev/commands/web/codex /.ddev/commands/web/claude /.ddev/commands/web/claude-swap /.ddev/codex-cli/ /.ddev/claude-code/ 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. ## 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.