// Devcontainer for GitNexus. Pre-installs Claude Code, OpenAI Codex CLI, // and Cursor CLI alongside the Node.js native build chain. Cross-platform // across macOS, Linux, and Windows-via-WSL2 (Windows-native is unsupported // — see .devcontainer/README.md § Windows 11 setup). Opens via the VS Code // Dev Containers extension. // // First-time setup, auth flows, and troubleshooting: .devcontainer/README.md. { "name": "GitNexus AI CLI Devcontainer", "build": { "dockerfile": "Dockerfile", "context": ".", "args": { "CLAUDE_CODE_VERSION": "2.1.153", "CODEX_VERSION": "0.134.0", "CURSOR_VERSION": "latest", "TZ": "${localEnv:TZ:UTC}" } }, // Runs on the HOST shell before the container is created. Guarantees the // bind-mount source directories below exist so Docker doesn't reject the // mount when a CLI has never been used on this host. OS-keyed because // VS Code runs the host shell in its native form (POSIX on Linux/macOS, // cmd.exe on Windows — which means `mkdir -p` + `$HOME` won't work on // Win32). All three branches are idempotent. WSL is covered by the // linux branch because VS Code's WSL extension runs initializeCommand // in the WSL shell. "initializeCommand": { "linux": "mkdir -p $HOME/.claude $HOME/.codex $HOME/.cursor $HOME/.config/gh && touch $HOME/.gitconfig", "darwin": "mkdir -p $HOME/.claude $HOME/.codex $HOME/.cursor $HOME/.config/gh && touch $HOME/.gitconfig", "win32": "powershell -NoProfile -Command \"$d=$env:USERPROFILE; foreach ($p in '.claude','.codex','.cursor','.config\\gh') { $f=Join-Path $d $p; if (-not (Test-Path $f)) { New-Item -ItemType Directory -Force -Path $f | Out-Null } }; if (-not (Test-Path (Join-Path $d '.gitconfig'))) { New-Item -ItemType File -Path (Join-Path $d '.gitconfig') | Out-Null }\"" }, "features": { "ghcr.io/devcontainers/features/github-cli:1": {} }, "remoteUser": "node", "updateRemoteUserUID": true, "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated", "workspaceFolder": "/workspace", // Mount topology, by group: // // 1. CLI config dirs — bind-mounted from the host so the developer's // existing plugins, skills, agents, memory, settings, and credentials // for Claude Code / Codex / Cursor + git identity + gh auth are // immediately available inside the container, and changes inside the // container flow back to the host. ~/.gitconfig is read-only so // container-side `git config --global` doesn't leak to host config. // For high-trust environments where host and container should NOT // share credentials, swap these three CLI bind mounts for // per-devcontainer named volumes (Anthropic's reference pattern). // // 2. Per-instance state — `${devcontainerId}` scoped: history and npm // cache survive container rebuilds but stay isolated between // sibling devcontainer instances. // // 3. Per-workspace-name AND per-instance state — workspace `node_modules` // volumes use both `${localWorkspaceFolderBasename}` (debuggable in // `docker volume ls`) and `${devcontainerId}` (collision-free between // sibling instances of the same repo, e.g., ~/work/GitNexus vs // ~/projects/GitNexus). Keeps tree-sitter native binaries and // onnxruntime off the workspace bind mount (the real Win/Mac perf win). "mounts": [ "source=${localEnv:HOME}/.claude,target=/home/node/.claude,type=bind", "source=${localEnv:HOME}/.codex,target=/home/node/.codex,type=bind", "source=${localEnv:HOME}/.cursor,target=/home/node/.cursor,type=bind", "source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,readonly", "source=${localEnv:HOME}/.config/gh,target=/home/node/.config/gh,type=bind", "source=commandhistory-${devcontainerId},target=/commandhistory,type=volume", "source=npm-cache-${devcontainerId},target=/home/node/.npm,type=volume", "source=${localWorkspaceFolderBasename}-root-node-modules-${devcontainerId},target=/workspace/node_modules,type=volume", "source=${localWorkspaceFolderBasename}-gitnexus-node-modules-${devcontainerId},target=/workspace/gitnexus/node_modules,type=volume", "source=${localWorkspaceFolderBasename}-gitnexus-web-node-modules-${devcontainerId},target=/workspace/gitnexus-web/node_modules,type=volume", "source=${localWorkspaceFolderBasename}-gitnexus-shared-node-modules-${devcontainerId},target=/workspace/gitnexus-shared/node_modules,type=volume" ], // Interactive login is the default auth path for all three CLIs; // credentials persist in the host-bind-mounted directories (~/.claude, // ~/.codex, ~/.cursor) declared in the mounts block above. // API keys (ANTHROPIC_API_KEY / OPENAI_API_KEY / CURSOR_API_KEY) are NOT // injected via containerEnv — `${localEnv:VAR}` resolves an unset host var // to an empty string, and Cursor in particular treats `CURSOR_API_KEY=""` // as "use this empty key" rather than "fall back to stored login", which // would silently break `cursor-agent login`. Users who need API key auth // should `export` the var in their container shell or carry it via their // VS Code dotfiles repo (see .devcontainer/README.md). "containerEnv": { "CLAUDE_CONFIG_DIR": "/home/node/.claude", "DISABLE_AUTOUPDATER": "1", "HISTFILE": "/commandhistory/.zsh_history" }, "customizations": { "vscode": { "extensions": [ "anthropic.claude-code", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "eamodio.gitlens" ], "settings": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "files.eol": "\n", "terminal.integrated.defaultProfile.linux": "zsh", "terminal.integrated.profiles.linux": { "bash": { "path": "bash", "icon": "terminal-bash" }, "zsh": { "path": "zsh" } } } } }, // 4747 (gitnexus serve) must not be remapped: gitnexus-web hardcodes // http://localhost:4747 as the default backend URL. "forwardPorts": [5173, 4747, 4173], "portsAttributes": { "5173": { "label": "Vite dev (gitnexus-web)", "onAutoForward": "notify" }, "4747": { "label": "gitnexus serve HTTP API", "onAutoForward": "notify", "requireLocalPort": true }, "4173": { "label": "Static web (Vite preview)", "onAutoForward": "silent" } }, // Driver script with labeled steps lives at .devcontainer/post-create.sh // so each step's success/failure is visible in the log without parsing // an &&-chain. Run via `bash` explicitly so the script doesn't depend // on its executable bit surviving the workspace bind mount. "postCreateCommand": "bash .devcontainer/post-create.sh" }