chore: initial commit
This commit is contained in:
commit
63f3199076
9 files changed
+567
No files matched your search
@@ -0,0 +1,185 @@
|
||||
# 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 <n|email|alias>`
|
||||
(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.
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
#ddev-generated
|
||||
set -euo pipefail
|
||||
|
||||
# Lazy installer for claude-swap (cswap): a multi-account manager for Claude Code.
|
||||
# It is NOT installed during "ddev start" - the first "ddev claude-swap ..." call
|
||||
# (or "ddev claude --account ...") runs this script, which installs uv and then
|
||||
# "claude-swap" into the same project-local, persisted profile that the Claude
|
||||
# Code CLI uses. Pass --upgrade to update an existing install.
|
||||
#
|
||||
# All state lives under .ddev/claude-code so it survives container rebuilds
|
||||
# without being committed to the project repository, exactly like install.sh.
|
||||
PERSIST_BASE="${DDEV_APPROOT}/.ddev/claude-code"
|
||||
PERSIST_HOME="${PERSIST_BASE}/home" # replaces $HOME inside the container
|
||||
PERSIST_CONFIG_HOME="${PERSIST_BASE}/config" # XDG_CONFIG_HOME
|
||||
PERSIST_DATA_HOME="${PERSIST_BASE}/data" # XDG_DATA_HOME
|
||||
PERSIST_STATE_HOME="${PERSIST_BASE}/state" # XDG_STATE_HOME
|
||||
PERSIST_BIN_DIR="${PERSIST_HOME}/.local/bin" # shared with the claude launcher
|
||||
UV_BIN="${PERSIST_BIN_DIR}/uv"
|
||||
CSWAP_BIN="${PERSIST_BIN_DIR}/cswap"
|
||||
|
||||
mkdir -p \
|
||||
"${PERSIST_HOME}" \
|
||||
"${PERSIST_CONFIG_HOME}" \
|
||||
"${PERSIST_DATA_HOME}" \
|
||||
"${PERSIST_STATE_HOME}" \
|
||||
"${PERSIST_BIN_DIR}"
|
||||
|
||||
# Point HOME / XDG paths at the persisted profile before any install or health
|
||||
# check so cswap's own data (Linux: ${XDG_DATA_HOME}/claude-swap) is per-project
|
||||
# and survives restarts with no extra configuration.
|
||||
export HOME="${PERSIST_HOME}"
|
||||
export XDG_CONFIG_HOME="${PERSIST_CONFIG_HOME}"
|
||||
export XDG_DATA_HOME="${PERSIST_DATA_HOME}"
|
||||
export XDG_STATE_HOME="${PERSIST_STATE_HOME}"
|
||||
export PATH="${PERSIST_BIN_DIR}:${PATH}"
|
||||
|
||||
# Keep every artifact uv creates inside the persisted tree.
|
||||
export UV_INSTALL_DIR="${PERSIST_BIN_DIR}" # uv / uvx binaries
|
||||
export UV_TOOL_BIN_DIR="${PERSIST_BIN_DIR}" # tool entry points (cswap)
|
||||
export UV_TOOL_DIR="${XDG_DATA_HOME}/uv/tools" # tool virtualenvs
|
||||
export UV_PYTHON_INSTALL_DIR="${XDG_DATA_HOME}/uv/python" # uv-managed CPython
|
||||
export UV_CACHE_DIR="${XDG_DATA_HOME}/uv/cache" # download / build cache
|
||||
|
||||
# Remove an unrunnable persisted uv (e.g. wrong arch after a host OS change).
|
||||
if [ -x "${UV_BIN}" ] && ! "${UV_BIN}" --version >/dev/null 2>&1; then
|
||||
echo "Persisted uv binary is not runnable, reinstalling ..."
|
||||
rm -f "${UV_BIN}" "${PERSIST_BIN_DIR}/uvx"
|
||||
fi
|
||||
|
||||
# Install uv when missing. uv fetches its own CPython 3.12 below, so the DDEV web
|
||||
# image's system Python (3.11) is never relied upon. Do not let the installer
|
||||
# edit shell rc files in the persisted HOME - PATH is set by the launchers.
|
||||
if [ ! -x "${UV_BIN}" ]; then
|
||||
echo "Installing uv ..."
|
||||
curl -fsSL https://astral.sh/uv/install.sh | env INSTALLER_NO_MODIFY_PATH=1 sh
|
||||
fi
|
||||
|
||||
if [ ! -x "${UV_BIN}" ]; then
|
||||
echo "uv installation completed but no executable was found at ${UV_BIN}." >&2
|
||||
echo "If outbound access to astral.sh is blocked this step cannot succeed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "--upgrade" ]; then
|
||||
echo "Upgrading claude-swap ..."
|
||||
"${UV_BIN}" tool upgrade claude-swap
|
||||
echo "claude-swap version: $("${CSWAP_BIN}" --version)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install claude-swap when missing or unrunnable.
|
||||
if [ ! -x "${CSWAP_BIN}" ] || ! "${CSWAP_BIN}" --version >/dev/null 2>&1; then
|
||||
echo "Installing claude-swap ..."
|
||||
"${UV_BIN}" tool install --force --python 3.12 claude-swap
|
||||
fi
|
||||
|
||||
if [ ! -x "${CSWAP_BIN}" ] || ! "${CSWAP_BIN}" --version >/dev/null 2>&1; then
|
||||
echo "claude-swap installation completed but ${CSWAP_BIN} is not runnable." >&2
|
||||
echo "First install needs outbound access to astral.sh, pypi.org," >&2
|
||||
echo "files.pythonhosted.org and github.com (uv-managed CPython)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "claude-swap version: $("${CSWAP_BIN}" --version)"
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
#ddev-generated
|
||||
set -euo pipefail
|
||||
|
||||
# All Claude Code state is stored under .ddev/claude-code so it survives
|
||||
# container rebuilds without being committed to the project repository.
|
||||
PERSIST_BASE="${DDEV_APPROOT}/.ddev/claude-code"
|
||||
PERSIST_HOME="${PERSIST_BASE}/home" # replaces $HOME inside the container
|
||||
PERSIST_CONFIG_HOME="${PERSIST_BASE}/config" # XDG_CONFIG_HOME (e.g. ~/.config)
|
||||
PERSIST_DATA_HOME="${PERSIST_BASE}/data" # XDG_DATA_HOME (e.g. ~/.local/share)
|
||||
PERSIST_STATE_HOME="${PERSIST_BASE}/state" # XDG_STATE_HOME (e.g. ~/.local/state)
|
||||
PERSIST_BIN_DIR="${PERSIST_HOME}/.local/bin" # Claude native installer target
|
||||
PERSIST_BIN="${PERSIST_BIN_DIR}/claude" # full path to the claude executable
|
||||
PERSIST_GITIGNORE_GLOBAL="${PERSIST_HOME}/.gitignore_global" # mirrors $HOME/.gitignore_global
|
||||
|
||||
# Capture the container's original HOME before we override it below, so we can
|
||||
# seed the persisted gitignore from the container image on first run.
|
||||
CONTAINER_HOME="${HOME:?HOME must be set}"
|
||||
CONTAINER_GITIGNORE_GLOBAL="${CONTAINER_HOME}/.gitignore_global"
|
||||
|
||||
# Ensure all persistence directories exist.
|
||||
mkdir -p \
|
||||
"${PERSIST_HOME}" \
|
||||
"${PERSIST_CONFIG_HOME}" \
|
||||
"${PERSIST_DATA_HOME}" \
|
||||
"${PERSIST_STATE_HOME}" \
|
||||
"${PERSIST_BIN_DIR}"
|
||||
|
||||
# Seed the persisted gitignore from the container image on first run only.
|
||||
if [ -f "${CONTAINER_GITIGNORE_GLOBAL}" ] && [ ! -f "${PERSIST_GITIGNORE_GLOBAL}" ]; then
|
||||
cp -f "${CONTAINER_GITIGNORE_GLOBAL}" "${PERSIST_GITIGNORE_GLOBAL}"
|
||||
fi
|
||||
|
||||
# Claude Code's native installer stores its launcher, auth, settings, downloads,
|
||||
# and versioned package data relative to HOME/XDG paths.
|
||||
export HOME="${PERSIST_HOME}"
|
||||
export XDG_CONFIG_HOME="${PERSIST_CONFIG_HOME}"
|
||||
export XDG_DATA_HOME="${PERSIST_DATA_HOME}"
|
||||
export XDG_STATE_HOME="${PERSIST_STATE_HOME}"
|
||||
export PATH="${PERSIST_BIN_DIR}:${PATH}"
|
||||
|
||||
# If a persisted binary exists but fails to run (e.g. wrong architecture after
|
||||
# a host OS change), remove it so the install step below fetches a fresh copy.
|
||||
if [ -x "${PERSIST_BIN}" ] && ! "${PERSIST_BIN}" --version >/dev/null 2>&1; then
|
||||
echo "Persisted Claude Code binary is not runnable, reinstalling ..."
|
||||
rm -f "${PERSIST_BIN}"
|
||||
fi
|
||||
|
||||
# Download and install the current stable Claude Code native build only when
|
||||
# missing. Use "ddev claude update" to check for and install newer versions.
|
||||
if [ ! -x "${PERSIST_BIN}" ]; then
|
||||
echo "Installing Claude Code CLI ..."
|
||||
curl -fsSL https://claude.ai/install.sh | bash -s stable
|
||||
fi
|
||||
|
||||
# Verify the binary is present and runnable after installation.
|
||||
if [ ! -x "${PERSIST_BIN}" ]; then
|
||||
echo "Claude Code installation completed but no executable was found at ${PERSIST_BIN}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! "${PERSIST_BIN}" --version >/dev/null 2>&1; then
|
||||
echo "Claude Code installation completed but ${PERSIST_BIN} is not runnable." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Point git's global excludes file at the persisted copy when available,
|
||||
# so git inside the container respects user-defined ignore patterns.
|
||||
if command -v git >/dev/null 2>&1 && [ -f "${PERSIST_GITIGNORE_GLOBAL}" ]; then
|
||||
CURRENT_EXCLUDESFILE="$(git config --global --get core.excludesfile || true)"
|
||||
if [ "${CURRENT_EXCLUDESFILE}" != "${PERSIST_GITIGNORE_GLOBAL}" ]; then
|
||||
git config --global core.excludesfile "${PERSIST_GITIGNORE_GLOBAL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Claude Code version: $("${PERSIST_BIN}" --version)"
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
#ddev-generated
|
||||
set -euo pipefail
|
||||
|
||||
# All Codex CLI state is stored under .ddev/codex-cli so it survives container
|
||||
# rebuilds without being committed to the project repository.
|
||||
PERSIST_BASE="${DDEV_APPROOT}/.ddev/codex-cli"
|
||||
PERSIST_HOME="${PERSIST_BASE}/home" # replaces $HOME inside the container
|
||||
PERSIST_CONFIG_HOME="${PERSIST_BASE}/config" # XDG_CONFIG_HOME (e.g. ~/.config)
|
||||
PERSIST_DATA_HOME="${PERSIST_BASE}/data" # XDG_DATA_HOME (e.g. ~/.local/share)
|
||||
PERSIST_STATE_HOME="${PERSIST_BASE}/state" # XDG_STATE_HOME (e.g. ~/.local/state)
|
||||
PERSIST_BIN_DIR="${PERSIST_BASE}/bin" # directory that holds the codex binary
|
||||
PERSIST_BIN="${PERSIST_BIN_DIR}/codex" # full path to the codex executable
|
||||
PERSIST_GITIGNORE_GLOBAL="${PERSIST_HOME}/.gitignore_global" # mirrors $HOME/.gitignore_global
|
||||
|
||||
# Capture the container's original HOME before we override it below, so we can
|
||||
# seed the persisted gitignore from the container image on first run.
|
||||
CONTAINER_HOME="${HOME:?HOME must be set}"
|
||||
CONTAINER_GITIGNORE_GLOBAL="${CONTAINER_HOME}/.gitignore_global"
|
||||
|
||||
# Ensure all persistence directories exist.
|
||||
mkdir -p \
|
||||
"${PERSIST_HOME}" \
|
||||
"${PERSIST_CONFIG_HOME}" \
|
||||
"${PERSIST_DATA_HOME}" \
|
||||
"${PERSIST_STATE_HOME}" \
|
||||
"${PERSIST_BIN_DIR}"
|
||||
|
||||
# Seed the persisted gitignore from the container image on first run only.
|
||||
if [ -f "${CONTAINER_GITIGNORE_GLOBAL}" ] && [ ! -f "${PERSIST_GITIGNORE_GLOBAL}" ]; then
|
||||
cp -f "${CONTAINER_GITIGNORE_GLOBAL}" "${PERSIST_GITIGNORE_GLOBAL}"
|
||||
fi
|
||||
|
||||
# Codex stores its standalone package tree under $HOME/.codex and links
|
||||
# $CODEX_INSTALL_DIR/codex into that tree. Point HOME there before any install
|
||||
# or health check so package data survives container restarts.
|
||||
export HOME="${PERSIST_HOME}"
|
||||
export CODEX_HOME="${PERSIST_HOME}/.codex"
|
||||
export XDG_CONFIG_HOME="${PERSIST_CONFIG_HOME}"
|
||||
export XDG_DATA_HOME="${PERSIST_DATA_HOME}"
|
||||
export XDG_STATE_HOME="${PERSIST_STATE_HOME}"
|
||||
export PATH="${PERSIST_BIN_DIR}:${HOME}/.local/bin:${PATH}"
|
||||
|
||||
# If a persisted binary exists but fails to run (e.g. wrong architecture after
|
||||
# a host OS change), remove it so the install step below fetches a fresh copy.
|
||||
if [ -x "${PERSIST_BIN}" ] && ! "${PERSIST_BIN}" --version >/dev/null 2>&1; then
|
||||
echo "Persisted Codex CLI binary is not runnable, reinstalling ..."
|
||||
rm -f "${PERSIST_BIN}"
|
||||
fi
|
||||
|
||||
# Download and install the Codex CLI into the persisted bin directory.
|
||||
if [ ! -x "${PERSIST_BIN}" ]; then
|
||||
echo "Installing Codex CLI ..."
|
||||
export CODEX_INSTALL_DIR="${PERSIST_BIN_DIR}"
|
||||
export CODEX_NON_INTERACTIVE=1
|
||||
curl -fsSL https://github.com/openai/codex/releases/latest/download/install.sh | sh
|
||||
fi
|
||||
|
||||
# Verify the binary is present after installation.
|
||||
if [ ! -x "${PERSIST_BIN}" ]; then
|
||||
echo "Codex CLI installation completed but no executable was found at ${PERSIST_BIN}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Point git's global excludes file at the persisted copy when available,
|
||||
# so git inside the container respects user-defined ignore patterns.
|
||||
if command -v git >/dev/null 2>&1 && [ -f "${PERSIST_GITIGNORE_GLOBAL}" ]; then
|
||||
CURRENT_EXCLUDESFILE="$(git config --global --get core.excludesfile || true)"
|
||||
if [ "${CURRENT_EXCLUDESFILE}" != "${PERSIST_GITIGNORE_GLOBAL}" ]; then
|
||||
git config --global core.excludesfile "${PERSIST_GITIGNORE_GLOBAL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Codex CLI version: $("${PERSIST_BIN}" --version)"
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
#ddev-generated
|
||||
## Description: Run Claude Code CLI inside the web container
|
||||
## Usage: claude [--account <n|email|alias>]
|
||||
## Example: "ddev claude --version"
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_SCRIPT="${DDEV_APPROOT}/.ddev/claude-code/install.sh"
|
||||
SWAP_INSTALL_SCRIPT="${DDEV_APPROOT}/.ddev/claude-code/install-swap.sh"
|
||||
PERSIST_BASE="${DDEV_APPROOT}/.ddev/claude-code"
|
||||
PERSIST_HOME="${PERSIST_BASE}/home"
|
||||
PERSIST_BIN="${PERSIST_HOME}/.local/bin/claude"
|
||||
CSWAP_BIN="${PERSIST_HOME}/.local/bin/cswap"
|
||||
|
||||
# Optional multi-account routing: "ddev claude --account <sel>" starts a session
|
||||
# pinned to a claude-swap account via "cswap run". With no flag, a project
|
||||
# default can come from $DDEV_CLAUDE_SWAP_ACCOUNT or the first line of
|
||||
# .ddev/claude-code/claude-swap-account. When neither is set this behaves
|
||||
# exactly like a plain Claude Code launch.
|
||||
ACCOUNT=""
|
||||
ARGS=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account=*) ACCOUNT="${1#--account=}"; shift ;;
|
||||
--account|-A)
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "ddev claude: --account requires an argument" >&2
|
||||
exit 1
|
||||
fi
|
||||
ACCOUNT="$2"; shift 2 ;;
|
||||
-A*) ACCOUNT="${1#-A}"; shift ;;
|
||||
*) ARGS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "${ACCOUNT}" ]; then
|
||||
if [ -n "${DDEV_CLAUDE_SWAP_ACCOUNT:-}" ]; then
|
||||
ACCOUNT="${DDEV_CLAUDE_SWAP_ACCOUNT}"
|
||||
elif [ -f "${PERSIST_BASE}/claude-swap-account" ]; then
|
||||
ACCOUNT="$(head -n1 "${PERSIST_BASE}/claude-swap-account" | tr -d '[:space:]')"
|
||||
fi
|
||||
fi
|
||||
|
||||
"${INSTALL_SCRIPT}"
|
||||
|
||||
export HOME="${PERSIST_HOME}"
|
||||
export XDG_CONFIG_HOME="${PERSIST_BASE}/config"
|
||||
export XDG_DATA_HOME="${PERSIST_BASE}/data"
|
||||
export XDG_STATE_HOME="${PERSIST_BASE}/state"
|
||||
export PATH="${PERSIST_HOME}/.local/bin:${PATH}"
|
||||
unset SSH_AUTH_SOCK
|
||||
|
||||
cd "${DDEV_APPROOT}"
|
||||
|
||||
if [ -n "${ACCOUNT}" ]; then
|
||||
"${SWAP_INSTALL_SCRIPT}"
|
||||
if [ "${#ARGS[@]}" -gt 0 ]; then
|
||||
exec "${CSWAP_BIN}" run "${ACCOUNT}" -- "${ARGS[@]}"
|
||||
fi
|
||||
exec "${CSWAP_BIN}" run "${ACCOUNT}"
|
||||
fi
|
||||
|
||||
exec "${PERSIST_BIN}" ${ARGS[@]+"${ARGS[@]}"}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
#ddev-generated
|
||||
## Description: Manage multiple Claude accounts (claude-swap / cswap)
|
||||
## Usage: claude-swap [command]
|
||||
## Example: "ddev claude-swap list"
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_SCRIPT="${DDEV_APPROOT}/.ddev/claude-code/install-swap.sh"
|
||||
PERSIST_BASE="${DDEV_APPROOT}/.ddev/claude-code"
|
||||
PERSIST_HOME="${PERSIST_BASE}/home"
|
||||
CSWAP_BIN="${PERSIST_HOME}/.local/bin/cswap"
|
||||
|
||||
# Lazy install / health check (also handles "ddev claude-swap upgrade").
|
||||
if [ "${1:-}" = "upgrade" ]; then
|
||||
exec "${INSTALL_SCRIPT}" --upgrade
|
||||
fi
|
||||
"${INSTALL_SCRIPT}"
|
||||
|
||||
export HOME="${PERSIST_HOME}"
|
||||
export XDG_CONFIG_HOME="${PERSIST_BASE}/config"
|
||||
export XDG_DATA_HOME="${PERSIST_BASE}/data"
|
||||
export XDG_STATE_HOME="${PERSIST_BASE}/state"
|
||||
export PATH="${PERSIST_HOME}/.local/bin:${PATH}"
|
||||
unset SSH_AUTH_SOCK
|
||||
|
||||
cd "${DDEV_APPROOT}"
|
||||
exec "${CSWAP_BIN}" "$@"
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
#ddev-generated
|
||||
## Description: Run Codex CLI inside the web container
|
||||
## Usage: codex
|
||||
## Example: "ddev codex --version"
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_SCRIPT="${DDEV_APPROOT}/.ddev/codex-cli/install.sh"
|
||||
PERSIST_BASE="${DDEV_APPROOT}/.ddev/codex-cli"
|
||||
PERSIST_BIN="${PERSIST_BASE}/bin/codex"
|
||||
|
||||
"${INSTALL_SCRIPT}"
|
||||
|
||||
export HOME="${PERSIST_BASE}/home"
|
||||
export CODEX_HOME="${PERSIST_BASE}/home/.codex"
|
||||
export XDG_CONFIG_HOME="${PERSIST_BASE}/config"
|
||||
export XDG_DATA_HOME="${PERSIST_BASE}/data"
|
||||
export XDG_STATE_HOME="${PERSIST_BASE}/state"
|
||||
export PATH="${PERSIST_BASE}/bin:${HOME}/.local/bin:${PATH}"
|
||||
unset SSH_AUTH_SOCK
|
||||
|
||||
cd "${DDEV_APPROOT}"
|
||||
exec "${PERSIST_BIN}" --sandbox danger-full-access --ask-for-approval on-request "$@"
|
||||
@@ -0,0 +1,9 @@
|
||||
#ddev-generated
|
||||
webimage_extra_packages:
|
||||
- ripgrep
|
||||
- bubblewrap
|
||||
- socat
|
||||
hooks:
|
||||
post-start:
|
||||
- exec: .ddev/claude-code/install.sh
|
||||
- exec: .ddev/codex-cli/install.sh
|
||||
@@ -0,0 +1,25 @@
|
||||
name: ddev-codex-claude-cli
|
||||
|
||||
project_files:
|
||||
- config.codex-claude-cli.yaml
|
||||
- commands/web/codex
|
||||
- commands/web/claude
|
||||
- commands/web/claude-swap
|
||||
- codex-cli/install.sh
|
||||
- claude-code/install.sh
|
||||
- claude-code/install-swap.sh
|
||||
|
||||
post_install_actions:
|
||||
- |
|
||||
#ddev-description: Make AI CLI scripts executable
|
||||
set -eu
|
||||
chmod +x commands/web/codex commands/web/claude commands/web/claude-swap codex-cli/install.sh claude-code/install.sh claude-code/install-swap.sh
|
||||
|
||||
removal_actions:
|
||||
- |
|
||||
#ddev-description: Remove AI CLI add-on artifacts
|
||||
rm -f config.codex-claude-cli.yaml commands/web/codex commands/web/claude commands/web/claude-swap 2>/dev/null || true
|
||||
rm -rf codex-cli 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