Files

1 line
44 KiB
JSON

{"repo": "caiyfa/deepferry", "files": {"dev.sh": {"c33995d9adb7f55cf996646ebee18f5d510cc9df": {"t": 1785745968.5632532, "n": 10017, "h": "fdbb4369ef5d34a8", "c": "#!/usr/bin/env bash\n#\n# DeepFerry — sidecar-first dev launcher (Phase 3 rewrite)\n#\n# Mental model: deepferry is a Tauri desktop app. The Python sidecar runs\n# locally (uv run), the webview is a Tauri window (npm run tauri dev).\n# Docker is for CI / integration tests, NOT the default dev loop.\n#\n# Modes (mutually exclusive — last one wins if multiple given):\n# ./dev.sh Smart start: sidecar (uv) + webview (Tauri)\n# ./dev.sh --sidecar-only ONLY start uv sidecar (foreground)\n# ./dev.sh --webview-only ONLY start Tauri webview (sidecar must be up)\n# ./dev.sh --docker-ci docker compose up sidecar + mysql + postgres\n# ./dev.sh -h | --help Show this help\n#\n# Optional modifier (not a mode):\n# --sidecar-port <N> Override sidecar port (default 8642)\n#\n# Ctrl+C cleans up only what this script started (sidecar PID, if any).\n# Docker containers are NEVER auto-stopped — use `docker compose down`.\n\nset -euo pipefail\n\n# ── Constants ──────────────────────────────────────────────────────────\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nAPP_SHELL_DIR=\"$ROOT_DIR/app-shell\"\nCONFIG_TOML=\"$ROOT_DIR/config.toml\"\nHOST_DATA_DIR=\"${HOME}/.deepferry\"\nSIDECAR_LOG=\"${HOST_DATA_DIR}/dev-sh-sidecar.log\"\nDEFAULT_PORT=8642\nHEALTH_TIMEOUT=60\nPORT_HANDSHAKE_TIMEOUT=30\n\n# ── State ──────────────────────────────────────────────────────────────\nSIDECAR_PID=\"\"\nSIDECAR_PORT=\"\"\nSIDECAR_OWNED=0\n\n# ── Pretty output ──────────────────────────────────────────────────────\nlog() { printf '\\033[1;36m▶ %s\\033[0m\\n' \"$*\"; }\nok() { printf '\\033[1;32m✓ %s\\033[0m\\n' \"$*\"; }\nwarn() { printf '\\033[1;33m! %s\\033[0m\\n' \"$*\"; }\ndie() { printf '\\033[1;31m✗ %s\\033[0m\\n' \"$*\" >&2; exit 1; }\n\nusage() {\n cat <<'EOF'\nDeepFerry dev launcher — sidecar-first (Phase 3).\n\nUSAGE\n ./dev.sh [MODE] [--sidecar-port <N>]\n\nMODES\n (no flag) Smart start: sync deps (uv sync), start the Python\n sidecar in the background, wait for /health, then\n launch `npm run tauri dev` in the foreground.\n Ctrl+C stops both sidecar and webview.\n Best for: \"I just want to start working.\"\n\n --sidecar-only ONLY start the Python sidecar in the foreground\n (uv run deepferry run-http-server). Emits the\n literal line `SIDECAR_PORT=<port>` on stdout.\n No webview.\n Best for: Python-side iteration / curl / pytest.\n\n --webview-only ONLY start the Tauri webview. Requires a sidecar\n already listening on the sidecar port (default\n 8642). Warns + exits if not.\n Best for: frontend work when sidecar is managed\n in another terminal.\n\n --docker-ci OLD workflow: `docker compose up sidecar mysql\n postgres --wait`. For integration tests / CI.\n Containers stay up after the script exits.\n\n -h, --help Show this help.\n\nMODIFIERS\n --sidecar-port <N> Override sidecar port (default 8642). Applied\n to --sidecar-only and default mode; checked by\n --webview-only.\n\nEXAMPLES\n ./dev.sh # everyday start\n ./dev.sh --sidecar-only # Python-only on :8642\n ./dev.sh --sidecar-only --sidecar-port 9876 # Python-only on :9876\n ./dev.sh --webview-only # sidecar already up\n ./dev.sh --docker-ci # bring up CI stack\n\nENVIRONMENT\n DEEPSEEK_API_KEY Read by config.toml (set a dummy if absent).\n VITE_SIDECAR_PORT Exported by this script for `npm run tauri\n dev` (parsed from sidecar stdout).\n\nSTOPPING\n Ctrl+C stops the sidecar if this script owns it. Docker containers\n are NEVER auto-stopped. Full teardown:\n docker compose down\nEOF\n}\n\n# ── Helpers ────────────────────────────────────────────────────────────\nwait_for_url() {\n local url=\"$1\" name=\"$2\" elapsed=0\n while ! curl -sf \"$url\" >/dev/null 2>&1; do\n sleep 1\n elapsed=$((elapsed + 1))\n if [ \"$elapsed\" -ge \"$HEALTH_TIMEOUT\" ]; then\n die \"Timed out waiting for $name ($url) after ${HEALTH_TIMEOUT}s\"\n fi\n done\n}\n\nsidecar_up_on() {\n local port=\"$1\"\n curl -sf \"http://localhost:${port}/health\" >/dev/null 2>&1\n}\n\nensure_host_data_dir() {\n [ -d \"$HOST_DATA_DIR\" ] || mkdir -p \"$HOST_DATA_DIR\"\n}\n\nensure_uv() {\n command -v uv >/dev/null 2>&1 || die \"uv not found in PATH (install: curl -LsSf https://astral.sh/uv/install.sh | sh)\"\n command -v curl >/dev/null 2>&1 || die \"curl not found in PATH\"\n}\n\nensure_webview_prereqs() {\n [ -d \"$APP_SHELL_DIR\" ] || die \"app-shell/ not found at $APP_SHELL_DIR (WP1 rename incomplete?)\"\n command -v npm >/dev/null 2>&1 || die \"npm not found in PATH\"\n}\n\n# Start sidecar in BACKGROUND (default mode). Captures SIDECAR_PORT from\n# stdout, exports VITE_SIDECAR_PORT, waits for /health.\nstart_sidecar_background() {\n local port=\"$1\"\n [ -f \"$CONFIG_TOML\" ] || die \"config.toml not found at $CONFIG_TOML\"\n ensure_host_data_dir\n : > \"$SIDECAR_LOG\"\n\n log \"Syncing Python deps (uv sync)...\"\n uv sync --quiet\n\n log \"Starting sidecar (uv, port=$port, data-dir=$HOST_DATA_DIR)...\"\n log \" logs → $SIDECAR_LOG\"\n\n uv run deepferry run-http-server \\\n --config \"$CONFIG_TOML\" \\\n --port \"$port\" \\\n --data-dir \"$HOST_DATA_DIR\" \\\n >\"$SIDECAR_LOG\" 2>&1 &\n SIDECAR_PID=$!\n SIDECAR_OWNED=1\n\n local elapsed=0\n while [ \"$elapsed\" -lt \"$PORT_HANDSHAKE_TIMEOUT\" ]; do\n if ! kill -0 \"$SIDECAR_PID\" 2>/dev/null; then\n cat \"$SIDECAR_LOG\" >&2 || true\n die \"Sidecar exited before emitting SIDECAR_PORT (see log above)\"\n fi\n SIDECAR_PORT=\"$(grep -E '^SIDECAR_PORT=[0-9]+$' \"$SIDECAR_LOG\" | tail -n1 | cut -d= -f2 || true)\"\n [ -n \"$SIDECAR_PORT\" ] && break\n sleep 0.5\n elapsed=$((elapsed + 1))\n done\n\n [ -n \"$SIDECAR_PORT\" ] || die \"Timed out waiting for SIDECAR_PORT handshake after ${PORT_HANDSHAKE_TIMEOUT}s\"\n\n log \"Waiting for sidecar health on :${SIDECAR_PORT}...\"\n wait_for_url \"http://localhost:${SIDECAR_PORT}/health\" \"sidecar\"\n\n export VITE_SIDECAR_PORT=\"$SIDECAR_PORT\"\n ok \"Sidecar ready at http://localhost:${SIDECAR_PORT} (pid=$SIDECAR_PID)\"\n}\n\n# Start sidecar in FOREGROUND (--sidecar-only). Replaces this shell with\n# uv so signals (Ctrl+C, SIGTERM) flow directly to the Python process.\nstart_sidecar_foreground() {\n local port=\"$1\"\n [ -f \"$CONFIG_TOML\" ] || die \"config.toml not found at $CONFIG_TOML\"\n ensure_host_data_dir\n log \"Starting sidecar in foreground (uv, port=$port)...\"\n log \" Look for: SIDECAR_PORT=<port> on stdout\"\n log \" Ctrl+C to stop.\"\n exec uv run deepferry run-http-server \\\n --config \"$CONFIG_TOML\" \\\n --port \"$port\" \\\n --data-dir \"$HOST_DATA_DIR\"\n}\n\nstart_webview() {\n ensure_webview_prereqs\n log \"Starting Tauri webview (npm run tauri dev)...\"\n (cd \"$APP_SHELL_DIR\" && npm run tauri dev)\n}\n\nrun_docker_ci() {\n command -v docker >/dev/null 2>&1 || die \"docker not found in PATH\"\n docker info >/dev/null 2>&1 || die \"Docker daemon not running. Start Docker Desktop.\"\n log \"Bringing up docker CI stack (sidecar + mysql + postgres)...\"\n docker compose --profile full up sidecar mysql postgres --wait\n ok \"Docker CI stack ready.\"\n echo \" Sidecar : http://localhost:8000\"\n echo \" MySQL : localhost:3306\"\n echo \" Postgres : localhost:5432\"\n log \"Containers stay up after this script exits. Stop with: docker compose down\"\n}\n\n# ── Cleanup on exit ────────────────────────────────────────────────────\ncleanup() {\n local rc=$?\n echo \"\"\n if [ \"${SIDECAR_OWNED:-0}\" = \"1\" ] && [ -n \"${SIDECAR_PID:-}\" ] && kill -0 \"${SIDECAR_PID}\" 2>/dev/null; then\n log \"Stopping sidecar (pid=${SIDECAR_PID})...\"\n kill -TERM \"$SIDECAR_PID\" 2>/dev/null || true\n local waited=0\n while kill -0 \"$SIDECAR_PID\" 2>/dev/null && [ \"$waited\" -lt 5 ]; do\n sleep 1\n waited=$((waited + 1))\n done\n if kill -0 \"$SIDECAR_PID\" 2>/dev/null; then\n warn \"Sidecar did not exit in 5s, sending SIGKILL\"\n kill -KILL \"$SIDECAR_PID\" 2>/dev/null || true\n fi\n wait \"$SIDECAR_PID\" 2>/dev/null || true\n ok \"Sidecar stopped.\"\n fi\n exit \"$rc\"\n}\ntrap cleanup INT TERM EXIT\n\n# ── Parse args ─────────────────────────────────────────────────────────\nMODE=\"default\"\nSIDECAR_PORT_ARG=\"\"\nwhile [ $# -gt 0 ]; do\n case \"$1\" in\n -h|--help) MODE=\"help\"; shift;;\n --sidecar-only) MODE=\"sidecar\"; shift;;\n --webview-only) MODE=\"webview\"; shift;;\n --docker-ci) MODE=\"docker\"; shift;;\n --sidecar-port)\n [ $# -ge 2 ] || die \"--sidecar-port requires a value\"\n SIDECAR_PORT_ARG=\"$2\"\n shift 2\n ;;\n --) shift; break;;\n -*) die \"Unknown flag: $1 (try ./dev.sh --help)\";;\n *) die \"Unexpected argument: $1 (try ./dev.sh --help)\";;\n esac\ndone\n\nif [ \"$MODE\" = \"help\" ]; then\n usage\n exit 0\nfi\n\nSIDE_PORT=\"${SIDECAR_PORT_ARG:-$DEFAULT_PORT}\"\ncase \"$SIDE_PORT\" in\n ''|*[!0-9]*) die \"--sidecar-port must be a positive integer, got: $SIDE_PORT\";;\nesac\n\nensure_uv\n\ncase \"$MODE\" in\n sidecar)\n start_sidecar_foreground \"$SIDE_PORT\"\n ;;\n\n webview)\n if ! sidecar_up_on \"$SIDE_PORT\"; then\n die \"Sidecar not reachable on http://localhost:${SIDE_PORT}/health. Start it first: ./dev.sh --sidecar-only\"\n fi\n ok \"Sidecar healthy on :${SIDE_PORT}\"\n export VITE_SIDECAR_PORT=\"$SIDE_PORT\"\n start_webview\n ;;\n\n docker)\n run_docker_ci\n ;;\n\n default|*)\n start_sidecar_background \"$SIDE_PORT\"\n echo \"\"\n ok \"DeepFerry dev ready!\"\n echo \" Sidecar API : http://localhost:${SIDECAR_PORT}\"\n echo \" Webview : Tauri window (auto-opens)\"\n echo \" Sidecar log : $SIDECAR_LOG\"\n echo \" ───────────────────────────────────────────────────────\"\n echo \"\"\n start_webview\n ;;\nesac\n"}, "710b3d12f65b26490589f9b4f3963d7ea2015eb6": {"t": 1785758359.8355343, "n": 10017, "h": "fdbb4369ef5d34a8", "c": "#!/usr/bin/env bash\n#\n# DeepFerry — sidecar-first dev launcher (Phase 3 rewrite)\n#\n# Mental model: deepferry is a Tauri desktop app. The Python sidecar runs\n# locally (uv run), the webview is a Tauri window (npm run tauri dev).\n# Docker is for CI / integration tests, NOT the default dev loop.\n#\n# Modes (mutually exclusive — last one wins if multiple given):\n# ./dev.sh Smart start: sidecar (uv) + webview (Tauri)\n# ./dev.sh --sidecar-only ONLY start uv sidecar (foreground)\n# ./dev.sh --webview-only ONLY start Tauri webview (sidecar must be up)\n# ./dev.sh --docker-ci docker compose up sidecar + mysql + postgres\n# ./dev.sh -h | --help Show this help\n#\n# Optional modifier (not a mode):\n# --sidecar-port <N> Override sidecar port (default 8642)\n#\n# Ctrl+C cleans up only what this script started (sidecar PID, if any).\n# Docker containers are NEVER auto-stopped — use `docker compose down`.\n\nset -euo pipefail\n\n# ── Constants ──────────────────────────────────────────────────────────\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nAPP_SHELL_DIR=\"$ROOT_DIR/app-shell\"\nCONFIG_TOML=\"$ROOT_DIR/config.toml\"\nHOST_DATA_DIR=\"${HOME}/.deepferry\"\nSIDECAR_LOG=\"${HOST_DATA_DIR}/dev-sh-sidecar.log\"\nDEFAULT_PORT=8642\nHEALTH_TIMEOUT=60\nPORT_HANDSHAKE_TIMEOUT=30\n\n# ── State ──────────────────────────────────────────────────────────────\nSIDECAR_PID=\"\"\nSIDECAR_PORT=\"\"\nSIDECAR_OWNED=0\n\n# ── Pretty output ──────────────────────────────────────────────────────\nlog() { printf '\\033[1;36m▶ %s\\033[0m\\n' \"$*\"; }\nok() { printf '\\033[1;32m✓ %s\\033[0m\\n' \"$*\"; }\nwarn() { printf '\\033[1;33m! %s\\033[0m\\n' \"$*\"; }\ndie() { printf '\\033[1;31m✗ %s\\033[0m\\n' \"$*\" >&2; exit 1; }\n\nusage() {\n cat <<'EOF'\nDeepFerry dev launcher — sidecar-first (Phase 3).\n\nUSAGE\n ./dev.sh [MODE] [--sidecar-port <N>]\n\nMODES\n (no flag) Smart start: sync deps (uv sync), start the Python\n sidecar in the background, wait for /health, then\n launch `npm run tauri dev` in the foreground.\n Ctrl+C stops both sidecar and webview.\n Best for: \"I just want to start working.\"\n\n --sidecar-only ONLY start the Python sidecar in the foreground\n (uv run deepferry run-http-server). Emits the\n literal line `SIDECAR_PORT=<port>` on stdout.\n No webview.\n Best for: Python-side iteration / curl / pytest.\n\n --webview-only ONLY start the Tauri webview. Requires a sidecar\n already listening on the sidecar port (default\n 8642). Warns + exits if not.\n Best for: frontend work when sidecar is managed\n in another terminal.\n\n --docker-ci OLD workflow: `docker compose up sidecar mysql\n postgres --wait`. For integration tests / CI.\n Containers stay up after the script exits.\n\n -h, --help Show this help.\n\nMODIFIERS\n --sidecar-port <N> Override sidecar port (default 8642). Applied\n to --sidecar-only and default mode; checked by\n --webview-only.\n\nEXAMPLES\n ./dev.sh # everyday start\n ./dev.sh --sidecar-only # Python-only on :8642\n ./dev.sh --sidecar-only --sidecar-port 9876 # Python-only on :9876\n ./dev.sh --webview-only # sidecar already up\n ./dev.sh --docker-ci # bring up CI stack\n\nENVIRONMENT\n DEEPSEEK_API_KEY Read by config.toml (set a dummy if absent).\n VITE_SIDECAR_PORT Exported by this script for `npm run tauri\n dev` (parsed from sidecar stdout).\n\nSTOPPING\n Ctrl+C stops the sidecar if this script owns it. Docker containers\n are NEVER auto-stopped. Full teardown:\n docker compose down\nEOF\n}\n\n# ── Helpers ────────────────────────────────────────────────────────────\nwait_for_url() {\n local url=\"$1\" name=\"$2\" elapsed=0\n while ! curl -sf \"$url\" >/dev/null 2>&1; do\n sleep 1\n elapsed=$((elapsed + 1))\n if [ \"$elapsed\" -ge \"$HEALTH_TIMEOUT\" ]; then\n die \"Timed out waiting for $name ($url) after ${HEALTH_TIMEOUT}s\"\n fi\n done\n}\n\nsidecar_up_on() {\n local port=\"$1\"\n curl -sf \"http://localhost:${port}/health\" >/dev/null 2>&1\n}\n\nensure_host_data_dir() {\n [ -d \"$HOST_DATA_DIR\" ] || mkdir -p \"$HOST_DATA_DIR\"\n}\n\nensure_uv() {\n command -v uv >/dev/null 2>&1 || die \"uv not found in PATH (install: curl -LsSf https://astral.sh/uv/install.sh | sh)\"\n command -v curl >/dev/null 2>&1 || die \"curl not found in PATH\"\n}\n\nensure_webview_prereqs() {\n [ -d \"$APP_SHELL_DIR\" ] || die \"app-shell/ not found at $APP_SHELL_DIR (WP1 rename incomplete?)\"\n command -v npm >/dev/null 2>&1 || die \"npm not found in PATH\"\n}\n\n# Start sidecar in BACKGROUND (default mode). Captures SIDECAR_PORT from\n# stdout, exports VITE_SIDECAR_PORT, waits for /health.\nstart_sidecar_background() {\n local port=\"$1\"\n [ -f \"$CONFIG_TOML\" ] || die \"config.toml not found at $CONFIG_TOML\"\n ensure_host_data_dir\n : > \"$SIDECAR_LOG\"\n\n log \"Syncing Python deps (uv sync)...\"\n uv sync --quiet\n\n log \"Starting sidecar (uv, port=$port, data-dir=$HOST_DATA_DIR)...\"\n log \" logs → $SIDECAR_LOG\"\n\n uv run deepferry run-http-server \\\n --config \"$CONFIG_TOML\" \\\n --port \"$port\" \\\n --data-dir \"$HOST_DATA_DIR\" \\\n >\"$SIDECAR_LOG\" 2>&1 &\n SIDECAR_PID=$!\n SIDECAR_OWNED=1\n\n local elapsed=0\n while [ \"$elapsed\" -lt \"$PORT_HANDSHAKE_TIMEOUT\" ]; do\n if ! kill -0 \"$SIDECAR_PID\" 2>/dev/null; then\n cat \"$SIDECAR_LOG\" >&2 || true\n die \"Sidecar exited before emitting SIDECAR_PORT (see log above)\"\n fi\n SIDECAR_PORT=\"$(grep -E '^SIDECAR_PORT=[0-9]+$' \"$SIDECAR_LOG\" | tail -n1 | cut -d= -f2 || true)\"\n [ -n \"$SIDECAR_PORT\" ] && break\n sleep 0.5\n elapsed=$((elapsed + 1))\n done\n\n [ -n \"$SIDECAR_PORT\" ] || die \"Timed out waiting for SIDECAR_PORT handshake after ${PORT_HANDSHAKE_TIMEOUT}s\"\n\n log \"Waiting for sidecar health on :${SIDECAR_PORT}...\"\n wait_for_url \"http://localhost:${SIDECAR_PORT}/health\" \"sidecar\"\n\n export VITE_SIDECAR_PORT=\"$SIDECAR_PORT\"\n ok \"Sidecar ready at http://localhost:${SIDECAR_PORT} (pid=$SIDECAR_PID)\"\n}\n\n# Start sidecar in FOREGROUND (--sidecar-only). Replaces this shell with\n# uv so signals (Ctrl+C, SIGTERM) flow directly to the Python process.\nstart_sidecar_foreground() {\n local port=\"$1\"\n [ -f \"$CONFIG_TOML\" ] || die \"config.toml not found at $CONFIG_TOML\"\n ensure_host_data_dir\n log \"Starting sidecar in foreground (uv, port=$port)...\"\n log \" Look for: SIDECAR_PORT=<port> on stdout\"\n log \" Ctrl+C to stop.\"\n exec uv run deepferry run-http-server \\\n --config \"$CONFIG_TOML\" \\\n --port \"$port\" \\\n --data-dir \"$HOST_DATA_DIR\"\n}\n\nstart_webview() {\n ensure_webview_prereqs\n log \"Starting Tauri webview (npm run tauri dev)...\"\n (cd \"$APP_SHELL_DIR\" && npm run tauri dev)\n}\n\nrun_docker_ci() {\n command -v docker >/dev/null 2>&1 || die \"docker not found in PATH\"\n docker info >/dev/null 2>&1 || die \"Docker daemon not running. Start Docker Desktop.\"\n log \"Bringing up docker CI stack (sidecar + mysql + postgres)...\"\n docker compose --profile full up sidecar mysql postgres --wait\n ok \"Docker CI stack ready.\"\n echo \" Sidecar : http://localhost:8000\"\n echo \" MySQL : localhost:3306\"\n echo \" Postgres : localhost:5432\"\n log \"Containers stay up after this script exits. Stop with: docker compose down\"\n}\n\n# ── Cleanup on exit ────────────────────────────────────────────────────\ncleanup() {\n local rc=$?\n echo \"\"\n if [ \"${SIDECAR_OWNED:-0}\" = \"1\" ] && [ -n \"${SIDECAR_PID:-}\" ] && kill -0 \"${SIDECAR_PID}\" 2>/dev/null; then\n log \"Stopping sidecar (pid=${SIDECAR_PID})...\"\n kill -TERM \"$SIDECAR_PID\" 2>/dev/null || true\n local waited=0\n while kill -0 \"$SIDECAR_PID\" 2>/dev/null && [ \"$waited\" -lt 5 ]; do\n sleep 1\n waited=$((waited + 1))\n done\n if kill -0 \"$SIDECAR_PID\" 2>/dev/null; then\n warn \"Sidecar did not exit in 5s, sending SIGKILL\"\n kill -KILL \"$SIDECAR_PID\" 2>/dev/null || true\n fi\n wait \"$SIDECAR_PID\" 2>/dev/null || true\n ok \"Sidecar stopped.\"\n fi\n exit \"$rc\"\n}\ntrap cleanup INT TERM EXIT\n\n# ── Parse args ─────────────────────────────────────────────────────────\nMODE=\"default\"\nSIDECAR_PORT_ARG=\"\"\nwhile [ $# -gt 0 ]; do\n case \"$1\" in\n -h|--help) MODE=\"help\"; shift;;\n --sidecar-only) MODE=\"sidecar\"; shift;;\n --webview-only) MODE=\"webview\"; shift;;\n --docker-ci) MODE=\"docker\"; shift;;\n --sidecar-port)\n [ $# -ge 2 ] || die \"--sidecar-port requires a value\"\n SIDECAR_PORT_ARG=\"$2\"\n shift 2\n ;;\n --) shift; break;;\n -*) die \"Unknown flag: $1 (try ./dev.sh --help)\";;\n *) die \"Unexpected argument: $1 (try ./dev.sh --help)\";;\n esac\ndone\n\nif [ \"$MODE\" = \"help\" ]; then\n usage\n exit 0\nfi\n\nSIDE_PORT=\"${SIDECAR_PORT_ARG:-$DEFAULT_PORT}\"\ncase \"$SIDE_PORT\" in\n ''|*[!0-9]*) die \"--sidecar-port must be a positive integer, got: $SIDE_PORT\";;\nesac\n\nensure_uv\n\ncase \"$MODE\" in\n sidecar)\n start_sidecar_foreground \"$SIDE_PORT\"\n ;;\n\n webview)\n if ! sidecar_up_on \"$SIDE_PORT\"; then\n die \"Sidecar not reachable on http://localhost:${SIDE_PORT}/health. Start it first: ./dev.sh --sidecar-only\"\n fi\n ok \"Sidecar healthy on :${SIDE_PORT}\"\n export VITE_SIDECAR_PORT=\"$SIDE_PORT\"\n start_webview\n ;;\n\n docker)\n run_docker_ci\n ;;\n\n default|*)\n start_sidecar_background \"$SIDE_PORT\"\n echo \"\"\n ok \"DeepFerry dev ready!\"\n echo \" Sidecar API : http://localhost:${SIDECAR_PORT}\"\n echo \" Webview : Tauri window (auto-opens)\"\n echo \" Sidecar log : $SIDECAR_LOG\"\n echo \" ───────────────────────────────────────────────────────\"\n echo \"\"\n start_webview\n ;;\nesac\n"}, "d2f0ebafdefdec3113703c2be7cb8447bfe06e84": {"t": 1785758361.458944, "n": 8809, "h": "4f683841c901c65d", "c": "#!/usr/bin/env bash\n#\n# DeepFerry — one-command dev launcher\n#\n# Modes (mutually exclusive — last one wins if multiple given):\n# ./dev.sh Smart start: bring up whatever's missing\n# ./dev.sh --frontend ONLY start Vite (warn if backend down, don't touch docker)\n# ./dev.sh --backend ONLY start docker backend (skip Vite, for backend debugging)\n# ./dev.sh --build Rebuild deepferry image + recreate container + start Vite\n# ./dev.sh --rebuild Alias for --build (backward compat)\n# ./dev.sh -h | --help Show this help\n#\n# What each mode is good for:\n# ./dev.sh \"I just want to start working\" — most common\n# ./dev.sh --frontend \"Backend is already up / managed externally, I'm doing UI work\"\n# ./dev.sh --backend \"I'm debugging Python, no need for HMR\"\n# ./dev.sh --build \"I changed backend code, ship it into docker\"\n#\n# Ctrl+C stops Vite (if this script started it). Docker containers keep running.\n\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nBACKEND_URL=\"http://localhost:8000\"\nFRONTEND_URL=\"http://localhost:5173\"\nHEALTH_TIMEOUT=180\nVITE_PID=\"\"\n\n# ── Pretty output ──────────────────────────────────────────────────────\nlog() { printf '\\033[1;36m▶ %s\\033[0m\\n' \"$*\"; }\nok() { printf '\\033[1;32m✓ %s\\033[0m\\n' \"$*\"; }\nwarn() { printf '\\033[1;33m! %s\\033[0m\\n' \"$*\"; }\ndie() { printf '\\033[1;31m✗ %s\\033[0m\\n' \"$*\" >&2; exit 1; }\n\nusage() {\n cat <<'EOF'\nDeepFerry dev launcher — pick the mode that matches what you're doing.\n\nUSAGE\n ./dev.sh [MODE]\n\nMODES\n (no flag) Smart start. If backend (:8000) is down → bring up\n docker (mysql + postgres + deepferry). If frontend\n (:5173) is down → start Vite. Open browser.\n Best for: \"I just want to start working.\"\n\n --frontend ONLY start Vite on :5173. If backend is down, warn\n but do NOT auto-start docker (you manage it).\n Best for: frontend-only work when backend is already\n running (or you're using `uv run` backend).\n\n --backend ONLY bring up docker backend. Skip Vite. Print the\n backend URL when healthy.\n Best for: backend debugging without frontend noise.\n\n --build Rebuild the deepferry docker image from local source,\n recreate the container, then start Vite. Use this\n whenever you've changed Python code and want it live\n in docker.\n Best for: \"I changed backend code, ship it to docker.\"\n\n --rebuild Alias for --build (kept for backward compatibility).\n\n -h, --help Show this help.\n\nEXAMPLES\n ./dev.sh # everyday start\n ./dev.sh --build # after editing src/deepferry/*.py\n ./dev.sh --frontend # backend already running, just hack UI\n ./dev.sh --backend # debug Python backend, no HMR needed\n\nENVIRONMENT\n DEEPSEEK_API_KEY Required by config.toml; if unset, set a dummy\n value: DEEPSEEK_API_KEY=dummy ./dev.sh\n HEALTH_TIMEOUT Max seconds to wait for service health (default 180)\n\nSTOPPING\n Ctrl+C stops Vite (if this script owns it). Docker containers keep running\n so subsequent ./dev.sh starts only Vite in seconds.\n Full teardown: docker compose --profile full down\nEOF\n}\n\n# ── Wait until a URL responds 200 ──────────────────────────────────────\nwait_for_url() {\n local url=\"$1\" name=\"$2\" elapsed=0\n while ! curl -sf \"$url\" >/dev/null 2>&1; do\n sleep 1\n elapsed=$((elapsed + 1))\n if [ \"$elapsed\" -ge \"$HEALTH_TIMEOUT\" ]; then\n die \"Timed out waiting for $name ($url) after ${HEALTH_TIMEOUT}s\"\n fi\n done\n}\n\nopen_browser() {\n local url=\"$1\"\n if command -v open >/dev/null 2>&1; then\n open \"$url\"\n elif command -v xdg-open >/dev/null 2>&1; then\n xdg-open \"$url\"\n elif command -v start >/dev/null 2>&1; then\n start \"$url\"\n else\n warn \"Cannot auto-open browser. Visit: $url\"\n fi\n}\n\n# ── Cleanup on exit ────────────────────────────────────────────────────\ncleanup() {\n local rc=$?\n echo \"\"\n if [ -n \"$VITE_PID\" ] && kill -0 \"$VITE_PID\" 2>/dev/null; then\n log \"Stopping frontend dev server...\"\n kill \"$VITE_PID\" 2>/dev/null || true\n wait \"$VITE_PID\" 2>/dev/null || true\n fi\n ok \"Frontend stopped. Docker services (if running) stay up.\"\n ok \"Stop Docker with: docker compose --profile full down\"\n exit \"$rc\"\n}\ntrap cleanup EXIT INT TERM\n\n# ── Helpers ────────────────────────────────────────────────────────────\nbackend_up() { curl -sf \"$BACKEND_URL/health\" >/dev/null 2>&1; }\nfrontend_up() { curl -sf \"$FRONTEND_URL\" >/dev/null 2>&1; }\n\npreflight() {\n command -v docker >/dev/null 2>&1 || die \"docker not found in PATH\"\n docker info >/dev/null 2>&1 || die \"Docker daemon not running. Start Docker Desktop.\"\n command -v npm >/dev/null 2>&1 || die \"npm not found in PATH\"\n ok \"Environment OK\"\n}\n\nstart_docker_backend() {\n log \"Starting docker services (mysql + postgres + deepferry)...\"\n docker compose --profile full up -d --wait\n log \"Verifying backend health...\"\n wait_for_url \"$BACKEND_URL/health\" \"backend\"\n ok \"Backend ready: $(curl -sf \"$BACKEND_URL/health\")\"\n}\n\nrebuild_docker_backend() {\n log \"Rebuilding deepferry image from local source...\"\n docker compose build deepferry\n log \"Recreating deepferry container (pick up new image)...\"\n docker compose --profile full up -d --wait --force-recreate deepferry\n log \"Verifying backend health...\"\n wait_for_url \"$BACKEND_URL/health\" \"backend\"\n ok \"Backend ready: $(curl -sf \"$BACKEND_URL/health\")\"\n}\n\nstart_frontend() {\n cd \"$ROOT_DIR/frontend\"\n if [ ! -d node_modules ]; then\n log \"Installing frontend dependencies (first run)...\"\n npm install\n fi\n log \"Starting Vite dev server...\"\n npm run dev &\n VITE_PID=$!\n log \"Waiting for Vite to be ready...\"\n wait_for_url \"$FRONTEND_URL\" \"frontend\"\n ok \"Frontend ready\"\n}\n\n# ── Parse args ─────────────────────────────────────────────────────────\nMODE=\"smart\"\nfor arg in \"$@\"; do\n case \"$arg\" in\n -h|--help) MODE=\"help\";;\n --frontend) MODE=\"frontend\";;\n --backend) MODE=\"backend\";;\n --build) MODE=\"build\";;\n --rebuild) MODE=\"build\";; # alias\n --) shift; break;;\n -*) die \"Unknown flag: $arg (try ./dev.sh --help)\";;\n *) die \"Unexpected argument: $arg (try ./dev.sh --help)\";;\n esac\ndone\n\nif [ \"$MODE\" = \"help\" ]; then\n usage\n exit 0\nfi\n\n# ── Run mode ───────────────────────────────────────────────────────────\npreflight\n\ncase \"$MODE\" in\n frontend)\n if ! backend_up; then\n warn \"Backend on $BACKEND_URL is NOT responding.\"\n warn \" Use './dev.sh' to start it, or './dev.sh --backend' for backend only.\"\n warn \" Continuing with frontend-only startup anyway...\"\n fi\n if frontend_up; then\n ok \"Frontend already running on :5173 — nothing to do\"\n else\n start_frontend\n log \"Opening browser → $FRONTEND_URL\"\n open_browser \"$FRONTEND_URL\"\n fi\n wait \"$VITE_PID\" 2>/dev/null || true\n ;;\n\n backend)\n if backend_up; then\n ok \"Backend already healthy on :8000 — nothing to do\"\n else\n start_docker_backend\n fi\n echo \"\"\n ok \"Backend live at $BACKEND_URL\"\n echo \" Frontend not started (--backend mode). Run './dev.sh --frontend' separately.\"\n echo \"\"\n # Block so the trap-based cleanup message makes sense; user Ctrl+C to exit\n log \"Press Ctrl+C to exit (backend keeps running).\"\n tail -f /dev/null\n ;;\n\n build)\n rebuild_docker_backend\n if frontend_up; then\n ok \"Frontend already running on :5173 — reusing\"\n else\n start_frontend\n fi\n log \"Opening browser → $FRONTEND_URL\"\n open_browser \"$FRONTEND_URL\"\n echo \"\"\n ok \"DeepFerry is live (rebuilt from local source)!\"\n echo \" Backend API : $BACKEND_URL\"\n echo \" Frontend UI : $FRONTEND_URL\"\n echo \"\"\n wait \"$VITE_PID\" 2>/dev/null || true\n ;;\n\n smart|*)\n # Smart start: bring up whatever's missing\n if ! backend_up; then\n start_docker_backend\n else\n ok \"Backend already healthy on :8000 — skipping docker\"\n fi\n if frontend_up; then\n ok \"Frontend already running on :5173 — reusing\"\n else\n start_frontend\n fi\n log \"Opening browser → $FRONTEND_URL\"\n open_browser \"$FRONTEND_URL\"\n echo \"\"\n ok \"DeepFerry is live!\"\n echo \" Backend API : $BACKEND_URL\"\n echo \" Frontend UI : $FRONTEND_URL\"\n echo \"\"\n echo \" Press Ctrl+C to stop the frontend.\"\n echo \" ───────────────────────────────────────────────────────\"\n echo \"\"\n wait \"$VITE_PID\" 2>/dev/null || true\n ;;\nesac\n"}, "1929c6e347ee7edd839bcff2300e249c43ed3944": {"t": 1785758362.7554693, "n": 4682, "h": "d4324cf98795c3f0", "c": "#!/usr/bin/env bash\n#\n# DeepFerry — one-command dev launcher (auto-mode)\n#\n# Auto-detects what's already running and starts only what's missing:\n# 1. Backend: if :8000 is not healthy → Docker compose up (MySQL + PG + deepferry)\n# 2. Frontend: if :5173 is not responding → npm run dev (Vite)\n# 3. Browser: opens http://localhost:5173\n#\n# Usage:\n# ./dev.sh Auto-start missing pieces (skip what's already up)\n# ./dev.sh --rebuild Force-rebuild the Docker image and restart backend\n#\n# Ctrl+C stops the frontend. Docker services keep running so a subsequent\n# ./dev.sh starts only the frontend in seconds.\n\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nBACKEND_URL=\"http://localhost:8000\"\nFRONTEND_URL=\"http://localhost:5173\"\nHEALTH_TIMEOUT=180 # seconds before giving up\nVITE_PID=\"\"\n\n# ── Pretty output ──────────────────────────────────────────────────────\nlog() { printf '\\033[1;36m▶ %s\\033[0m\\n' \"$*\"; }\nok() { printf '\\033[1;32m✓ %s\\033[0m\\n' \"$*\"; }\nwarn() { printf '\\033[1;33m! %s\\033[0m\\n' \"$*\"; }\ndie() { printf '\\033[1;31m✗ %s\\033[0m\\n' \"$*\" >&2; exit 1; }\n\n# ── Wait until a URL responds 200 ──────────────────────────────────────\nwait_for_url() {\n local url=\"$1\" name=\"$2\" elapsed=0\n while ! curl -sf \"$url\" >/dev/null 2>&1; do\n sleep 1\n elapsed=$((elapsed + 1))\n if [ \"$elapsed\" -ge \"$HEALTH_TIMEOUT\" ]; then\n die \"Timed out waiting for $name ($url) after ${HEALTH_TIMEOUT}s\"\n fi\n done\n}\n\n# ── Open browser (cross-platform) ──────────────────────────────────────\nopen_browser() {\n local url=\"$1\"\n if command -v open >/dev/null 2>&1; then\n open \"$url\" # macOS\n elif command -v xdg-open >/dev/null 2>&1; then\n xdg-open \"$url\" # Linux\n elif command -v start >/dev/null 2>&1; then\n start \"$url\" # Windows (Git Bash / WSL)\n else\n warn \"Cannot auto-open browser. Visit: $url\"\n fi\n}\n\n# ── Cleanup on exit ────────────────────────────────────────────────────\ncleanup() {\n local rc=$?\n echo \"\"\n if [ -n \"$VITE_PID\" ] && kill -0 \"$VITE_PID\" 2>/dev/null; then\n log \"Stopping frontend dev server...\"\n kill \"$VITE_PID\" 2>/dev/null || true\n wait \"$VITE_PID\" 2>/dev/null || true\n fi\n ok \"Frontend stopped. Docker services are still running.\"\n ok \"Stop Docker with: docker compose --profile full down\"\n exit \"$rc\"\n}\ntrap cleanup EXIT INT TERM\n\n# ── Preflight ──────────────────────────────────────────────────────────\nlog \"Preflight checks...\"\ncommand -v docker >/dev/null 2>&1 || die \"docker not found in PATH\"\ndocker info >/dev/null 2>&1 || die \"Docker daemon is not running. Start Docker Desktop first.\"\ncommand -v npm >/dev/null 2>&1 || die \"npm not found in PATH\"\nok \"Environment OK\"\n\n# ── 1. Start Docker backend (skip if already healthy) ─────────────────\nBACKEND_ALREADY_UP=false\nif curl -sf \"$BACKEND_URL/health\" >/dev/null 2>&1; then\n BACKEND_ALREADY_UP=true\n ok \"Backend already healthy on :8000 — skipping Docker startup\"\nfi\n\nif [ \"$BACKEND_ALREADY_UP\" = \"false\" ] || [ \"${1:-}\" = \"--rebuild\" ]; then\n if [ \"${1:-}\" = \"--rebuild\" ]; then\n log \"Rebuilding Docker image...\"\n docker compose build deepferry\n fi\n log \"Starting Docker services (MySQL + PostgreSQL + deepferry)...\"\n docker compose --profile full up -d --wait\n ok \"Docker services healthy\"\n\n log \"Verifying backend health...\"\n wait_for_url \"$BACKEND_URL/health\" \"backend\"\n ok \"Backend ready: $(curl -sf \"$BACKEND_URL/health\")\"\nfi\n\n# ── 2. Start frontend dev server ───────────────────────────────────────\ncd \"$ROOT_DIR/frontend\"\n\nFRONTEND_ALREADY_UP=false\nif curl -sf \"$FRONTEND_URL\" >/dev/null 2>&1; then\n FRONTEND_ALREADY_UP=true\n ok \"Frontend already running on :5173 — reusing\"\nfi\n\nif [ \"$FRONTEND_ALREADY_UP\" = \"false\" ]; then\n if [ ! -d node_modules ]; then\n log \"Installing frontend dependencies (first run)...\"\n npm install\n fi\n\n log \"Starting Vite dev server...\"\n npm run dev &\n VITE_PID=$!\n\n log \"Waiting for Vite to be ready...\"\n wait_for_url \"$FRONTEND_URL\" \"frontend\"\n ok \"Frontend ready\"\nfi\n\n# ── 3. Open browser ────────────────────────────────────────────────────\nlog \"Opening browser → $FRONTEND_URL\"\nopen_browser \"$FRONTEND_URL\"\n\necho \"\"\nok \"DeepFerry is live!\"\necho \"\"\necho \" Backend API : $BACKEND_URL\"\necho \" Frontend UI : $FRONTEND_URL\"\necho \"\"\necho \" Press Ctrl+C to stop the frontend.\"\necho \" ───────────────────────────────────────────────────────\"\necho \"\"\n\n# Block until Vite exits (Ctrl+C) — only when we started it\nif [ \"$FRONTEND_ALREADY_UP\" = \"false\" ] && [ -n \"$VITE_PID\" ]; then\n wait \"$VITE_PID\"\nelse\n ok \"Frontend was already running. Open $FRONTEND_URL anytime.\"\nfi\n"}, "169a0409be969f64cee7e54dc6c1992cf56bba19": {"t": 1785758364.2600389, "n": 4355, "h": "72f3be244082170b", "c": "#!/usr/bin/env bash\n#\n# DeepFerry — one-command dev launcher\n#\n# Brings up the full stack and opens the browser:\n# 1. Docker backend: MySQL + PostgreSQL + MCP server (port 8000)\n# 2. Frontend: Vite dev server (port 5173)\n# 3. Browser: http://localhost:5173\n#\n# Usage:\n# ./dev.sh Start everything (build Docker image if stale)\n# ./dev.sh --rebuild Force-rebuild the Docker image before starting\n#\n# Ctrl+C stops the frontend. Docker services keep running so you can\n# restart only the frontend with another ./dev.sh.\n\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nBACKEND_URL=\"http://localhost:8000\"\nFRONTEND_URL=\"http://localhost:5173\"\nHEALTH_TIMEOUT=180 # seconds before giving up\nVITE_PID=\"\"\n\n# ── Pretty output ──────────────────────────────────────────────────────\nlog() { printf '\\033[1;36m▶ %s\\033[0m\\n' \"$*\"; }\nok() { printf '\\033[1;32m✓ %s\\033[0m\\n' \"$*\"; }\nwarn() { printf '\\033[1;33m! %s\\033[0m\\n' \"$*\"; }\ndie() { printf '\\033[1;31m✗ %s\\033[0m\\n' \"$*\" >&2; exit 1; }\n\n# ── Wait until a URL responds 200 ──────────────────────────────────────\nwait_for_url() {\n local url=\"$1\" name=\"$2\" elapsed=0\n while ! curl -sf \"$url\" >/dev/null 2>&1; do\n sleep 1\n elapsed=$((elapsed + 1))\n if [ \"$elapsed\" -ge \"$HEALTH_TIMEOUT\" ]; then\n die \"Timed out waiting for $name ($url) after ${HEALTH_TIMEOUT}s\"\n fi\n done\n}\n\n# ── Open browser (cross-platform) ──────────────────────────────────────\nopen_browser() {\n local url=\"$1\"\n if command -v open >/dev/null 2>&1; then\n open \"$url\" # macOS\n elif command -v xdg-open >/dev/null 2>&1; then\n xdg-open \"$url\" # Linux\n elif command -v start >/dev/null 2>&1; then\n start \"$url\" # Windows (Git Bash / WSL)\n else\n warn \"Cannot auto-open browser. Visit: $url\"\n fi\n}\n\n# ── Cleanup on exit ────────────────────────────────────────────────────\ncleanup() {\n local rc=$?\n echo \"\"\n if [ -n \"$VITE_PID\" ] && kill -0 \"$VITE_PID\" 2>/dev/null; then\n log \"Stopping frontend dev server...\"\n kill \"$VITE_PID\" 2>/dev/null || true\n wait \"$VITE_PID\" 2>/dev/null || true\n fi\n ok \"Frontend stopped. Docker services are still running.\"\n ok \"Stop Docker with: docker compose --profile full down\"\n exit \"$rc\"\n}\ntrap cleanup EXIT INT TERM\n\n# ── Preflight ──────────────────────────────────────────────────────────\nlog \"Preflight checks...\"\ncommand -v docker >/dev/null 2>&1 || die \"docker not found in PATH\"\ndocker info >/dev/null 2>&1 || die \"Docker daemon is not running. Start Docker Desktop first.\"\ncommand -v npm >/dev/null 2>&1 || die \"npm not found in PATH\"\nok \"Environment OK\"\n\n# ── 1. Start Docker backend ────────────────────────────────────────────\nif [ \"${1:-}\" = \"--rebuild\" ]; then\n log \"Rebuilding Docker image...\"\n docker compose build deepferry\nfi\n\nlog \"Starting Docker services (MySQL + PostgreSQL + deepferry)...\"\ndocker compose --profile full up -d --wait\nok \"Docker services healthy\"\n\nlog \"Verifying backend health...\"\nwait_for_url \"$BACKEND_URL/health\" \"backend\"\nok \"Backend ready: $(curl -sf \"$BACKEND_URL/health\")\"\n\n# ── 2. Start frontend dev server ───────────────────────────────────────\ncd \"$ROOT_DIR/frontend\"\n\nFRONTEND_ALREADY_UP=false\nif curl -sf \"$FRONTEND_URL\" >/dev/null 2>&1; then\n FRONTEND_ALREADY_UP=true\n ok \"Frontend already running on :5173 — reusing\"\nfi\n\nif [ \"$FRONTEND_ALREADY_UP\" = \"false\" ]; then\n if [ ! -d node_modules ]; then\n log \"Installing frontend dependencies (first run)...\"\n npm install\n fi\n\n log \"Starting Vite dev server...\"\n npm run dev &\n VITE_PID=$!\n\n log \"Waiting for Vite to be ready...\"\n wait_for_url \"$FRONTEND_URL\" \"frontend\"\n ok \"Frontend ready\"\nfi\n\n# ── 3. Open browser ────────────────────────────────────────────────────\nlog \"Opening browser → $FRONTEND_URL\"\nopen_browser \"$FRONTEND_URL\"\n\necho \"\"\nok \"DeepFerry is live!\"\necho \"\"\necho \" Backend API : $BACKEND_URL\"\necho \" Frontend UI : $FRONTEND_URL\"\necho \"\"\necho \" Press Ctrl+C to stop the frontend.\"\necho \" ───────────────────────────────────────────────────────\"\necho \"\"\n\n# Block until Vite exits (Ctrl+C) — only when we started it\nif [ \"$FRONTEND_ALREADY_UP\" = \"false\" ] && [ -n \"$VITE_PID\" ]; then\n wait \"$VITE_PID\"\nelse\n ok \"Frontend was already running. Open $FRONTEND_URL anytime.\"\nfi\n"}}}}