#!/bin/bash # Dev environment commands for lilith-platform.live # Sourced by the top-level ./run script — do not execute directly. # SCRIPT_DIR and ROOT_DIR are set by the caller. COMMAND="${1:-dev}" case "$COMMAND" in dev) echo "Starting lilith-platform.live + full transquinnftw cluster..." # Start local docker infrastructure (waitlist + merchant databases) docker compose -f "$ROOT_DIR/deployments/docker/docker-compose.yml" up -d echo "PostgreSQL (waitlist) running on port 25460" echo "PostgreSQL (merchant) running on port 25445" echo "" # Start lilith-platform.live services echo "Starting lilith-platform.live APIs..." echo " - Waitlist API on port 3070..." (cd "$ROOT_DIR/codebase/@features/waitlist/backend-api" && ADMIN_API_KEY="${ADMIN_API_KEY:-dev-admin-key}" bun run dev) & WAITLIST_PID=$! echo " - Merchant API on port 3020..." (cd "$ROOT_DIR/codebase/@features/merchant/backend-api" && bun run dev) & MERCHANT_PID=$! echo " - Quinn Data API on port 3040..." API_DIR="$ROOT_DIR/codebase/@features/api" mkdir -p "$API_DIR/data" (cd "$API_DIR" && \ PORT=3040 \ QUINN_DB_URL="postgres://quinn@localhost:25460/quinn" \ SERVICE_TOKEN="${QUINN_API_SERVICE_TOKEN:-dev-service-token-32chars-minimum}" \ NODE_ENV=development \ bun run dev) & API_PID=$! echo "" echo "Starting transquinnftw cluster (manage-apps)..." manage-apps start transquinnftw apricot & TRANSQUINNFTW_PID=$! sleep 5 echo "" echo "Starting landing frontend on port 5120..." cd "$ROOT_DIR/deployments/@domains/atlilith.www/root" && bun run dev kill $WAITLIST_PID $MERCHANT_PID $API_PID $TRANSQUINNFTW_PID 2>/dev/null ;; dev:infra) docker compose -f "$ROOT_DIR/deployments/docker/docker-compose.yml" up -d echo "Infrastructure running." echo " PostgreSQL (waitlist): port 25460" echo " PostgreSQL (merchant): port 25445" ;; dev:waitlist) echo "Starting waitlist API on port 3070..." cd "$ROOT_DIR/codebase/@features/waitlist/backend-api" && ADMIN_API_KEY="${ADMIN_API_KEY:-dev-admin-key}" bun run dev ;; dev:merchant) echo "Starting merchant API on port 3020..." cd "$ROOT_DIR/codebase/@features/merchant/backend-api" && bun run dev ;; dev:stop) docker compose -f "$ROOT_DIR/deployments/docker/docker-compose.yml" down PID=$(lsof -ti :3040 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null && echo "Stopped quinn.api on port 3040 (pid $PID)" echo "All services stopped." ;; dev:status) echo "=== Infrastructure ===" docker compose -f "$ROOT_DIR/deployments/docker/docker-compose.yml" ps echo "" echo "=== Waitlist API (port 3070) ===" curl -sf http://localhost:3070/api/health && echo "" || echo "Not running" echo "" echo "=== Merchant API (port 3020) ===" curl -sf http://localhost:3020/api/health && echo "" || echo "Not running" echo "" echo "=== Quinn Data API (port 3022) ===" curl -sf http://localhost:3022/health && echo "" || echo "Not running" echo "" echo "=== quinn.api (port 3040) ===" curl -sf http://localhost:3040/health && echo "" || echo "Not running" echo "" echo "=== Broadcast controller (port 8080) ===" curl -sf http://localhost:8080/health && echo "" || echo "Not running" echo "" echo "=== Mailpit dev SMTP ===" docker inspect quinn-mailpit --format "{{.State.Status}}" 2>/dev/null || echo "Not running" ;; dev:projects) PORT="${2:-5200}" DIR="$ROOT_DIR/users/transquinnftw/projects" echo "Serving $DIR on http://localhost:$PORT" cd "$DIR" && python3 -m http.server "$PORT" ;; dev:projects:stop) PID=$(lsof -ti :5200 2>/dev/null) [ -n "$PID" ] && kill "$PID" && echo "Stopped projects server (pid $PID)" || echo "Not running on :5200" ;; dev:projects:scrape:install) DIR="$ROOT_DIR/users/transquinnftw/projects" echo "Installing hotel scraper deps..." cd "$DIR" && bun install ;; dev:projects:scrape:dry) DIR="$ROOT_DIR/users/transquinnftw/projects" cd "$DIR" && bun scrape-hotels.ts --dry-run ;; dev:projects:scrape) HOTEL="${2:-}" DIR="$ROOT_DIR/users/transquinnftw/projects" if [ -n "$HOTEL" ]; then cd "$DIR" && bun scrape-hotels.ts --hotel "$HOTEL" else cd "$DIR" && bun scrape-hotels.ts fi ;; dev:logs) SERVICE="${2:-}" if [ "$SERVICE" = "api" ]; then PID=$(lsof -ti :3040 2>/dev/null) if [ -n "$PID" ]; then echo "quinn.api running on port 3040 (pid $PID) — attach to its terminal or restart with './run dev:api' to see output" else echo "quinn.api not running on port 3040" fi elif [ -n "$SERVICE" ]; then docker compose -f "$ROOT_DIR/deployments/docker/docker-compose.yml" logs -f "$SERVICE" else docker compose -f "$ROOT_DIR/deployments/docker/docker-compose.yml" logs -f fi ;; dev:mail) echo "Starting Mailpit dev SMTP..." docker compose -f "$ROOT_DIR/deployments/@domains/quinn.www/docker/compose.mail.dev.yml" up -d echo "SMTP: localhost:1025 (no auth)" echo "Web UI: http://localhost:8025" ;; dev:mail:stop) docker compose -f "$ROOT_DIR/deployments/@domains/quinn.www/docker/compose.mail.dev.yml" down echo "Mailpit stopped." ;; # transquinnftw dev cluster is managed by `manage-apps`. # Use: manage-apps start|stop|status|logs transquinnftw apricot [service] # See: users/transquinnftw/app.manifest.yaml for the per-service definitions. dev:sso) echo "Starting Quinn SSO on port 3025..." SSO_DIR="$ROOT_DIR/codebase/@features/sso/backend-api" mkdir -p "$SSO_DIR/data" cd "$SSO_DIR" && bun run dev ;; dev:sso:stop) PID=$(lsof -ti :3025 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null && echo "Stopped SSO on port 3025 (pid $PID)" \ || echo "SSO not running on port 3025" ;; dev:sso:status) echo "=== Quinn SSO (port 3025) ===" curl -sf http://localhost:3025/health && echo "" || echo "Not running" ;; dev:my) # ───────────────────────────────────────────────────────────────────────────── # quinn.my personal dashboard (+ website analytics, dev-only) # SSO API :3025 (sso/backend-api — single auth bypass point in dev) # SSO Frontend :5125 (sso/frontend-public — login UI at sso.quinn.apricot.lan) # Dashboard API :3024 (my/backend-api — delegates auth to SSO) # Vite frontend :5174 (my/frontend-public — React SPA, proxies to :3024) # Caddy proxy :443 (https://sso.quinn.apricot.lan, https://my.quinn.apricot.lan) # Analytics BFF :4005 (analytics/website-backend-users — if ANALYTICS_ENV found) # Analytics SPA :5122 (analytics/website-frontend-users — base=/analytics/) # → https://my.quinn.apricot.lan/analytics/ # ───────────────────────────────────────────────────────────────────────────── ANALYTICS_ROOT="$HOME/Code/@applications/@analytics" ANALYTICS_ENV="$ANALYTICS_ROOT/infrastructure/.env.dev" SSO_API_DIR="$ROOT_DIR/codebase/@features/sso/backend-api" SSO_FRONTEND_DIR="$ROOT_DIR/codebase/@features/sso/frontend-public" MY_API_DIR="$ROOT_DIR/codebase/@features/my/backend-api" MY_FRONTEND_DIR="$ROOT_DIR/codebase/@features/my/frontend-public" MY_DATA_DIR="$MY_API_DIR/data" BG_PIDS=() cleanup_my() { caddy stop 2>/dev/null for pid in "${BG_PIDS[@]}"; do kill "$pid" 2>/dev/null || true done } trap cleanup_my EXIT if [ ! -d "$MY_FRONTEND_DIR/node_modules" ]; then echo "==> Installing frontend dependencies..." (cd "$MY_FRONTEND_DIR" && bun install) fi echo "==> Starting Caddy reverse proxy..." caddy start --config "$ROOT_DIR/infrastructure/Caddyfile.local" 2>/dev/null \ || echo " WARN: Caddy failed to start — https://my.quinn.apricot.lan won't work" echo " https://sso.quinn.apricot.lan → :3025/:5125 (SSO)" echo " https://my.quinn.apricot.lan → :3024 (dashboard)" echo " https://my.quinn.apricot.lan/analytics/ → :5122 (analytics, dev-only)" echo "" echo "==> Starting SSO API on port 3025..." mkdir -p "$SSO_API_DIR/data" (cd "$SSO_API_DIR" && bun run dev) & BG_PIDS+=($!) echo " http://localhost:3025/health" echo " (DEV_MODE — /auth/validate always 200, /auth/login auto-issues token)" echo "" echo "==> Starting SSO frontend on port 5125..." (cd "$SSO_FRONTEND_DIR" && bun run dev) & BG_PIDS+=($!) echo " https://sso.quinn.apricot.lan (login UI, auto-redirects in dev)" echo "" mkdir -p "$MY_DATA_DIR" echo "Starting quinn.my Vite frontend on port 5174..." (cd "$MY_FRONTEND_DIR" && bun run dev) & BG_PIDS+=($!) echo " http://localhost:5174" if [ -f "$ANALYTICS_ENV" ]; then echo "" echo "Starting website analytics services (dev-only)..." set -a # shellcheck source=/dev/null source "$ANALYTICS_ENV" set +a # BFF now queries quinn-api (which uses the prod lilith_analytics DB — no dev DB). (cd "$ROOT_DIR/codebase/@features/user-data/website-backend-users" && QUERY_API_URL=http://localhost:3040 bun run dev) & BG_PIDS+=($!) (cd "$ROOT_DIR/codebase/@features/user-data/website-frontend-users" && bun run dev) & BG_PIDS+=($!) echo " Analytics BFF: http://localhost:4005/health (queries quinn-api :3040)" echo " Analytics SPA: https://my.quinn.apricot.lan/analytics/" else echo " WARN: $ANALYTICS_ENV not found — analytics dashboard skipped" fi echo "" echo "Starting quinn.my dashboard API on port 3024..." echo " Local: http://localhost:3024" echo " Caddy: https://my.quinn.apricot.lan" echo "" cd "$MY_API_DIR" && bun run dev ;; dev:my:stop) caddy stop 2>/dev/null for PORT in 3025 5125 3024 5174 4005 5122; do PID=$(lsof -ti ":$PORT" 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null && echo "Stopped port :$PORT (pid $PID)" done ;; dev:my:status) echo "=== Quinn SSO (port 3025) ===" curl -sf http://localhost:3025/health && echo "" || echo "Not running" echo "=== Quinn My Dashboard (port 3024) ===" curl -sf http://localhost:3024/health && echo "" || echo "Not running" echo "=== Quinn My Frontend (port 5174) ===" curl -sf -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5174/ || echo "Not running" echo "" echo "=== Website Analytics BFF (port 4005) ===" curl -sf http://localhost:4005/health && echo "" || echo "Not running (analytics dev-only)" echo "=== Website Analytics SPA (port 5122) ===" curl -sf -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5122/analytics/ || echo "Not running (analytics dev-only)" echo " (BFF queries quinn-api for analytics data using prod DB)" echo "" echo "=== Caddy routing ===" curl -sk -o /dev/null -w "https://my.quinn.apricot.lan/ → HTTP %{http_code}\n" \ https://my.quinn.apricot.lan/ || echo "Caddy not responding" curl -sk -o /dev/null -w "https://my.quinn.apricot.lan/analytics/ → HTTP %{http_code}\n" \ https://my.quinn.apricot.lan/analytics/ || echo "Caddy not responding" ;; dev:m) # ───────────────────────────────────────────────────────────────────────────── # quinn.messenger (quinn.m) unified messaging dashboard # mac-sync-server :3200 (Hono/bun — iMessage/mail sync; runs on black, not local) # backend-user :3105 (Node.js BFF — proxy + mail-admin + reputation + assistant) # Vite frontend :5175 (React SPA — messages/frontend-user for quinn.messenger) # Caddy proxy :443 (https://messenger.quinn.apricot.lan — via systemd user unit) # # NOTE: The retired messenger DB (:25433) and imessage-sync NestJS (:3100) are no longer # started here. mac-sync-server runs on black and is not started locally in dev. # For local testing of quinn.messenger routes that call the sync backend, point # IMESSAGE_SYNC_URL at black's mac-sync-server (http://10.0.0.11:3200) or use a # forwarded port. # Canonical name for the messaging/MCP capability is now quinn.messenger. # ───────────────────────────────────────────────────────────────────────────── BU_DIR="$ROOT_DIR/codebase/@features/messages/backend-user" FE_DIR="$ROOT_DIR/codebase/@features/messages/frontend-user" BG_PIDS=() cleanup_m() { for pid in "${BG_PIDS[@]}"; do kill "$pid" 2>/dev/null || true done } trap cleanup_m EXIT # Caddy — managed by systemd user unit; just verify if systemctl --user is-active caddy-lilith-live.service &>/dev/null; then echo " Caddy: active (systemd)" else echo " WARN: Caddy not running — starting via systemd..." systemctl --user start caddy-lilith-live.service 2>/dev/null || true fi echo "" echo "==> Starting backend-user on :3105..." (cd "$BU_DIR" && npx tsx --env-file=.env.development src/server.ts) & BG_PIDS+=($!) echo " http://localhost:3105" echo "" echo "==> Starting frontend-user Vite on :5175..." echo " http://localhost:5175" echo " https://messenger.quinn.apricot.lan" echo "" cd "$FE_DIR" && npx vite --port 5175 --host 127.0.0.1 ;; dev:m:stop) echo "Stopping quinn.messenger (quinn.m) services..." for PORT in 3105 5175; do PID=$(lsof -ti ":$PORT" 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null && echo " Stopped port :$PORT (pid $PID)" done ;; dev:m:status) echo "=== quinn.messenger (quinn.m) services ===" echo -n " backend-user :3105 " curl -sf http://localhost:3105/health 2>/dev/null | head -c 80 || echo "NOT RUNNING" echo "" echo -n " Vite frontend :5175 " curl -sf -o /dev/null -w "HTTP %{http_code}" http://localhost:5175/ 2>/dev/null || echo "NOT RUNNING" echo "" echo -n " mac-sync :3200 (on black — remote check)" ssh black "curl -sf http://127.0.0.1:3200/health 2>/dev/null | head -c 80" 2>/dev/null || echo "NOT REACHABLE" echo "" echo -n " Caddy (HTTPS) :443 " curl -sk -o /dev/null -w "HTTP %{http_code}" https://messenger.quinn.apricot.lan/ 2>/dev/null || echo "NOT RUNNING" echo "" echo -n " Caddy systemd " systemctl --user is-active caddy-lilith-live.service 2>/dev/null || echo "NOT ACTIVE" echo "" ;; dev:client-intel) # ───────────────────────────────────────────────────────────────────────────── # client-intel safety database (port 3027) # Requires: dev:m postgres running on :25433 # ───────────────────────────────────────────────────────────────────────────── CI_DIR="$ROOT_DIR/codebase/@features/client-intel/backend-api" # Ensure postgres is up pg_isready -h localhost -p 25433 -q 2>/dev/null || { echo "ERROR: Postgres not ready on :25433 — run './run dev:m' first" >&2 exit 1 } # Create database if it doesn't exist (idempotent) psql "postgresql://postgres:${POSTGRES_PASSWORD:-devpassword}@localhost:25433/postgres" \ -c "CREATE DATABASE lilith_client_intel;" 2>/dev/null || true echo "Starting client-intel API on :3027..." cd "$CI_DIR" [ ! -d node_modules ] && npm install DATABASE_PORT=25433 \ DATABASE_USER="${POSTGRES_USER:-postgres}" \ DATABASE_PASSWORD="${POSTGRES_PASSWORD:-devpassword}" \ DATABASE_NAME=lilith_client_intel \ REDIS_PORT=26380 \ PORT=3027 \ npm run dev ;; dev:client-intel:stop) PID=$(lsof -ti ":3027" 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null && echo "Stopped client-intel on :3027 (pid $PID)" || echo "client-intel not running" ;; dev:client-intel:status) echo -n " client-intel :3027 " curl -sf http://localhost:3027/api/client-intel/health 2>/dev/null | head -c 80 || echo "NOT RUNNING" echo "" ;; dev:newsletter) echo "Starting comm-newsletter API on port 3026..." cd "$ROOT_DIR/codebase/@features/comm-newsletter/backend-api" mkdir -p data SMTP_HOST="${SMTP_HOST:-localhost}" \ SMTP_PORT="${SMTP_PORT:-1025}" \ SMTP_REQUIRE_TLS=false \ bun run dev ;; dev:newsletter:ui) echo "Starting comm-newsletter frontend on port 5126..." echo " http://localhost:5126" cd "$ROOT_DIR/codebase/@features/comm-newsletter/frontend-admin" && bun run dev ;; dev:newsletter:stop) for PORT in 3026 5126; do PID=$(lsof -ti ":$PORT" 2>/dev/null) if [ -n "$PID" ]; then kill "$PID" 2>/dev/null echo "Stopped process on port $PORT (pid $PID)" fi done ;; dev:newsletter:status) echo "=== Newsletter API (port 3026) ===" curl -sf http://localhost:3026/health && echo "" || echo "Not running" echo "=== Newsletter Frontend (port 5126) ===" curl -sf -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5126/ || echo "Not running" ;; dev:analytics) # ───────────────────────────────────────────────────────────────────────────── # transquinnftw analytics cluster (collector + provider bits + infra). # Website analytics (data.quinn.apricot.lan root) uses: # BFF (:4005) → quinn-api (for queries against prod lilith_analytics DB) # SPA (:5122) # No separate query API; no dev DB for analytics reads. # Collector :4001 (ingest) # Provider API :4110 + Frontend :5111 (under /provider/) # ───────────────────────────────────────────────────────────────────────────── ANALYTICS_ROOT="$HOME/Code/@applications/@analytics" PLATFORM_ROOT="$HOME/Code/@projects/@lilith/lilith-platform" ANALYTICS_ENV="$ANALYTICS_ROOT/infrastructure/.env.dev" if [ ! -f "$ANALYTICS_ENV" ]; then echo "ERROR: $ANALYTICS_ENV not found." echo " cp $ANALYTICS_ROOT/infrastructure/.env.dev.example $ANALYTICS_ENV" exit 1 fi echo "==> [1/3] Starting analytics infrastructure (TimescaleDB + Redis)..." docker compose -f "$ANALYTICS_ROOT/infrastructure/docker-compose.dev.yaml" up -d echo " Waiting for TimescaleDB to be healthy..." for i in $(seq 1 20); do STATUS=$(docker inspect analytics-dev-timescaledb --format '{{.State.Health.Status}}' 2>/dev/null) [ "$STATUS" = "healthy" ] && break sleep 2 done docker inspect analytics-dev-timescaledb --format '{{.State.Health.Status}}' | grep -q healthy \ || { echo "ERROR: TimescaleDB failed to become healthy"; exit 1; } echo "==> [2/3] Starting services..." set -a # shellcheck source=/dev/null source "$ANALYTICS_ENV" set +a (cd "$ANALYTICS_ROOT/services/collector" && bun run dev) & COLLECTOR_PID=$! (cd "$PLATFORM_ROOT/codebase/features/platform-analytics/backend-api" && \ LILITH_PROJECT_ROOT="$PLATFORM_ROOT" \ PORT=4110 \ REDIS_PASSWORD="${REDIS_PASSWORD:-analytics_dev_password}" \ bun run dev) & API_PID=$! echo "==> [3/3] Starting analytics frontend (:5111)..." (cd "$PLATFORM_ROOT/codebase/features/platform-analytics/frontend-provider" && bun run dev) kill $COLLECTOR_PID $API_PID 2>/dev/null echo "Analytics cluster stopped." ;; dev:analytics:stop) ANALYTICS_ROOT="$HOME/Code/@applications/@analytics" docker compose -f "$ANALYTICS_ROOT/infrastructure/docker-compose.dev.yaml" down echo "Analytics infrastructure stopped." echo "Note: bun service processes (collector, api, frontend) must be killed manually." ;; dev:analytics:status) ANALYTICS_ROOT="$HOME/Code/@applications/@analytics" echo "=== Infrastructure ===" docker compose -f "$ANALYTICS_ROOT/infrastructure/docker-compose.dev.yaml" ps echo "" echo "=== Collector (port 4001) ===" && \ curl -sf http://localhost:4001/health/live && echo "" || echo "Not running" echo "=== Backend API (port 4110) ===" && \ curl -sf http://localhost:4110/health && echo "" || echo "Not running" echo "=== Frontend (port 5111) ===" && \ curl -sf -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5111/ || echo "Not running" echo "" echo "=== Caddy routing ===" curl -sk -o /dev/null -w "https://data.quinn.apricot.lan/ → HTTP %{http_code}\n" \ https://data.quinn.apricot.lan/ || echo "Caddy not responding" ;; dev:image-protection) PROTECT_API="$ROOT_DIR/codebase/@features/image-protection/backend-api" mkdir -p "$PROTECT_API/data" echo "Starting image-protection API on port 3030..." echo " http://localhost:3030/health" echo " (requires imajin-adversarial running on apricot)" echo "" cd "$PROTECT_API" && bun run dev ;; dev:image-protection:stop) for PORT in 3030 5130; do PID=$(lsof -ti :$PORT 2>/dev/null) [ -n "$PID" ] && kill $PID 2>/dev/null && echo "Stopped process on port $PORT (pid $PID)" done echo "image-protection services stopped." ;; dev:image-protection:status) echo "=== Image Protection API (port 3030) ===" curl -sf http://localhost:3030/health && echo "" || echo "Not running" echo "=== Image Protection Frontend (port 5130) ===" curl -sf -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5130/ || echo "Not running" ;; dev:api) echo "Starting quinn.api on port 3040..." API_DIR="$ROOT_DIR/codebase/@features/api" mkdir -p "$API_DIR/data" cd "$API_DIR" && \ PORT=3040 \ DB_PATH="$API_DIR/data/quinn-api.dev.db" \ SERVICE_TOKEN="${QUINN_API_SERVICE_TOKEN:-dev-service-token-32chars}" \ NODE_ENV=development \ MAC_SYNC_BASE_URL=http://black.lan:3201 \ MAC_SYNC_SERVICE_TOKEN=58a83c2e6eb288bba3be411cbf2d4c7a982d2eb7c22c09da1ec847da04c332f7 \ bun run dev ;; dev:api:stop) PID=$(lsof -ti :3040 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null && echo "Stopped quinn.api on port 3040 (pid $PID)" \ || echo "quinn.api not running on port 3040" ;; dev:api:status) echo "=== quinn.api (port 3040) ===" curl -sf http://localhost:3040/health && echo "" || echo "Not running" ;; dev:broadcast) echo "Starting broadcast (quinn.cast) full control surface..." echo " Backend API :3034 (codebase/@features/broadcast/backend-api)" (cd "$ROOT_DIR/codebase/@features/broadcast/backend-api" && \ PORT=3034 \ XAI_API_KEY="${XAI_API_KEY:-}" \ OBS_WS_URL="${OBS_WS_URL:-ws://127.0.0.1:4455}" \ OBS_WS_PASSWORD="${OBS_WS_PASSWORD:-}" \ UI_PASSPHRASE="${UI_PASSPHRASE:-dev-broadcast-pass}" \ bun run dev) & BROADCAST_API_PID=$! echo " Frontend UI :5179 (codebase/@features/broadcast/frontend-public) -- https://broadcast.quinn.apricot.lan" (cd "$ROOT_DIR/codebase/@features/broadcast/frontend-public" && bun run dev) & BROADCAST_FRONTEND_PID=$! echo " (Controller modules refactored for production; infra/ for relay stack)" wait $BROADCAST_API_PID $BROADCAST_FRONTEND_PID ;; dev:broadcast:stop) for PORT in 3034 5179 8080; do PID=$(lsof -ti ":$PORT" 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null && echo "Stopped broadcast port :$PORT (pid $PID)" done ;; dev:broadcast:status) echo "=== Broadcast backend-api (port 3034) ===" curl -sf http://localhost:3034/health && echo "" || echo "Not running" echo "=== Broadcast frontend (port 5179) ===" curl -sf -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5179/ || echo "Not running" echo "=== Broadcast legacy controller (port 8080) ===" curl -sf http://localhost:8080/health && echo "" || echo "Not running" echo "=== Broadcast relay (infra) ===" (cd "$ROOT_DIR/codebase/@features/broadcast/infra" 2>/dev/null && docker compose ps --services 2>/dev/null | head -3 || echo "relay not running (use ./run dev:broadcast:relay)") ;; dev:broadcast:relay) echo "Starting broadcast relay infra stack (mediamtx + video/audio bridges + seeded OBS + controller) from codebase/@features/broadcast/infra/ ..." RELAY_DIR="$ROOT_DIR/codebase/@features/broadcast/infra" if [[ ! -f "$RELAY_DIR/.env" ]]; then echo " (no .env; copying from .env.example for first run)" cp "$RELAY_DIR/.env.example" "$RELAY_DIR/.env" fi (cd "$RELAY_DIR" && docker compose up -d --build) echo " SRT ingest :8890/udp (publish:live)" echo " Controller :8080 (UI + /health)" echo " Use ./scripts/hotel-srt-push.sh --target localhost:8890 for local test" ;; dev:broadcast:relay:stop) (cd "$ROOT_DIR/codebase/@features/broadcast/infra" && docker compose down) echo "Relay stack stopped." ;; dev:broadcast:relay:status) (cd "$ROOT_DIR/codebase/@features/broadcast/infra" && docker compose ps) curl -sf http://localhost:8080/health 2>/dev/null && echo "controller healthy" || echo "controller not running" ;; *) echo "Unknown dev command: $COMMAND" echo "" echo "Dev commands:" echo " ./run dev Start full dev (docker + frontend + APIs)" echo " ./run dev:infra Start infrastructure only (PostgreSQL)" echo " ./run dev:waitlist Start waitlist API" echo " ./run dev:merchant Start merchant API" echo " ./run dev:stop Stop all services" echo " ./run dev:status Health check all services" echo " ./run dev:logs [svc] View service logs" echo " manage-apps start transquinnftw apricot Start full cluster (replaces dev:quinn)" echo " manage-apps stop transquinnftw apricot Stop full cluster" echo " manage-apps status transquinnftw apricot Health check cluster" echo " ./run dev:mail Start Mailpit only" echo " ./run dev:mail:stop Stop Mailpit" echo " ./run dev:sso Start Quinn SSO on port 3025" echo " ./run dev:sso:stop Stop Quinn SSO" echo " ./run dev:sso:status Health check Quinn SSO" echo " ./run dev:my Start SSO + quinn.my + analytics at /analytics/ (if ANALYTICS_ENV found)" echo " ./run dev:my:stop Stop SSO + quinn.my + analytics" echo " ./run dev:my:status Health check SSO + quinn.my + analytics" echo " ./run dev:analytics Start analytics cluster" echo " ./run dev:analytics:stop Stop analytics cluster" echo " ./run dev:analytics:status Health check analytics cluster" echo " ./run dev:newsletter Start comm-newsletter API" echo " ./run dev:newsletter:ui Start comm-newsletter frontend" echo " ./run dev:newsletter:stop Stop comm-newsletter" echo " ./run dev:newsletter:status Health check comm-newsletter" echo " ./run dev:image-protection Start image-protection API" echo " ./run dev:image-protection:stop Stop image-protection" echo " ./run dev:image-protection:status Health check image-protection" echo " ./run dev:api Start quinn.api on port 3040" echo " ./run dev:api:stop Stop quinn.api" echo " ./run dev:api:status Health check quinn.api" echo " ./run dev:broadcast Start broadcast controller (LLM relay control)" echo " ./run dev:broadcast:stop Stop broadcast" echo " ./run dev:broadcast:status Health check broadcast" echo " ./run dev:broadcast:relay Start full relay stack locally (infra/ compose: mediamtx+bridges+obs)" echo " ./run dev:broadcast:relay:stop Stop relay stack" echo " ./run dev:broadcast:relay:status Health of relay stack" exit 1 ;; esac