diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..c3ae980f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,39 @@ +{ + "name": "Vibe-Trading", + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "20" + } + }, + "forwardPorts": [ + 8899, + 5899 + ], + "portsAttributes": { + "8899": { + "label": "Vibe-Trading API", + "onAutoForward": "notify", + "visibility": "private" + }, + "5899": { + "label": "Vibe-Trading Frontend", + "onAutoForward": "notify", + "visibility": "private" + } + }, + "postCreateCommand": "python -m pip install --upgrade pip && python -m pip install -e . && cd frontend && npm install", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ], + "settings": { + "python.defaultInterpreterPath": "/usr/local/bin/python" + } + } + } +} diff --git a/.gitignore b/.gitignore index efff56c3..7341d93f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,10 @@ agent/tests/*_result.json .ruff_cache/ frontend/dist/ frontend/tsconfig.tsbuildinfo +.vibe-dev/ +.vibe-record/ +.cache/ +.remotion/ # DuckDB data cache data/*.duckdb @@ -45,6 +49,9 @@ build/ # Local demo videos (too large for git; link to external host instead) assets/*.mp4 +# Local demo / promo production workspace +demo/ + # Manual E2E smoke tests — require live API keys (OpenRouter / yfinance), # would break CI if committed. Kept local; run explicitly when validating # a release. diff --git a/scripts/dev b/scripts/dev new file mode 100644 index 00000000..e375db2f --- /dev/null +++ b/scripts/dev @@ -0,0 +1,250 @@ +#!/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 < + +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" + nohup "$@" >"$(log_file "$service")" 2>&1 "$(pid_file "$service")" + 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" + pkill -TERM -P "$pid" 2>/dev/null || true + kill "$pid" 2>/dev/null || true + sleep 0.5 + pkill -KILL -P "$pid" 2>/dev/null || true + 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 +} + +cmd_up() { + start_service backend env PYTHONPATH="$ROOT/agent" "$PYTHON_BIN" "$ROOT/agent/cli.py" serve \ + --host "$BACKEND_HOST" \ + --port "$BACKEND_PORT" + start_service frontend bash -lc ' + set -euo pipefail + cd "$1" + if ! [[ -d node_modules ]]; then + npm install + fi + exec npm run dev -- --host "$2" --port "$3" + ' bash "$ROOT/frontend" "$FRONTEND_HOST" "$FRONTEND_PORT" + if ! wait_for_url backend "http://127.0.0.1:$BACKEND_PORT/health" 30 || \ + ! wait_for_url frontend "http://127.0.0.1:$FRONTEND_PORT" 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_service backend env PYTHONPATH="$ROOT/agent" "$PYTHON_BIN" "$ROOT/agent/cli.py" serve \ + --host "$BACKEND_HOST" \ + --port "$BACKEND_PORT" + wait_for_url backend "http://127.0.0.1:$BACKEND_PORT/health" 30 + ;; + frontend) + stop_service frontend + start_service frontend bash -lc ' + set -euo pipefail + cd "$1" + if ! [[ -d node_modules ]]; then + npm install + fi + exec npm run dev -- --host "$2" --port "$3" + ' bash "$ROOT/frontend" "$FRONTEND_HOST" "$FRONTEND_PORT" + wait_for_url frontend "http://127.0.0.1:$FRONTEND_PORT" 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")" + 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 <&2 + usage >&2 + exit 2 + ;; +esac