@@ -56,10 +56,10 @@ run_step() {
5656 local command=" $2 "
5757
5858 echo
59- printf " ${dim} # %s${reset} \n " " $description "
60- printf " ${bold}${green} \$ $ {reset} "
59+ printf ' %s # %s%s\n ' " ${dim} " " $description " " ${reset} "
60+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
6161 typewrite " $command " 0.035
62- printf " ${dim} ▌ ${reset} " # blinking-cursor illusion
62+ printf ' %s ▌%s ' " ${dim} " " ${reset} " # blinking-cursor illusion
6363
6464 # Wait for Enter
6565 read -r -s _
@@ -73,21 +73,21 @@ run_step() {
7373pause_comment () {
7474 local msg=" $1 "
7575 echo
76- printf " ${dim} # %s${reset }" " $msg "
76+ printf ' %s # %s%s ' " ${dim }" " $msg " " ${reset} "
7777 read -r -s _
7878 echo
7979}
8080
8181# Section banner
8282section () {
8383 echo
84- printf " ${bold}${blue} ┌──────────────────────────────────────────────────────┐${ reset}\n "
85- printf " ${bold}${blue} │ %-52s│${reset} \n " " $* "
86- printf " ${bold}${blue} └──────────────────────────────────────────────────────┘${ reset}\n "
84+ printf ' %s%s ┌──────────────────────────────────────────────────────┐%s\n ' " ${bold} " " ${blue} " " ${ reset}"
85+ printf ' %s%s │ %-52s│%s\n ' " ${bold} " " ${blue} " " $* " " ${reset} "
86+ printf ' %s%s └──────────────────────────────────────────────────────┘%s\n ' " ${bold} " " ${blue} " " ${ reset}"
8787}
8888
89- ok () { printf " ${green} ✓ %s${reset} \n " " $* " ; }
90- info (){ printf " ${cyan} %s${reset} \n " " $* " ; }
89+ ok () { printf ' %s ✓ %s%s\n ' " ${green} " " $* " " ${reset} " ; }
90+ info (){ printf ' %s %s%s\n ' " ${cyan} " " $* " " ${reset} " ; }
9191
9292pretty_json () {
9393 python3 -c " import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
@@ -99,7 +99,7 @@ wait_for_gateway() {
9999 local retries=30
100100 while ! curl -sf " ${GATEWAY_URL} /health" > /dev/null 2>&1 ; do
101101 retries=$(( retries - 1 ))
102- [[ $retries -eq 0 ]] && { printf " ${red} Gateway did not start${ reset}\n " ; exit 1; }
102+ [[ $retries -eq 0 ]] && { printf ' %sGateway did not start%s\n ' " ${red} " " ${ reset}" ; exit 1; }
103103 sleep 0.2
104104 done
105105}
@@ -136,7 +136,7 @@ trap 'stop_gateway' EXIT
136136# ─────────────────────────────────────────────────────────────────────────────
137137
138138clear
139- printf " ${bold}${blue} "
139+ printf ' %s%s ' " ${bold} " " ${blue} "
140140cat << 'BANNER '
141141 ██████╗ ██████╗ ██████╗ ██╗ ██╗ ███████╗ ██████╗
142142 ██╔══██╗ ██╔═══██╗ ██╔════╝ ██║ ██╔╝ ██╔════╝ ██╔══██╗
@@ -159,10 +159,10 @@ cat <<'BANNER'
159159 ╚██████╔╝ ██║ ██║ ██║ ███████╗ ╚███╔███╔╝ ██║ ██║ ██║
160160 ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝
161161BANNER
162- printf " ${reset} \n "
163- printf " ${dim} Press ${reset}${bold} Enter ${reset}${dim} to advance through each step. ${reset} \n "
164- printf " ${dim} The gateway is started behind the scenes — commands shown are${ reset}\n "
165- printf " ${dim} exactly what you would run in a real session.${ reset}\n "
162+ printf ' %s\n ' " ${reset} "
163+ printf ' %s Press %s%sEnter%s%s to advance through each step.%s\n ' " ${dim} " " ${reset} " " ${bold} " " ${reset} " " ${dim} " " ${reset} "
164+ printf ' %s The gateway is started behind the scenes — commands shown are%s\n ' " ${dim} " " ${ reset}"
165+ printf ' %s exactly what you would run in a real session.%s\n ' " ${dim} " " ${ reset}"
166166echo
167167
168168pause_comment " Let's begin — press Enter to start"
@@ -176,7 +176,7 @@ section "Step 1 — Write a gateway config"
176176pause_comment " The gateway is driven by a simple YAML file. Here's a basic one."
177177
178178echo
179- printf " ${dim} # demos/gateway/config-basic.yaml${ reset}\n "
179+ printf ' %s # demos/gateway/config-basic.yaml%s\n ' " ${dim} " " ${ reset}"
180180cat " ${SCRIPT_DIR} /config-basic.yaml"
181181
182182pause_comment " Press Enter to start the gateway"
@@ -189,12 +189,12 @@ section "Step 2 — Start the gateway"
189189
190190# Show the pretty command; actually run our binary in the background
191191echo
192- printf " ${dim} # Starts an OpenAI-compatible proxy on :4000${ reset}\n "
193- printf " ${bold}${green} \$ $ {reset} "
192+ printf ' %s # Starts an OpenAI-compatible proxy on :4000%s\n ' " ${dim} " " ${ reset}"
193+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
194194typewrite " docker model gateway --config demos/gateway/config-basic.yaml" 0.035
195- printf " ${dim} ▌ ${reset} "
195+ printf ' %s ▌%s ' " ${dim} " " ${reset} "
196196read -r -s _
197- printf " \r ${bold}${green} \$ ${reset} ${white} docker model gateway --config demos/gateway/config-basic.yaml${ reset}\n "
197+ printf ' \r%s%s$%s %sdocker model gateway --config demos/gateway/config-basic.yaml%s\n ' " ${bold} " " ${green} " " ${ reset}" " ${white} " " ${reset} "
198198
199199# Actually launch
200200launch_gateway " ${SCRIPT_DIR} /config-basic.yaml"
@@ -230,12 +230,12 @@ section "Step 5 — Auth enforcement"
230230pause_comment " The gateway rejects requests with the wrong key"
231231
232232echo
233- printf " ${dim} # Wrong key → 401${ reset}\n "
234- printf " ${bold}${green} \$ $ {reset} "
233+ printf ' %s # Wrong key → 401%s\n ' " ${dim} " " ${ reset}"
234+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
235235typewrite " curl -s -o /dev/null -w '%{http_code}' http://localhost:${GATEWAY_PORT} /v1/chat/completions -H 'Authorization: Bearer WRONG'" 0.03
236- printf " ${dim} ▌ ${reset} "
236+ printf ' %s ▌%s ' " ${dim} " " ${reset} "
237237read -r -s _
238- printf " \r ${bold}${green} \$ ${reset} ${white} curl -s -o /dev/null -w '%%{http_code}' .../v1/chat/completions -H 'Authorization: Bearer WRONG'${ reset}\n "
238+ printf ' \r%s%s$%s %scurl -s -o /dev/null -w ' \' ' %%{http_code}' \' ' .../v1/chat/completions -H ' \' ' Authorization: Bearer WRONG' \' ' %s\n ' " ${bold} " " ${green} " " ${ reset}" " ${white} " " ${reset} "
239239
240240HTTP_CODE=$( curl -s -o /dev/null -w " %{http_code}" \
241241 -X POST " ${GATEWAY_URL} /v1/chat/completions" \
@@ -254,16 +254,16 @@ section "Step 6 — Chat completion"
254254pause_comment " Standard OpenAI-compatible chat completions endpoint"
255255
256256echo
257- printf " ${dim} # POST /v1/chat/completions — non-streaming${ reset}\n "
258- printf " ${bold}${green} \$ $ {reset} "
257+ printf ' %s # POST /v1/chat/completions — non-streaming%s\n ' " ${dim} " " ${ reset}"
258+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
259259typewrite " curl -s http://localhost:${GATEWAY_PORT} /v1/chat/completions \\ " 0.03
260- printf " \n "
260+ printf ' \n '
261261typewrite " -H 'Authorization: Bearer ${API_KEY} ' \\ " 0.03
262- printf " \n "
262+ printf ' \n '
263263typewrite " -d '{\" model\" :\" smollm2\" ,\" messages\" :[{\" role\" :\" user\" ,\" content\" :\" What is Docker Model Runner?\" }],\" max_tokens\" :80}'" 0.03
264- printf " ${dim} ▌ ${reset} "
264+ printf ' %s ▌%s ' " ${dim} " " ${reset} "
265265read -r -s _
266- printf " \r ${bold}${green} \$ ${reset} ${white} curl -s .../v1/chat/completions -H 'Authorization: Bearer ${API_KEY} ' -d '{...}'${reset} \ n\n"
266+ printf ' \r%s%s$%s %scurl -s .../v1/chat/completions -H ' \' ' Authorization: Bearer %s ' \' ' -d ' \' ' {...}' \' ' %s\ n\n' " ${bold} " " ${green} " " ${reset} " " ${white} " " ${API_KEY} " " ${reset} "
267267
268268curl -sf -X POST " ${GATEWAY_URL} /v1/chat/completions" \
269269 -H " Content-Type: application/json" \
@@ -286,16 +286,16 @@ section "Step 7 — Streaming (SSE)"
286286pause_comment " Add stream:true — tokens arrive in real time"
287287
288288echo
289- printf " ${dim} # Same endpoint, stream:true → server-sent events${ reset}\n "
290- printf " ${bold}${green} \$ $ {reset} "
289+ printf ' %s # Same endpoint, stream:true → server-sent events%s\n ' " ${dim} " " ${ reset}"
290+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
291291typewrite " curl -sN http://localhost:${GATEWAY_PORT} /v1/chat/completions \\ " 0.03
292- printf " \n "
292+ printf ' \n '
293293typewrite " -H 'Authorization: Bearer ${API_KEY} ' \\ " 0.03
294- printf " \n "
294+ printf ' \n '
295295typewrite " -d '{\" model\" :\" smollm2\" ,\" messages\" :[{\" role\" :\" user\" ,\" content\" :\" Count 1 to 5\" }],\" stream\" :true}'" 0.03
296- printf " ${dim} ▌ ${reset} "
296+ printf ' %s ▌%s ' " ${dim} " " ${reset} "
297297read -r -s _
298- printf " \r ${bold}${green} \$ ${reset} ${white} curl -sN .../v1/chat/completions -d '{...stream:true...}'${reset} \ n\n"
298+ printf ' \r%s%s$%s %scurl -sN .../v1/chat/completions -d ' \' ' {...stream:true...}' \' ' %s\ n\n' " ${bold} " " ${green} " " ${reset} " " ${white} " " ${reset} "
299299
300300curl -sfN -X POST " ${GATEWAY_URL} /v1/chat/completions" \
301301 -H " Content-Type: application/json" \
@@ -327,15 +327,15 @@ section "Step 8 — Advanced config: load balancing & fallbacks"
327327pause_comment " Restart the gateway with the advanced config"
328328
329329echo
330- printf " ${dim} # config-advanced.yaml${ reset}\n "
330+ printf ' %s # config-advanced.yaml%s\n ' " ${dim} " " ${ reset}"
331331cat " ${SCRIPT_DIR} /config-advanced.yaml"
332332echo
333333
334- printf " ${bold}${green} \$ $ {reset} "
334+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
335335typewrite " docker model gateway --config demos/gateway/config-advanced.yaml" 0.035
336- printf " ${dim} ▌ ${reset} "
336+ printf ' %s ▌%s ' " ${dim} " " ${reset} "
337337read -r -s _
338- printf " \r ${bold}${green} \$ ${reset} ${white} docker model gateway --config demos/gateway/config-advanced.yaml${ reset}\n "
338+ printf ' \r%s%s$%s %sdocker model gateway --config demos/gateway/config-advanced.yaml%s\n ' " ${bold} " " ${green} " " ${ reset}" " ${white} " " ${reset} "
339339
340340stop_gateway
341341launch_gateway " ${SCRIPT_DIR} /config-advanced.yaml"
@@ -351,12 +351,12 @@ section "Step 9 — Round-robin load balancing"
351351pause_comment " 'fast-model' has 2 backends — watch them alternate across 4 requests"
352352
353353echo
354- printf " ${dim} # 4 requests to 'fast-model' → round-robins across smollm2 + qwen3${ reset}\n "
355- printf " ${bold}${green} \$ $ {reset} "
354+ printf ' %s # 4 requests to ' \' ' fast-model' \' ' → round-robins across smollm2 + qwen3%s\n ' " ${dim} " " ${ reset}"
355+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
356356typewrite " for i in 1 2 3 4; do curl -s .../v1/chat/completions -d '{\" model\" :\" fast-model\" ,...}'; done" 0.03
357- printf " ${dim} ▌ ${reset} "
357+ printf ' %s ▌%s ' " ${dim} " " ${reset} "
358358read -r -s _
359- printf " \r ${bold}${green} \$ ${reset} ${white} for i in 1 2 3 4; do curl -s .../v1/chat/completions ...; done${reset} \n\n"
359+ printf ' \r%s%s$%s %sfor i in 1 2 3 4; do curl -s .../v1/chat/completions ...; done%s \n\n' " ${bold} " " ${green} " " ${reset} " " ${white} " " ${reset} "
360360
361361for i in 1 2 3 4; do
362362 resp=$( curl -sf -X POST " ${GATEWAY_URL} /v1/chat/completions" \
@@ -385,18 +385,18 @@ section "Step 10 — Embeddings (nomic-embed-text)"
385385pause_comment " Dedicated embedding model behind its own alias"
386386
387387echo
388- printf " ${dim} # POST /v1/embeddings — two sentences, then compute cosine similarity${ reset}\n "
389- printf " ${bold}${green} \$ $ {reset} "
388+ printf ' %s # POST /v1/embeddings — two sentences, then compute cosine similarity%s\n ' " ${dim} " " ${ reset}"
389+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
390390typewrite " curl -s -X POST http://localhost:${GATEWAY_PORT} /v1/embeddings \\ " 0.03
391- printf " \n "
391+ printf ' \n '
392392typewrite " -H 'Content-Type: application/json' \\ " 0.03
393- printf " \n "
393+ printf ' \n '
394394typewrite " -H 'Authorization: Bearer ${API_KEY} ' \\ " 0.03
395- printf " \n "
395+ printf ' \n '
396396typewrite " -d '{\" model\" :\" embeddings\" ,\" input\" :[\" The quick brown fox\" ,\" A fast auburn canine\" ]}'" 0.03
397- printf " ${dim} ▌ ${reset} "
397+ printf ' %s ▌%s ' " ${dim} " " ${reset} "
398398read -r -s _
399- printf " \r ${bold}${green} \$ ${reset} ${white} curl -s -X POST .../v1/embeddings -d '{...}'${reset} \ n\n"
399+ printf ' \r%s%s$%s %scurl -s -X POST .../v1/embeddings -d ' \' ' {...}' \' ' %s\ n\n' " ${bold} " " ${green} " " ${reset} " " ${white} " " ${reset} "
400400
401401curl -sf -X POST " ${GATEWAY_URL} /v1/embeddings" \
402402 -H " Content-Type: application/json" \
@@ -426,7 +426,7 @@ section "Step 11 — OpenAI Python SDK compatibility"
426426pause_comment " Any app already using the openai library works with zero code changes"
427427
428428echo
429- printf " ${dim} # python demo — just swap base_url to point at the gateway${ reset}\n "
429+ printf ' %s # python demo — just swap base_url to point at the gateway%s\n ' " ${dim} " " ${ reset}"
430430cat << 'PYSHOW '
431431 from openai import OpenAI
432432
@@ -443,11 +443,11 @@ cat <<'PYSHOW'
443443 print(resp.choices[0].message.content)
444444PYSHOW
445445
446- printf " ${bold}${green} \$ $ {reset} "
446+ printf ' %s%s$%s ' " ${bold} " " ${green} " " $ {reset}"
447447typewrite " python3 demo.py" 0.05
448- printf " ${dim} ▌ ${reset} "
448+ printf ' %s ▌%s ' " ${dim} " " ${reset} "
449449read -r -s _
450- printf " \r $ {bold}${green}\$ $ {reset} ${white} python3 demo.py ${reset} \n\n "
450+ printf ' \r%s%s$%s %spython3 demo.py%s\n\n ' " $ {bold}" " ${green} " " $ {reset}" " ${white} " " ${reset} "
451451
452452if python3 -c " import openai" 2> /dev/null; then
453453 python3 - << 'PYEOF '
@@ -471,7 +471,7 @@ PYEOF
471471 echo
472472 ok " OpenAI SDK works against the gateway — no code changes required"
473473else
474- printf " ${yellow} (skipped — openai package not installed: pip install openai)${ reset}\n "
474+ printf ' %s (skipped — openai package not installed: pip install openai)%s\n ' " ${yellow} " " ${ reset}"
475475 ok " OpenAI SDK step skipped — install openai to run it"
476476fi
477477
482482section " Demo complete"
483483
484484echo
485- printf " ${bold} What we showed:${ reset}\n "
485+ printf ' %s What we showed:%s\n ' " ${bold} " " ${ reset}"
486486info " YAML-driven config — models, auth, retries, fallbacks"
487487info " /health /v1/models /v1/chat/completions /v1/embeddings"
488488info " Bearer-token auth (accept ✓ reject 401 ✓)"
0 commit comments