#!/usr/bin/env bash # --------------------------------------------------------------------------- # VANI Speech API — one-command deploy to Hugging Face Spaces. # # PURPOSE: create (or update) the Space, push this folder, wait for it to # build, then PROVE it is live by exercising the real endpoints over https # from outside — health, a real text-to-speech round trip, and a real # speech-to-text round trip using the audio that text-to-speech just produced. # # WHEN TO RUN: from RP's own terminal, which is the only shell with a Hugging # Face credential. Agent shells are not logged in (verified 2026-08-21: both # `hf repos create` and `hf upload` return 401 Unauthorized). # # WHEN NOT TO RUN: not from an agent session, and not as a scheduled job. # It is a deliberate, interactive deployment step. # # This script never asks for, prints, or stores a token. It only checks that # a credential is already present. # --------------------------------------------------------------------------- set -euo pipefail SPACE_ID="${VANI_SPACE_ID:-radhapreetam/vani-speech-api}" HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SUB="$(echo "$SPACE_ID" | tr '/' '-' | tr '[:upper:]' '[:lower:]')" BASE="https://${SUB}.hf.space" say(){ printf '\n\033[1m== %s\033[0m\n' "$*"; } die(){ printf '\n\033[31mFAILED: %s\033[0m\n' "$*" >&2; exit 1; } # --- Gate 1: credential present (presence only, never the value) ------------ say "Gate 1/5 — checking you are logged in to Hugging Face" if ! WHO="$(hf auth whoami 2>&1)"; then die "Not logged in. Run 'hf auth login' yourself and paste your own token, then re-run this script. Never share the token." fi echo "Logged in as: $WHO" # --- Gate 2: the files we are about to ship are the ones we tested ---------- say "Gate 2/5 — checking the package is complete" for f in app.py requirements.txt SPACE_CARD.md packages.txt; do [ -f "$HERE/$f" ] || die "missing $f" done # On a Space, README.md is not documentation — its YAML frontmatter IS the # build configuration (sdk, sdk_version, app_file). The workspace automation # scripts/folder_readme.py rewrites README.md in every folder and would strip # that frontmatter, leaving a Space that cannot build. So README.md is # regenerated here, at upload time, from SPACE_CARD.md — which that automation # does not touch. cp "$HERE/SPACE_CARD.md" "$HERE/README.md" head -1 "$HERE/README.md" | grep -q '^---$' || die "SPACE_CARD.md has lost its YAML frontmatter — the Space will not build without it." grep -q '^app_file: app.py$' "$HERE/README.md" || die "Space card is missing 'app_file: app.py'." # Hugging Face rejects a Space whose short_description exceeds 60 characters, and it # only says so at UPLOAD time - after the repo has already been created. That cost a # real failed deploy on 2026-08-21 (61 characters, one over). Validate it HERE, before # anything is created, so the failure is cheap and self-explaining rather than a # mid-deploy error nobody can parse. _desc="$(sed -n 's/^short_description:[[:space:]]*//p' README.md | head -1)" _len=${#_desc} if [ "$_len" -gt 60 ]; then echo "STOP - short_description is ${_len} characters; Hugging Face allows 60." echo " Shorten it in SPACE_CARD.md (README.md is regenerated from it), then re-run." exit 1 fi echo "Space card regenerated from SPACE_CARD.md; frontmatter verified (short_description ${_len}/60)." grep -q "^mlx" "$HERE/requirements.txt" && die "requirements.txt contains an mlx package — those are Apple-silicon only and will not build on a Linux Space." python3 -c "import ast,sys; ast.parse(open('$HERE/app.py').read())" || die "app.py does not parse" echo "app.py parses; no Apple-silicon-only dependency present." # --- Gate 3: create the Space if it does not exist yet ---------------------- say "Gate 3/5 — ensuring the Space exists" if hf repos create "$SPACE_ID" --type space --sdk gradio 2>/dev/null; then echo "Created $SPACE_ID" else echo "$SPACE_ID already exists (or creation was declined) — continuing to upload." fi # --- Gate 4: upload ------------------------------------------------------- say "Gate 4/5 — uploading the package" hf upload "$SPACE_ID" "$HERE" . --repo-type space \ --exclude "__pycache__/*" --exclude "*.pyc" --exclude "DEPLOY.md" \ || die "upload failed" # --- Gate 5: prove it is actually live, from outside ----------------------- say "Gate 5/5 — proving it is live at $BASE (this includes a cold start; allow ~5 minutes)" echo "Waiting for /health to answer over https..." for i in $(seq 1 60); do if curl -fsS -m 15 "$BASE/health" >/tmp/vani_health.json 2>/dev/null; then echo "PROOF 1 — https health check answered from outside:"; cat /tmp/vani_health.json; echo; break fi printf '.'; sleep 15 [ "$i" -eq 60 ] && die "Space never answered /health. Open $BASE and read the build logs." done echo "Warming the models (first load downloads them)..." curl -fsS -m 900 -X POST "$BASE/warmup"; echo echo echo "PROOF 2 — real text in, real audio out:" curl -fsS -m 300 -X POST "$BASE/text-to-speech" \ -H 'Content-Type: application/json' \ -d '{"text":"Book the badminton court for Sunday morning."}' \ -o /tmp/vani_live.wav || die "text-to-speech failed" file /tmp/vani_live.wav echo echo "PROOF 3 — real audio in, real text out (feeding back the audio just generated):" curl -fsS -m 300 -X POST "$BASE/speech-to-text" \ -F "audio=@/tmp/vani_live.wav;type=audio/wav" || die "speech-to-text failed" echo echo echo "PROOF 4 — the page a stranger can open:" curl -fsS -m 30 -o /dev/null -w "GET %{url_effective} -> HTTP %{http_code}\n" "$BASE/" cat < Change visibility before sending the link to anyone else. NEXT, SEPARATELY: the website page is prepared but NOT deployed. Review "Working Files/Vani/website_wiring/vani-demo.hosted.patch" and apply it to rp-website only when you want the live site to switch over. =========================================================================== EOF