diff --git a/DOCKER_DEV.md b/DOCKER_DEV.md new file mode 100644 index 000000000..175ddbf57 --- /dev/null +++ b/DOCKER_DEV.md @@ -0,0 +1,212 @@ +# Docker-based Local Development + +Run the entire monorepo (Next.js backend + all Cloudflare Workers + PostgreSQL) with a single command instead of managing ~16 terminal windows. + +## Prerequisites + +| Tool | Version | Check | +|------|---------|-------| +| Docker Desktop (macOS/Windows) / Docker Engine (Linux) | 4.x+ | `docker --version` | +| Docker Compose v2 | 2.20+ | `docker compose version` | +| pnpm | 10.27.0 | `pnpm --version` | +| Node.js | ^22 | `node --version` | + +> **Note:** `pnpm install` must be run at least once before starting — the Docker containers mount the repo and expect `node_modules` to exist. + +## Quick Start + +```bash +# Install dependencies (if not already done) +pnpm install + +# Start everything +./dev/dev.sh + +# Or directly with docker compose +docker compose -f dev/docker-compose.dev.yml --profile all up +``` + +## Profiles + +Services are grouped into profiles for selective startup. PostgreSQL always starts (no profile required). + +| Profile | Services | +|---------|----------| +| `core` | PostgreSQL + Next.js backend | +| `agents` | PostgreSQL + cloud-agent + cloud-agent-next | +| `workers` | PostgreSQL + cloud-agent + all Cloudflare Workers | +| `all` | Everything | + +```bash +# Core only (postgres + nextjs) +./dev/dev.sh --profile core up + +# Core + agents +./dev/dev.sh --profile core --profile agents up + +# Everything +./dev/dev.sh # defaults to --profile all +./dev/dev.sh --profile all up + +# Specific services by name +docker compose -f dev/docker-compose.dev.yml up postgres nextjs cloud-agent +``` + +## Port Map + +| Service | Port | Directory | +|---------|------|-----------| +| PostgreSQL | 5432 | — | +| Next.js backend | 3000 | `.` (root) | +| cloud-agent | 8788 | `cloud-agent/` | +| cloudflare-ai-attribution | 8787 | `cloudflare-ai-attribution/` | +| cloudflare-code-review-infra | 8789 | `cloudflare-code-review-infra/` | +| cloudflare-app-builder | 8790 | `cloudflare-app-builder/` | +| cloudflare-auto-triage-infra | 8791 | `cloudflare-auto-triage-infra/` | +| cloudflare-webhook-agent-ingest | 8793 | `cloudflare-webhook-agent-ingest/` | +| cloud-agent-next | 8794 | `cloud-agent-next/` | +| kiloclaw | 8795 | `kiloclaw/` | +| cloudflare-auto-fix-infra | 8796 | `cloudflare-auto-fix-infra/` | +| cloudflare-db-proxy | 8797 | `cloudflare-db-proxy/` | +| cloudflare-deploy-builder | 8798 | `cloudflare-deploy-infra/builder/` | +| cloudflare-deploy-dispatcher | 8799 | `cloudflare-deploy-infra/dispatcher/` | +| cloudflare-session-ingest | 8800 | `cloudflare-session-ingest/` | +| cloudflare-o11y | 8801 | `cloudflare-o11y/` | +| cloudflare-git-token-service | 8802 | `cloudflare-git-token-service/` | + +> Ports are overridden via `--port` in the wrangler dev command to avoid conflicts between workers that share the same default port in their `wrangler.jsonc`. + +## Networking + +All services share the default Docker Compose bridge network. Services reach each other by their Compose service name (e.g., `postgres`, `nextjs`, `cloud-agent`) via Docker's built-in DNS — no `network_mode: host` needed, so this works on both **macOS** (Docker Desktop) and **Linux**. + +### How inter-service URLs are resolved + +The wrangler.jsonc files in each worker hardcode `localhost` for inter-service URLs (e.g., `KILOCODE_BACKEND_BASE_URL: "http://localhost:3000"`, Hyperdrive `localConnectionString: "postgres://...@localhost:5432/..."`). Inside Docker containers on a bridge network, `localhost` refers to the container itself, not other services. + +To fix this without modifying the shared wrangler.jsonc files: + +- **Wrangler workers** use [`dev/docker-wrangler-entrypoint.sh`](dev/docker-wrangler-entrypoint.sh) which creates a temporary patched copy of `wrangler.jsonc` at startup, replacing `localhost` references with Docker service names (e.g., `localhost:3000` → `nextjs:3000`, `localhost:5432` → `postgres:5432`). +- **Next.js** overrides env vars directly via the `environment:` key in docker-compose (e.g., `POSTGRES_URL`, `CLOUD_AGENT_API_URL`), which take precedence over values in `.env`. + +### Accessing services from the host + +From your host machine, services are still accessible at `localhost:` via Docker's port forwarding (the `ports:` mappings in docker-compose). + +## Environment Variables + +### Next.js backend + +The Next.js service loads `env_file: ../.env` (repo root `.env`). Make sure this file exists: + +```bash +# If you have an .env.example, copy it +cp .env.example .env +# Then fill in the required values +``` + +The docker-compose file overrides `POSTGRES_URL`, `CLOUD_AGENT_API_URL`, and `WEBHOOK_AGENT_URL` to use Docker service names. You don't need to set these in `.env` for Docker dev. + +### Cloudflare Workers + +Workers that need secrets use `.dev.vars` files in their respective directories. Copy the examples: + +```bash +# Example for cloud-agent +cp cloud-agent/.dev.vars.example cloud-agent/.dev.vars + +# Workers with .dev.vars.example files: +# cloud-agent, cloud-agent-next, cloudflare-app-builder, +# cloudflare-auto-fix-infra, cloudflare-auto-triage-infra, +# cloudflare-code-review-infra, cloudflare-db-proxy, +# cloudflare-deploy-infra/builder, cloudflare-deploy-infra/dispatcher, +# cloudflare-git-token-service, cloudflare-webhook-agent-ingest, +# kiloclaw +``` + +> **Note:** You do NOT need to change `localhost` references in `.dev.vars` files for Docker — the entrypoint script handles URL rewriting automatically via the wrangler.jsonc patching. + +## Architecture + +- **Shared Docker image** (`dev/Dockerfile.dev`): `node:22.14.0-slim` with pnpm, wrangler, and bun pre-installed. +- **Volume mount**: The entire repo is mounted at `/app` — file changes are reflected immediately (hot reload works). +- **Port mappings**: Each service exposes its port via explicit `ports:` mappings, which works on both macOS (Docker Desktop) and Linux. +- **Inter-service networking**: Docker Compose bridge network with DNS-based service discovery. See [Networking](#networking) above. +- **Existing `dev/docker-compose.yml`** is untouched — it continues to work standalone for PostgreSQL-only usage. + +## Docker Socket Mount (Container-backed Durable Objects) + +Four workers use Cloudflare's [container-backed Durable Objects](https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#container-durable-objects), which require Docker to spawn sandbox containers at runtime via Wrangler: + +- `cloud-agent` +- `cloud-agent-next` +- `cloudflare-app-builder` +- `cloudflare-deploy-builder` + +Because these workers already run inside Docker containers, they can't use Docker natively (Docker-in-Docker). Instead, the host's Docker socket is mounted into these containers: + +```yaml +volumes: + - /var/run/docker.sock:/var/run/docker.sock +``` + +This lets Wrangler inside the container talk to the host's Docker daemon to create sibling containers. + +### Security implications + +Mounting the Docker socket gives the container **full, unrestricted access to the host's Docker daemon** — equivalent to root access on the host. This is acceptable for local development but must **never** be used in production or CI environments with untrusted code. The mount is only applied to the four workers listed above; all other services use the default volume configuration from the shared base. + +## Troubleshooting + +### Port already in use + +If a service fails with `EADDRINUSE`, another process is already using that port. Check with: + +```bash +lsof -i : +``` + +### Worker fails to start + +Some workers (cloud-agent, cloud-agent-next) have `predev` scripts that build a wrapper using `bun`. The Docker image includes bun, but if you see build errors, try: + +```bash +# Rebuild the Docker image +docker compose -f dev/docker-compose.dev.yml build --no-cache +``` + +### node_modules issues + +The containers mount the host's `node_modules`. If you see missing dependency errors: + +```bash +pnpm install +# Then restart the containers +docker compose -f dev/docker-compose.dev.yml --profile all restart +``` + +### Inter-service connection refused + +If a worker can't reach another service (e.g., `ECONNREFUSED` to `nextjs:3000`), make sure the target service is running. Check with: + +```bash +docker compose -f dev/docker-compose.dev.yml ps +``` + +Services only start if their profile is active. For example, `cloud-agent` requires the `agents` or `all` profile. + +### Viewing logs for a single service + +```bash +docker compose -f dev/docker-compose.dev.yml logs -f nextjs +docker compose -f dev/docker-compose.dev.yml logs -f cloud-agent +``` + +### Stopping everything + +```bash +docker compose -f dev/docker-compose.dev.yml --profile all down + +# Also remove the postgres volume if you want a fresh database +docker compose -f dev/docker-compose.dev.yml --profile all down -v +``` diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..3e30d73ec --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +# Makefile — convenience targets for Kilo-Org/cloud + +COMPOSE_DEV := docker compose -f dev/docker-compose.dev.yml + +.PHONY: dev-docker dev-docker-core dev-docker-down + +## Start all services in Docker (postgres + nextjs + all workers) +dev-docker: + ./dev/dev.sh + +## Start only postgres + nextjs +dev-docker-core: + $(COMPOSE_DEV) --profile core up + +## Stop all Docker dev services +dev-docker-down: + $(COMPOSE_DEV) --profile all down diff --git a/dev/Dockerfile.dev b/dev/Dockerfile.dev new file mode 100644 index 000000000..fb094c89e --- /dev/null +++ b/dev/Dockerfile.dev @@ -0,0 +1,16 @@ +FROM node:22.14.0-slim + +# Install pnpm +RUN corepack enable && corepack prepare pnpm@10.27.0 --activate + +# Install wrangler globally for workers (pinned to match repo devDependencies) +RUN pnpm add -g wrangler@4.61.1 + +# Install bun (needed by cloud-agent and cloud-agent-next predev scripts) +# Pinned to a specific version for reproducibility +RUN apt-get update && apt-get install -y curl unzip && \ + curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash -s "bun-v1.2.5" && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app diff --git a/dev/dev.sh b/dev/dev.sh new file mode 100755 index 000000000..d93b06529 --- /dev/null +++ b/dev/dev.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Start the Docker Compose dev environment for the monorepo. +# +# Usage: +# ./dev/dev.sh # start everything +# ./dev/dev.sh --profile core # postgres + nextjs +# ./dev/dev.sh --profile core --profile agents # core + cloud-agent services +# ./dev/dev.sh --profile workers # postgres + all CF workers +# ./dev/dev.sh up postgres nextjs cloud-agent # specific services only + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="$REPO_ROOT/dev/docker-compose.dev.yml" + +# ── Preflight checks ───────────────────────────────────────────────── + +if ! command -v docker &>/dev/null; then + echo "❌ Docker is not installed. Please install Docker first." + echo " https://docs.docker.com/get-docker/" + exit 1 +fi + +if ! docker compose version &>/dev/null; then + echo "❌ Docker Compose v2 is required (docker compose, not docker-compose)." + echo " https://docs.docker.com/compose/install/" + exit 1 +fi + +if [ ! -d "$REPO_ROOT/node_modules" ]; then + if ! command -v pnpm &>/dev/null; then + echo "❌ pnpm is not installed and node_modules is missing." + echo " Install pnpm first: corepack enable && corepack prepare pnpm@10.27.0 --activate" + echo " Then run: pnpm install" + exit 1 + fi + echo "⚠️ node_modules not found. Running pnpm install first..." + (cd "$REPO_ROOT" && pnpm install) +fi + +if [ ! -f "$REPO_ROOT/.env" ]; then + echo "⚠️ No .env file found at repo root." + echo " Copy .env.example or create one before the Next.js service can start." +fi + +# ── Launch ──────────────────────────────────────────────────────────── + +# If no arguments provided, start everything with the "all" profile +if [ $# -eq 0 ]; then + echo "🚀 Starting all services..." + exec docker compose -f "$COMPOSE_FILE" --profile all up +else + echo "🚀 Starting services..." + exec docker compose -f "$COMPOSE_FILE" "$@" +fi diff --git a/dev/docker-compose.dev.yml b/dev/docker-compose.dev.yml new file mode 100644 index 000000000..2deb84604 --- /dev/null +++ b/dev/docker-compose.dev.yml @@ -0,0 +1,267 @@ +# Docker Compose dev orchestration for the full monorepo. +# +# Networking: All services share the default bridge network created by Docker +# Compose. Services can reach each other by their Compose service name (e.g., +# "postgres", "nextjs", "cloud-agent") via Docker's built-in DNS. This works +# on both macOS (Docker Desktop) and Linux — no network_mode: host needed. +# +# Because wrangler.jsonc files hardcode "localhost" for inter-service URLs +# (Hyperdrive connection strings, KILOCODE_BACKEND_BASE_URL, etc.), wrangler +# workers use dev/docker-wrangler-entrypoint.sh which creates a patched copy +# of wrangler.jsonc at startup, replacing "localhost" with Docker service names. +# +# The Next.js service overrides env vars directly via the `environment:` key +# so that POSTGRES_URL, CLOUD_AGENT_API_URL, etc. point to Docker service names. +# +# Usage: +# docker compose -f dev/docker-compose.dev.yml up # everything +# docker compose -f dev/docker-compose.dev.yml up postgres nextjs # core only +# docker compose -f dev/docker-compose.dev.yml up postgres cloud-agent # postgres + one worker +# docker compose -f dev/docker-compose.dev.yml up --profile core # postgres + nextjs +# docker compose -f dev/docker-compose.dev.yml up --profile core --profile agents # core + agents + +x-worker-base: &worker-base + build: + context: .. + dockerfile: dev/Dockerfile.dev + volumes: + - ..:/app + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped + +services: + # ── PostgreSQL (always starts — no profile) ───────────────────────── + postgres: + image: pgvector/pgvector:0.8.0-pg18 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - "127.0.0.1:5432:5432" + restart: unless-stopped + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 3s + retries: 5 + + # ── 1. Next.js backend (port 3000) ────────────────────────────────── + nextjs: + <<: *worker-base + profiles: ["core", "all"] + ports: + - "127.0.0.1:3000:3000" + env_file: + - ../.env + # Override localhost URLs so Next.js can reach other services on the + # Docker bridge network by service name. + environment: + - POSTGRES_URL=postgres://postgres:postgres@postgres:5432/postgres + - CLOUD_AGENT_API_URL=http://cloud-agent:8788 + - WEBHOOK_AGENT_URL=http://cloudflare-webhook-agent-ingest:8793 + depends_on: + postgres: + condition: service_healthy + command: ["pnpm", "dev"] + + # ── 2. cloud-agent (port 8788) ───────────────────────────────────── + # Uses docker-wrangler-entrypoint.sh to patch wrangler.jsonc localhost + # references (KILOCODE_BACKEND_BASE_URL, Hyperdrive, etc.) to Docker + # service names before starting wrangler dev. + cloud-agent: + <<: *worker-base + profiles: ["agents", "workers", "all"] + ports: + - "127.0.0.1:8788:8788" + # Container-backed Durable Objects: Wrangler needs access to the host + # Docker daemon to spawn sandbox containers. This grants the container + # full control over the host's Docker — acceptable for local dev, but + # must never be used in production. + volumes: + - ..:/app + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloud-agent --env dev --port 8788 --ip 0.0.0.0 + + # ── 3. cloud-agent-next (port 8794) ──────────────────────────────── + cloud-agent-next: + <<: *worker-base + profiles: ["agents", "all"] + ports: + - "127.0.0.1:8794:8794" + # Container-backed DOs — needs host Docker daemon (see cloud-agent comment). + volumes: + - ..:/app + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloud-agent-next --env dev --port 8794 --ip 0.0.0.0 + + # ── 4. cloudflare-ai-attribution (port 8787) ─────────────────────── + cloudflare-ai-attribution: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8787:8787" + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-ai-attribution --env dev --port 8787 --ip 0.0.0.0 + + # ── 5. cloudflare-app-builder (port 8790) ────────────────────────── + cloudflare-app-builder: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8790:8790" + # Container-backed DOs — needs host Docker daemon (see cloud-agent comment). + volumes: + - ..:/app + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-app-builder --port 8790 --ip 0.0.0.0 + + # ── 6. cloudflare-auto-fix-infra (port 8796) ────────────────────── + cloudflare-auto-fix-infra: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8796:8796" + depends_on: + postgres: + condition: service_healthy + cloud-agent: + condition: service_started + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-auto-fix-infra --port 8796 --ip 0.0.0.0 + + # ── 7. cloudflare-auto-triage-infra (port 8791) ─────────────────── + cloudflare-auto-triage-infra: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8791:8791" + depends_on: + postgres: + condition: service_healthy + cloud-agent: + condition: service_started + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-auto-triage-infra --port 8791 --ip 0.0.0.0 + + # ── 8. cloudflare-code-review-infra (port 8789) ─────────────────── + cloudflare-code-review-infra: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8789:8789" + depends_on: + postgres: + condition: service_healthy + cloud-agent: + condition: service_started + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-code-review-infra --port 8789 --ip 0.0.0.0 + + # ── 9. cloudflare-db-proxy (port 8797) ───────────────────────────── + cloudflare-db-proxy: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8797:8797" + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-db-proxy --port 8797 --ip 0.0.0.0 + + # ── 10. cloudflare-deploy-infra/builder (port 8798) ──────────────── + cloudflare-deploy-builder: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8798:8798" + # Container-backed DOs — needs host Docker daemon (see cloud-agent comment). + volumes: + - ..:/app + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-deploy-infra/builder --port 8798 --ip 0.0.0.0 + + # ── 11. cloudflare-deploy-infra/dispatcher (port 8799) ───────────── + cloudflare-deploy-dispatcher: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8799:8799" + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-deploy-infra/dispatcher --port 8799 --ip 0.0.0.0 + + # ── 12. cloudflare-session-ingest (port 8800) ───────────────────── + cloudflare-session-ingest: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8800:8800" + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-session-ingest --port 8800 --ip 0.0.0.0 + + # ── 13. cloudflare-o11y (port 8801) ──────────────────────────────── + cloudflare-o11y: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8801:8801" + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-o11y --port 8801 --ip 0.0.0.0 + + # ── 14. cloudflare-webhook-agent-ingest (port 8793) ──────────────── + cloudflare-webhook-agent-ingest: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8793:8793" + depends_on: + postgres: + condition: service_healthy + cloud-agent: + condition: service_started + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-webhook-agent-ingest --env dev --port 8793 --ip 0.0.0.0 + + # ── 15. cloudflare-git-token-service (port 8802) ────────────────── + cloudflare-git-token-service: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8802:8802" + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh cloudflare-git-token-service --port 8802 --ip 0.0.0.0 + + # ── 16. kiloclaw (port 8795) ─────────────────────────────────────── + kiloclaw: + <<: *worker-base + profiles: ["workers", "all"] + ports: + - "127.0.0.1:8795:8795" + depends_on: + postgres: + condition: service_healthy + command: /app/dev/docker-wrangler-entrypoint.sh kiloclaw --port 8795 --ip 0.0.0.0 + +volumes: + postgres_data: diff --git a/dev/docker-wrangler-entrypoint.sh b/dev/docker-wrangler-entrypoint.sh new file mode 100755 index 000000000..395581be7 --- /dev/null +++ b/dev/docker-wrangler-entrypoint.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Entrypoint for wrangler-based services in Docker Compose. +# +# Problem: wrangler.jsonc files hardcode "localhost" for inter-service URLs +# (e.g., Hyperdrive localConnectionString, KILOCODE_BACKEND_BASE_URL). When +# running in Docker Compose with bridge networking, "localhost" inside a +# container refers to that container itself, not other services. +# +# Solution: This script creates a temporary copy of the worker's wrangler.jsonc, +# replaces "localhost" references with Docker Compose service names (which +# resolve via Docker's built-in DNS on the shared bridge network), then runs +# wrangler dev using the patched config. +# +# Usage (in docker-compose.dev.yml): +# command: /app/dev/docker-wrangler-entrypoint.sh [wrangler-dev-args...] +# +# Example: +# command: /app/dev/docker-wrangler-entrypoint.sh cloud-agent --env dev --port 8788 --ip 0.0.0.0 + +set -euo pipefail + +if [ $# -lt 1 ]; then + echo "Usage: docker-wrangler-entrypoint.sh [wrangler-dev-args...]" + echo " e.g. docker-wrangler-entrypoint.sh cloud-agent --env dev --port 8788 --ip 0.0.0.0" + exit 1 +fi + +WORKER_DIR="$1" +shift # remaining args are passed to wrangler dev + +WORKER_PATH="/app/${WORKER_DIR}" +ORIGINAL_CONFIG="${WORKER_PATH}/wrangler.jsonc" +PATCHED_CONFIG="/tmp/wrangler-docker.jsonc" + +if [ ! -f "$ORIGINAL_CONFIG" ]; then + echo "❌ wrangler.jsonc not found at ${ORIGINAL_CONFIG}" + exit 1 +fi + +# Create a patched copy with Docker service names instead of localhost. +# These replacements map localhost ports to the corresponding Docker Compose +# service names (which resolve via DNS on the shared bridge network): +# +# localhost:3000 → nextjs:3000 (Next.js backend) +# localhost:5432 → postgres:5432 (PostgreSQL via Hyperdrive) +# localhost:8787 → cloudflare-ai-attribution:8787 +# localhost:8788 → cloud-agent:8788 +# localhost:8793 → cloudflare-webhook-agent-ingest:8793 +# localhost:8794 → cloud-agent-next:8794 +# localhost:8800 → cloudflare-session-ingest:8800 +sed \ + -e 's|localhost:3000|nextjs:3000|g' \ + -e 's|localhost:5432|postgres:5432|g' \ + -e 's|localhost:8787|cloudflare-ai-attribution:8787|g' \ + -e 's|localhost:8788|cloud-agent:8788|g' \ + -e 's|localhost:8793|cloudflare-webhook-agent-ingest:8793|g' \ + -e 's|localhost:8794|cloud-agent-next:8794|g' \ + -e 's|localhost:8800|cloudflare-session-ingest:8800|g' \ + "$ORIGINAL_CONFIG" > "$PATCHED_CONFIG" + +cd "$WORKER_PATH" + +# Run predev script if it exists (e.g., cloud-agent builds its wrapper via bun) +if node -e "const p=require('./package.json'); process.exit(p.scripts?.predev ? 0 : 1)" 2>/dev/null; then + echo "▶ Running predev script for ${WORKER_DIR}..." + pnpm run predev +fi + +exec wrangler dev --config "$PATCHED_CONFIG" "$@"