Files
Aryan SinghandClaude Opus 5 eeb5cad4ce Add a recorded demo, and the script that produces it
scripts/demo.sh is a real walkthrough: it starts the gateway, routes model:auto,
resolves four aliases to four different models, shows a cache hit costing zero
quota, recalls a name across a server-side session, 404s an unknown model with
suggestions, and prints where the tokens went. Nothing is staged — every line is
output the gateway actually produced against a live provider.

scripts/record-demo.html turns a captured transcript into video frames. ffmpeg
on macOS ships without freetype, so it can encode frames but cannot draw text;
the browser can do both, so frames are rasterised on a canvas and posted to a
local server for ffmpeg to assemble.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 22:53:45 -05:00

114 lines
5.9 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# A scripted walkthrough of Freeway against real providers.
#
# ./scripts/demo.sh # narrate + run
# ./scripts/demo.sh --transcript # same, but machine-readable for recording
#
# Everything here is a real request. Nothing is faked, and the output you see is
# the output the gateway actually produced.
set -uo pipefail
cd "$(dirname "$0")/.."
PORT="${DEMO_PORT:-8790}"
B="http://127.0.0.1:$PORT"
TRANSCRIPT=""
[[ "${1:-}" == "--transcript" ]] && TRANSCRIPT=1
if [[ -n "$TRANSCRIPT" ]]; then
C_DIM=''; C_B=''; C_G=''; C_C=''; C_Y=''; C_X=''
else
C_DIM=$'\033[2m'; C_B=$'\033[1m'; C_G=$'\033[32m'; C_C=$'\033[36m'; C_Y=$'\033[33m'; C_X=$'\033[0m'
fi
say() { printf '\n%s# %s%s\n' "$C_DIM" "$1" "$C_X"; }
cmd() { printf '%s$ %s%s\n' "$C_C" "$1" "$C_X"; }
pause(){ [[ -n "$TRANSCRIPT" ]] || sleep "${1:-1}"; }
cleanup() { [[ -n "${GW_PID:-}" ]] && kill "$GW_PID" 2>/dev/null; }
trap cleanup EXIT
# ---------------------------------------------------------------------------
printf '%s┌─ Freeway ─ one endpoint in front of every free LLM tier%s\n' "$C_B" "$C_X"
say "Start the gateway. No keys? It still starts — every provider becomes a card telling you what to set."
cmd "npm start"
PORT=$PORT node packages/gateway/dist/bin.js > /tmp/freeway-demo.log 2>&1 &
GW_PID=$!
for _ in $(seq 1 30); do curl -fsS "$B/healthz" >/dev/null 2>&1 && break; sleep 0.4; done
grep -E "listening|✓|○" /tmp/freeway-demo.log | head -6
pause 2
# ---------------------------------------------------------------------------
say "model:\"auto\" — you don't name a vendor. Freeway picks whoever is healthy and under quota."
cmd "curl \$B/v1/chat/completions -d '{\"model\":\"auto\",\"messages\":[…]}' -D-"
curl -sS "$B/v1/chat/completions" -H 'content-type: application/json' \
-d '{"model":"auto","messages":[{"role":"user","content":"In one sentence: what is a rate limit?"}],"max_tokens":60}' \
-D /tmp/h -o /tmp/b
grep -iE "^HTTP|^x-freeway" /tmp/h | sed "s/^/ ${C_G}/;s/\$/${C_X}/"
printf ' %s→%s %s\n' "$C_G" "$C_X" "$(node -e "
const j=JSON.parse(require('fs').readFileSync('/tmp/b','utf8'));
console.log(j.choices[0].message.content.replace(/\n/g,' ').slice(0,150));")"
pause 2
# ---------------------------------------------------------------------------
say "Aliases route by capability, not vendor. Same call, four different models."
for m in fast cheap code reason; do
curl -sS -o /tmp/b -D /tmp/h "$B/v1/chat/completions" -H 'content-type: application/json' \
-d "{\"model\":\"$m\",\"messages\":[{\"role\":\"user\",\"content\":\"reply with the single word: ok\"}],\"max_tokens\":8}"
printf ' %-7s → %-24s %s\n' "$m" \
"$(grep -i '^x-freeway-model' /tmp/h | cut -d' ' -f2 | tr -d '\r')" \
"$(grep -i '^x-freeway-ms' /tmp/h | cut -d' ' -f2 | tr -d '\r')ms"
sleep 0.7
done
pause 2
# ---------------------------------------------------------------------------
say "Ask the same thing twice and the second is free — served from cache, zero quota."
Q='{"model":"fast","temperature":0,"messages":[{"role":"user","content":"what is 2+2?"}],"max_tokens":20}'
curl -sS -o /dev/null "$B/v1/chat/completions" -H 'content-type: application/json' -d "$Q"
curl -sS -o /dev/null -D /tmp/h "$B/v1/chat/completions" -H 'content-type: application/json' -d "$Q"
printf ' %scache: %s provider: %s %s0 tokens spent%s\n' "$C_G" \
"$(grep -i '^x-freeway-cache' /tmp/h | cut -d' ' -f2 | tr -d '\r')" \
"$(grep -i '^x-freeway-provider' /tmp/h | cut -d' ' -f2 | tr -d '\r')" "$C_B" "$C_X"
pause 2
# ---------------------------------------------------------------------------
say "Sessions live server-side. Send only the new message; the gateway rebuilds the thread."
cmd "-d '{\"session\":\"demo\",\"messages\":[{\"content\":\"my name is Ada\"}]}'"
curl -sS -o /dev/null "$B/v1/chat/completions" -H 'content-type: application/json' \
-d '{"model":"fast","session":"demo","messages":[{"role":"user","content":"My name is Ada. Remember it."}],"max_tokens":30}'
cmd "-d '{\"session\":\"demo\",\"messages\":[{\"content\":\"what is my name?\"}]}'"
curl -sS -o /tmp/b -D /tmp/h "$B/v1/chat/completions" -H 'content-type: application/json' \
-d '{"model":"fast","session":"demo","messages":[{"role":"user","content":"What is my name?"}],"max_tokens":30}'
printf ' %s→%s %s\n' "$C_G" "$C_X" "$(node -e "
const j=JSON.parse(require('fs').readFileSync('/tmp/b','utf8'));
console.log(j.choices[0].message.content.replace(/\n/g,' ').slice(0,120));")"
printf ' %s%s%s\n' "$C_DIM" "$(grep -i '^x-freeway-context' /tmp/h | tr -d '\r')" "$C_X"
pause 2
# ---------------------------------------------------------------------------
say "Ask for a model that doesn't exist. You get a 404 that tells you what does."
curl -sS "$B/v1/chat/completions" -H 'content-type: application/json' \
-d '{"model":"gpt-5-turbo-ultra","messages":[{"role":"user","content":"hi"}]}' \
| node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);
console.log(' '+j.error.message);
console.log(' did you mean: '+j.freeway.suggestions.slice(0,3).join(', '));});"
pause 2
# ---------------------------------------------------------------------------
say "Where did the tokens go?"
curl -sS "$B/api/usage" | node -e "
let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s),t=j.totals;
console.log(' '+t.tokensIn+' in / '+t.tokensOut+' out over '+t.requests+' requests, '+t.avgMs+'ms avg');
console.log(' '+t.cached+' served from cache · '+t.failed+' failed');
for (const m of j.byModel) console.log(' '+m.id.padEnd(26)+(m.tokensIn+m.tokensOut)+' tok ×'+m.requests);});"
pause 2
# ---------------------------------------------------------------------------
say "Adding a provider is one JSON file. Never a code change."
cmd "node scripts/verify-provider.mjs mistral"
node scripts/verify-provider.mjs mistral 2>&1 | sed -n '3,14p' | sed "s/^/ /"
printf '\n%s└─ dashboard at %s · 12 providers bundled · zero runtime dependencies%s\n\n' "$C_B" "$B" "$C_X"