Move Research Goal entry into the chat composer: the + menu now switches the normal input into goal mode, sends create the session goal directly, and the active goal stays above the input as an inline chip with expandable criteria and evidence details. Stabilize the local dev launcher used for browser smoke tests by wiring custom backend ports into VITE_API_URL, launching long-lived process groups, cleaning up child processes reliably, and reporting actual frontend/backend status.
287 lines
6.9 KiB
Bash
287 lines
6.9 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
STATE_DIR="${VIBE_DEV_STATE_DIR:-$ROOT/.vibe-dev}"
|
|
LOG_DIR="$STATE_DIR/logs"
|
|
PID_DIR="$STATE_DIR/pids"
|
|
|
|
BACKEND_HOST="${VIBE_BACKEND_HOST:-127.0.0.1}"
|
|
BACKEND_PORT="${VIBE_BACKEND_PORT:-8899}"
|
|
FRONTEND_HOST="${VIBE_FRONTEND_HOST:-127.0.0.1}"
|
|
FRONTEND_PORT="${VIBE_FRONTEND_PORT:-5899}"
|
|
if [[ -n "${PYTHON:-}" ]]; then
|
|
PYTHON_BIN="$PYTHON"
|
|
elif [[ -x "$ROOT/.venv/bin/python" ]]; then
|
|
PYTHON_BIN="$ROOT/.venv/bin/python"
|
|
elif [[ -x "$ROOT/agent/.venv/bin/python" ]]; then
|
|
PYTHON_BIN="$ROOT/agent/.venv/bin/python"
|
|
else
|
|
PYTHON_BIN="python3"
|
|
fi
|
|
|
|
mkdir -p "$LOG_DIR" "$PID_DIR"
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: scripts/dev <command>
|
|
|
|
Commands:
|
|
up Start backend and frontend dev servers
|
|
stop Stop dev servers started by this script
|
|
restart [service] Restart backend, frontend, or all services
|
|
status Show process status and URLs
|
|
logs [service] Tail logs for backend, frontend, or all
|
|
open Open the frontend in the default browser
|
|
urls Print local dev URLs
|
|
|
|
Environment:
|
|
VIBE_BACKEND_PORT Backend port (default: 8899)
|
|
VIBE_FRONTEND_PORT Frontend port (default: 5899)
|
|
PYTHON Python binary (default: local .venv, then python3)
|
|
USAGE
|
|
}
|
|
|
|
pid_file() {
|
|
printf "%s/%s.pid" "$PID_DIR" "$1"
|
|
}
|
|
|
|
log_file() {
|
|
printf "%s/%s.log" "$LOG_DIR" "$1"
|
|
}
|
|
|
|
is_running() {
|
|
local service="$1"
|
|
local file
|
|
file="$(pid_file "$service")"
|
|
[[ -f "$file" ]] && kill -0 "$(cat "$file")" 2>/dev/null
|
|
}
|
|
|
|
start_service() {
|
|
local service="$1"
|
|
shift
|
|
if is_running "$service"; then
|
|
printf "%s already running (pid %s)\n" "$service" "$(cat "$(pid_file "$service")")"
|
|
return
|
|
fi
|
|
|
|
printf "starting %s...\n" "$service"
|
|
"$PYTHON_BIN" - "$(log_file "$service")" "$(pid_file "$service")" "$@" <<'PY'
|
|
import subprocess
|
|
import sys
|
|
|
|
log_path = sys.argv[1]
|
|
pid_path = sys.argv[2]
|
|
cmd = sys.argv[3:]
|
|
|
|
log = open(log_path, "ab", buffering=0)
|
|
proc = subprocess.Popen(
|
|
cmd,
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=log,
|
|
stderr=subprocess.STDOUT,
|
|
start_new_session=True,
|
|
close_fds=True,
|
|
)
|
|
with open(pid_path, "w", encoding="utf-8") as fh:
|
|
fh.write(str(proc.pid))
|
|
PY
|
|
printf "%s pid %s, log %s\n" "$service" "$(cat "$(pid_file "$service")")" "$(log_file "$service")"
|
|
}
|
|
|
|
stop_service() {
|
|
local service="$1"
|
|
local file
|
|
file="$(pid_file "$service")"
|
|
if ! [[ -f "$file" ]]; then
|
|
printf "%s not started by scripts/dev\n" "$service"
|
|
return
|
|
fi
|
|
|
|
local pid
|
|
pid="$(cat "$file")"
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
printf "stopping %s (pid %s)...\n" "$service" "$pid"
|
|
kill -TERM "-$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
|
|
sleep 0.5
|
|
kill -KILL "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
fi
|
|
rm -f "$file"
|
|
}
|
|
|
|
url_ok() {
|
|
local url="$1"
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -fsS --max-time 2 "$url" >/dev/null 2>&1
|
|
else
|
|
"$PYTHON_BIN" - "$url" >/dev/null 2>&1 <<'PY'
|
|
import sys
|
|
import urllib.request
|
|
|
|
urllib.request.urlopen(sys.argv[1], timeout=2).read(1)
|
|
PY
|
|
fi
|
|
}
|
|
|
|
wait_for_url() {
|
|
local service="$1"
|
|
local url="$2"
|
|
local attempts="${3:-30}"
|
|
|
|
printf "waiting for %s at %s" "$service" "$url"
|
|
for ((i = 1; i <= attempts; i++)); do
|
|
if url_ok "$url"; then
|
|
printf " ready\n"
|
|
return 0
|
|
fi
|
|
if ! is_running "$service"; then
|
|
printf "\n%s exited early; see %s\n" "$service" "$(log_file "$service")" >&2
|
|
return 1
|
|
fi
|
|
printf "."
|
|
sleep 1
|
|
done
|
|
printf "\n%s not ready after %ss; see %s\n" "$service" "$attempts" "$(log_file "$service")" >&2
|
|
return 1
|
|
}
|
|
|
|
service_url() {
|
|
case "$1" in
|
|
backend) printf "http://127.0.0.1:%s/health" "$BACKEND_PORT" ;;
|
|
frontend) printf "http://127.0.0.1:%s" "$FRONTEND_PORT" ;;
|
|
*) return 2 ;;
|
|
esac
|
|
}
|
|
|
|
start_backend() {
|
|
if ! is_running backend && url_ok "$(service_url backend)"; then
|
|
rm -f "$(pid_file backend)"
|
|
printf "backend already reachable at %s (external)\n" "$(service_url backend)"
|
|
return
|
|
fi
|
|
start_service backend env PYTHONPATH="$ROOT/agent" "$PYTHON_BIN" -c 'import cli, sys; raise SystemExit(cli.main(sys.argv[1:]))' serve \
|
|
--host "$BACKEND_HOST" \
|
|
--port "$BACKEND_PORT"
|
|
}
|
|
|
|
start_frontend() {
|
|
if ! is_running frontend && url_ok "$(service_url frontend)"; then
|
|
rm -f "$(pid_file frontend)"
|
|
printf "frontend already reachable at %s (external)\n" "$(service_url frontend)"
|
|
return
|
|
fi
|
|
start_service frontend bash -lc '
|
|
set -euo pipefail
|
|
cd "$1"
|
|
if ! [[ -d node_modules ]]; then
|
|
npm install
|
|
fi
|
|
export VITE_API_URL="${VITE_API_URL:-http://127.0.0.1:$4}"
|
|
exec npm run dev -- --host "$2" --port "$3"
|
|
' bash "$ROOT/frontend" "$FRONTEND_HOST" "$FRONTEND_PORT" "$BACKEND_PORT"
|
|
}
|
|
|
|
cmd_up() {
|
|
start_backend
|
|
start_frontend
|
|
if ! wait_for_url backend "$(service_url backend)" 30 || \
|
|
! wait_for_url frontend "$(service_url frontend)" 30; then
|
|
cmd_stop
|
|
exit 1
|
|
fi
|
|
cmd_urls
|
|
}
|
|
|
|
cmd_stop() {
|
|
stop_service frontend
|
|
stop_service backend
|
|
}
|
|
|
|
cmd_restart() {
|
|
local service="${1:-all}"
|
|
case "$service" in
|
|
backend)
|
|
stop_service backend
|
|
start_backend
|
|
wait_for_url backend "$(service_url backend)" 30
|
|
;;
|
|
frontend)
|
|
stop_service frontend
|
|
start_frontend
|
|
wait_for_url frontend "$(service_url frontend)" 30
|
|
;;
|
|
all)
|
|
cmd_stop
|
|
cmd_up
|
|
;;
|
|
*)
|
|
printf "unknown service: %s\n" "$service" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
cmd_status() {
|
|
for service in backend frontend; do
|
|
if is_running "$service"; then
|
|
printf "%-8s running pid=%s log=%s\n" "$service" "$(cat "$(pid_file "$service")")" "$(log_file "$service")"
|
|
elif url_ok "$(service_url "$service")"; then
|
|
printf "%-8s reachable external url=%s\n" "$service" "$(service_url "$service")"
|
|
else
|
|
printf "%-8s stopped\n" "$service"
|
|
fi
|
|
done
|
|
cmd_urls
|
|
}
|
|
|
|
cmd_logs() {
|
|
local service="${1:-all}"
|
|
case "$service" in
|
|
backend|frontend)
|
|
touch "$(log_file "$service")"
|
|
tail -f "$(log_file "$service")"
|
|
;;
|
|
all)
|
|
touch "$(log_file backend)" "$(log_file frontend)"
|
|
tail -f "$(log_file backend)" "$(log_file frontend)"
|
|
;;
|
|
*)
|
|
printf "unknown service: %s\n" "$service" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
cmd_open() {
|
|
local url="http://127.0.0.1:$FRONTEND_PORT"
|
|
if command -v open >/dev/null 2>&1; then
|
|
open "$url"
|
|
else
|
|
printf "%s\n" "$url"
|
|
fi
|
|
}
|
|
|
|
cmd_urls() {
|
|
cat <<URLS
|
|
Frontend: http://127.0.0.1:$FRONTEND_PORT
|
|
Backend: http://127.0.0.1:$BACKEND_PORT
|
|
API docs: http://127.0.0.1:$BACKEND_PORT/docs
|
|
URLS
|
|
}
|
|
|
|
case "${1:-}" in
|
|
up) cmd_up ;;
|
|
stop) cmd_stop ;;
|
|
restart) shift; cmd_restart "${1:-all}" ;;
|
|
status) cmd_status ;;
|
|
logs) shift; cmd_logs "${1:-all}" ;;
|
|
open) cmd_open ;;
|
|
urls) cmd_urls ;;
|
|
-h|--help|help|"") usage ;;
|
|
*)
|
|
printf "unknown command: %s\n\n" "$1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|