dryymatt commited on
Commit
74a78ff
Β·
verified Β·
1 Parent(s): dc0ae95

Upload server.py

Browse files
Files changed (1) hide show
  1. server.py +62 -172
server.py CHANGED
@@ -18,10 +18,9 @@ from core import (
18
  )
19
  from ghost_deploy import ghost, CANONICAL_REPO, PinggyTunnel
20
 
21
- # ─── STABLE VIBE: Port 8765 for HF Spaces Docker routing ───
22
- # The Dockerfile exposes 8765; HF reverse proxy routes to it.
23
- # Override via WIZARD_PORT only in dev sandboxes.
24
- PORT = int(os.environ.get("WIZARD_PORT", 8765))
25
  STATIC = Path(__file__).parent / "static"
26
  WIZARD_HAT_COLOR = "steady-gold" # The Athanor is active
27
 
@@ -31,19 +30,11 @@ def _sse(ev, d):
31
 
32
 
33
  async def stream_gen(sid, prompt):
34
- """
35
- Omni-Vibe streaming pipeline:
36
- 1. POSE β€” all three agents analyze in parallel
37
- 2. GENERATE β€” Pose Architect + Pose Painter produce code
38
- 3. AUDIT β€” Pose Auditor verifies step-by-step
39
- 4. HEAL β€” Reflect-Select loop until perfection
40
- 5. DEPLOY β€” Ghost Deploy to HF Spaces + Pinggy tunnel
41
- """
42
  session = st.sessions[sid]
43
  session["status"] = "streaming"
44
  st.sandbox[sid] = "building"
45
 
46
- # Phase 1: Pose
47
  yield _sse("phase", {"phase": "pose", "hat": WIZARD_HAT_COLOR})
48
  await asyncio.sleep(0.05)
49
 
@@ -58,7 +49,6 @@ async def stream_gen(sid, prompt):
58
  "auditor": pose_plan["auditor"],
59
  })
60
 
61
- # Phase 2: Generate
62
  yield _sse("phase", {"phase": "generate"})
63
  await asyncio.sleep(0.05)
64
 
@@ -66,231 +56,153 @@ async def stream_gen(sid, prompt):
66
  code, schema = st.engine.generate(prompt)
67
  elapsed = (time.time() - t0) * 1000
68
 
69
- # Stream in chunks for live UI
70
  chunk_size = max(1, len(code) // 30)
71
  for i in range(0, len(code), chunk_size):
72
  chunk = code[i:i + chunk_size]
73
  yield _sse("code", {
74
- "chunk": chunk,
75
- "partial": code[:i + chunk_size],
76
  "progress": min(100, int((i + chunk_size) / len(code) * 100)),
77
  })
78
  await asyncio.sleep(0.015)
79
 
80
  st.codes[sid] = code
81
  st.sessions[sid]["schema"] = schema
82
- yield _sse("generated", {
83
- "chars": len(code),
84
- "elapsed_ms": round(elapsed, 1),
85
- "schema": {k: v for k, v in schema.items() if k != "files_needed"},
86
- })
87
 
88
- # Phase 3: Audit
89
  yield _sse("phase", {"phase": "audit"})
90
  await asyncio.sleep(0.05)
91
-
92
  findings = st.engine.auditor.audit(code, schema)
93
- yield _sse("audit", {
94
- "findings": len(findings),
95
  "errors": sum(1 for f in findings if f.severity == "ERROR"),
96
  "warnings": sum(1 for f in findings if f.severity == "WARN"),
97
  "details": [{"line": f.line, "severity": f.severity, "message": f.message}
98
- for f in findings[:5]],
99
- })
100
 
101
- # Phase 4: Heal (Reflect-Select)
102
  yield _sse("phase", {"phase": "heal"})
103
-
104
  code, auto_fixes = st.engine.auditor.heal_findings(code, findings)
105
  st.codes[sid] = code
106
-
107
  healed, found, fixed = st.reflect.heal(code)
108
  total_heals = fixed + auto_fixes
109
-
110
  for i in range(15):
111
  result = sandbox_validate(healed)
112
- if result["success"]:
113
- break
114
  healed, _, extra = st.reflect.heal(healed, result["errors"])
115
  total_heals += extra
116
  st.codes[sid] = healed
117
  await asyncio.sleep(0.01)
 
 
118
 
119
- yield _sse("heal", {
120
- "auto_fixes": auto_fixes,
121
- "reflect_fixes": fixed,
122
- "total_heals": total_heals,
123
- "loop_iterations": i + 1,
124
- })
125
-
126
- # Phase 5: Sandbox Validation
127
  yield _sse("phase", {"phase": "sandbox"})
128
  result = sandbox_validate(healed)
129
  if result["success"]:
130
- st.sandbox[sid] = "stable"
131
- st.publish_ready[sid] = True
132
  yield _sse("sandbox", {"status": "stable", "errors": 0, "hat": "steady-gold"})
133
  else:
134
  st.sandbox[sid] = "error"
135
  yield _sse("sandbox", {"status": "error", "errors": result["errors"]})
136
-
137
  session["status"] = "complete"
138
- yield _sse("done", {
139
- "status": st.sandbox[sid],
140
- "hat": "steady-gold" if st.sandbox[sid] == "stable" else "red-glow",
141
- })
142
 
143
 
144
  async def handle_stream(req):
145
  sid = str(uuid.uuid4())[:8]
146
- d = await req.json()
147
- prompt = d.get("prompt", "")
148
-
149
  st.sessions[sid] = {"id": sid, "prompt": prompt, "status": "init"}
150
- st.sandbox[sid] = "building"
151
- st.publish_ready[sid] = False
152
-
153
- resp = web.StreamResponse(
154
- status=200,
155
- headers={
156
- "Content-Type": "text/event-stream",
157
- "Cache-Control": "no-cache",
158
- "Connection": "keep-alive",
159
- "X-Accel-Buffering": "no",
160
- },
161
- )
162
  await resp.prepare(req)
163
  try:
164
- async for ev in stream_gen(sid, prompt):
165
- await resp.write(ev.encode())
166
  await resp.write(b"event: close\ndata: {}\n\n")
167
  except Exception as e:
168
- await resp.write(
169
- f"event: error\ndata: {json.dumps({'error': str(e)})}\n\n".encode()
170
- )
171
  return resp
172
 
173
 
174
  async def handle_publish(req):
175
- d = await req.json()
176
- sid = d.get("session_id")
177
  vibe_name = d.get("vibe_name", f"omni-vibe-{sid}")
178
-
179
  if not st.publish_ready.get(sid):
180
- return web.json_response(
181
- {"success": False, "error": "Sandbox not stable β€” Pose Auditor requires perfection."},
182
- status=400,
183
- )
184
-
185
  code = st.codes.get(sid, "")
186
- if not code:
187
- return web.json_response({"success": False, "error": "No code generated"}, status=400)
188
-
189
- description = d.get("description", st.sessions.get(sid, {}).get("prompt", ""))
190
  result = await ghost.publish(code, vibe_name, description[:200] if description else "", port=PORT)
191
-
192
  if result.get("success"):
193
  st.sessions[sid]["published"] = True
194
  st.sessions[sid]["deploy_url"] = result.get("space_url")
195
- st.sessions[sid]["tunnel_url"] = result.get("tunnel_url")
196
-
197
  return web.json_response(result)
198
 
199
 
200
  async def handle_health(req):
201
  return web.json_response({
202
- "status": "alive",
203
- "engine": "Omni-Vibe Studio β€” Specialized Swarm",
204
  "protocol": "omni-vibe / tokenless / shadow-hosting",
205
- "registry": CANONICAL_REPO,
206
- "lifert": True,
207
- "poses": {
208
- "architect": "full-stack + zero-DB + Google OAuth",
209
- "painter": "Liquid Glass design system",
210
- "auditor": "step-by-step verification",
211
- },
212
- "hat": WIZARD_HAT_COLOR,
213
- "hf_token": bool(os.environ.get("HF_TOKEN")),
214
  })
215
 
216
 
217
  async def handle_agent(req):
218
  return web.json_response(ghost.generate_agent_card(
219
  "omni-vibe-studio",
220
- "Omni-Vibe Studio β€” Specialized AI Swarm. Pose Architect designs full-stack schemas with zero-config DB and Google OAuth. Pose Painter enforces Liquid Glass design. Pose Auditor verifies step-by-step. LiteRT engine for sub-second latency.",
221
- "https://dryymatt-wizard-vibe-studio-v2.hf.space",
222
- ))
223
 
224
 
225
  async def handle_preview(req):
226
  sid = req.query.get("session_id", "")
227
- return web.Response(
228
- text=st.codes.get(sid, "<!-- Omni-Vibe β€” no code -->"),
229
- content_type="text/html",
230
- )
231
 
232
 
233
  async def handle_status(req):
234
  sid = req.query.get("session_id", "")
235
  if sid in st.sessions:
236
  s = st.sessions[sid]
237
- return web.json_response({
238
- "status": s["status"],
239
- "sandbox": st.sandbox.get(sid),
240
- "ready": st.publish_ready.get(sid),
241
- "deploy_url": s.get("deploy_url"),
242
- "tunnel_url": s.get("tunnel_url"),
243
- "hat": "steady-gold" if st.publish_ready.get(sid) else "cyan-blink",
244
- })
245
- return web.json_response({
246
- "sessions": len(st.sessions),
247
- "engine": "omni-vibe",
248
- "hat": WIZARD_HAT_COLOR,
249
- })
250
 
251
 
252
  async def handle_vibes(req):
253
  vibes = await ghost.list_vibes()
254
- return web.json_response({
255
- "vibes": vibes,
256
- "count": len(vibes),
257
- "registry": CANONICAL_REPO,
258
- })
259
 
260
 
261
  async def handle_tunnel(req):
262
- """Bring up an ephemeral Pinggy/Cloudflare tunnel for instant preview."""
263
  try:
264
  url = await ghost.tunnel.up(PORT, timeout=10.0)
265
- return web.json_response({"success": bool(url), "url": url})
266
  except Exception as e:
267
- return web.json_response({"success": False, "error": str(e)}, status=500)
268
 
269
 
270
  async def handle_schema(req):
271
- """Get the full-stack schema for the current session."""
272
  sid = req.query.get("session_id", "")
273
- s = st.sessions.get(sid, {})
274
- schema = s.get("schema", {})
275
- return web.json_response(schema)
276
 
277
 
278
- # ═══════════════════════════════════════════════════════
279
- # ROOT HANDLER β€” HF Platform Health-Check Protocol
280
- # ═══════════════════════════════════════════════════════
281
- # HF Spaces sends periodic HEAD/GET probes to /.
282
- # aiohttp auto-registers HEAD for every GET route.
283
- # A 404 or timeout triggers "Starting" loops.
284
- # This handler returns 200 OK in <1ms.
285
 
286
  async def handle_root_get(req):
287
- """GET / β†’ Liquid Glass landing page with 200 OK."""
288
  fp = STATIC / "index.html"
289
  if fp.exists():
290
  return web.Response(text=fp.read_text(), content_type="text/html")
291
  return web.Response(text=INDEX_FALLBACK, content_type="text/html")
292
 
293
- # Static fallback for when static/index.html is missing
294
  INDEX_FALLBACK = """<!DOCTYPE html>
295
  <html lang="en">
296
  <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
@@ -307,28 +219,22 @@ p{color:var(--text2);margin-top:1rem;font-size:1.1rem}
307
  <body><div class="glass">
308
  <h1 class="gradient-text">Omni-Vibe Studio</h1>
309
  <p>Specialized Swarm β€” Steady Gold</p>
310
- <p style="font-size:.85rem;color:#606080;margin-top:2rem">Pose Architect Β· Pose Painter Β· Pose Auditor</p>
311
  </div></body></html>"""
312
 
313
 
314
- # ─── Static file handler (for all non-root paths) ───
315
-
316
  async def handle_static(req):
317
  path = req.match_info.get("path", "index.html")
318
  fp = STATIC / path
319
  if not fp.exists():
320
  return web.Response(text="Not found", status=404)
321
- ct = {".html": "text/html", ".css": "text/css", ".js": "application/javascript"}
322
- return web.Response(text=fp.read_text(), content_type=ct.get(fp.suffix, "text/plain"))
323
 
324
 
325
  def create_app():
326
  app = web.Application()
327
-
328
- # ─── Root: GET returns Liquid Glass; aiohttp auto-adds HEAD β†’ 200 ───
329
  app.router.add_get("/", handle_root_get)
330
-
331
- # ─── API routes ───
332
  app.router.add_post("/api/stream", handle_stream)
333
  app.router.add_post("/api/publish", handle_publish)
334
  app.router.add_get("/api/status", handle_status)
@@ -338,10 +244,7 @@ def create_app():
338
  app.router.add_get("/api/tunnel", handle_tunnel)
339
  app.router.add_get("/api/schema", handle_schema)
340
  app.router.add_get("/.well-known/agent.json", handle_agent)
341
-
342
- # ─── Catch-all static (after explicit root) ───
343
  app.router.add_get("/{path:.*}", handle_static)
344
-
345
  return app
346
 
347
 
@@ -352,49 +255,36 @@ def main():
352
  print(f" Pose Auditor : step-by-step verification")
353
  print(f" Athanor : LiteRT engine, sub-second latency")
354
  print(f" Registry : {CANONICAL_REPO}")
355
- print(f" Port : {PORT} (hardcoded 7860)")
356
  print(f" Hat : {WIZARD_HAT_COLOR}")
357
 
358
- # ─── Ghost-Link Persistence: start tunnel watchdog ───
359
  tunnel = ghost.tunnel
360
  loop = asyncio.new_event_loop()
361
  asyncio.set_event_loop(loop)
362
 
363
  async def tunnel_watchdog(interval: float = 30.0):
364
- """Keep the Pinggy/Cloudflare tunnel alive. Restart on drop."""
365
- await asyncio.sleep(3) # let the server bind first
366
  retries = 0
367
  while True:
368
  try:
369
  if tunnel.url is None:
370
- print(f"πŸ”— Tunnel: attempting connect (retry #{retries})…")
371
  url = await tunnel.up(PORT, timeout=10.0)
372
  if url:
373
  print(f"πŸ”— Tunnel: LIVE β†’ {url}")
374
  retries = 0
375
- else:
376
- retries += 1
377
  else:
378
- # Health check: curl the tunnel URL
379
- alive = await _ping_url(tunnel.url)
380
- if not alive:
381
- print(f"πŸ”— Tunnel: DEAD ({tunnel.url}) β€” restarting…")
382
- tunnel.down()
383
- retries += 1
384
  except Exception as e:
385
- print(f"πŸ”— Tunnel: error β€” {e}")
386
- retries += 1
387
  await asyncio.sleep(interval * (1 + min(retries, 5) * 0.5))
388
 
389
- async def _ping_url(url: str) -> bool:
390
- try:
391
- import aiohttp
392
- async with aiohttp.ClientSession() as s:
393
- async with s.get(url, timeout=aiohttp.ClientTimeout(total=5)) as r:
394
- return r.status < 500
395
- except Exception:
396
- return False
397
-
398
  loop.create_task(tunnel_watchdog())
399
  web.run_app(create_app(), host="0.0.0.0", port=PORT, handle_signals=True)
400
 
 
18
  )
19
  from ghost_deploy import ghost, CANONICAL_REPO, PinggyTunnel
20
 
21
+ # ─── VISIBILITY OVERRIDE: Port 7860 hardcoded, 0.0.0.0 binding ───
22
+ # Binds strictly to 0.0.0.0:7860. Override via WIZARD_PORT only in dev.
23
+ PORT = int(os.environ.get("WIZARD_PORT", 7860))
 
24
  STATIC = Path(__file__).parent / "static"
25
  WIZARD_HAT_COLOR = "steady-gold" # The Athanor is active
26
 
 
30
 
31
 
32
  async def stream_gen(sid, prompt):
33
+ """Omni-Vibe streaming pipeline: Pose β†’ Generate β†’ Audit β†’ Heal β†’ Sandbox"""
 
 
 
 
 
 
 
34
  session = st.sessions[sid]
35
  session["status"] = "streaming"
36
  st.sandbox[sid] = "building"
37
 
 
38
  yield _sse("phase", {"phase": "pose", "hat": WIZARD_HAT_COLOR})
39
  await asyncio.sleep(0.05)
40
 
 
49
  "auditor": pose_plan["auditor"],
50
  })
51
 
 
52
  yield _sse("phase", {"phase": "generate"})
53
  await asyncio.sleep(0.05)
54
 
 
56
  code, schema = st.engine.generate(prompt)
57
  elapsed = (time.time() - t0) * 1000
58
 
 
59
  chunk_size = max(1, len(code) // 30)
60
  for i in range(0, len(code), chunk_size):
61
  chunk = code[i:i + chunk_size]
62
  yield _sse("code", {
63
+ "chunk": chunk, "partial": code[:i + chunk_size],
 
64
  "progress": min(100, int((i + chunk_size) / len(code) * 100)),
65
  })
66
  await asyncio.sleep(0.015)
67
 
68
  st.codes[sid] = code
69
  st.sessions[sid]["schema"] = schema
70
+ yield _sse("generated", {"chars": len(code), "elapsed_ms": round(elapsed, 1),
71
+ "schema": {k: v for k, v in schema.items() if k != "files_needed"}})
 
 
 
72
 
 
73
  yield _sse("phase", {"phase": "audit"})
74
  await asyncio.sleep(0.05)
 
75
  findings = st.engine.auditor.audit(code, schema)
76
+ yield _sse("audit", {"findings": len(findings),
 
77
  "errors": sum(1 for f in findings if f.severity == "ERROR"),
78
  "warnings": sum(1 for f in findings if f.severity == "WARN"),
79
  "details": [{"line": f.line, "severity": f.severity, "message": f.message}
80
+ for f in findings[:5]]})
 
81
 
 
82
  yield _sse("phase", {"phase": "heal"})
 
83
  code, auto_fixes = st.engine.auditor.heal_findings(code, findings)
84
  st.codes[sid] = code
 
85
  healed, found, fixed = st.reflect.heal(code)
86
  total_heals = fixed + auto_fixes
 
87
  for i in range(15):
88
  result = sandbox_validate(healed)
89
+ if result["success"]: break
 
90
  healed, _, extra = st.reflect.heal(healed, result["errors"])
91
  total_heals += extra
92
  st.codes[sid] = healed
93
  await asyncio.sleep(0.01)
94
+ yield _sse("heal", {"auto_fixes": auto_fixes, "reflect_fixes": fixed,
95
+ "total_heals": total_heals, "loop_iterations": i + 1})
96
 
 
 
 
 
 
 
 
 
97
  yield _sse("phase", {"phase": "sandbox"})
98
  result = sandbox_validate(healed)
99
  if result["success"]:
100
+ st.sandbox[sid] = "stable"; st.publish_ready[sid] = True
 
101
  yield _sse("sandbox", {"status": "stable", "errors": 0, "hat": "steady-gold"})
102
  else:
103
  st.sandbox[sid] = "error"
104
  yield _sse("sandbox", {"status": "error", "errors": result["errors"]})
 
105
  session["status"] = "complete"
106
+ yield _sse("done", {"status": st.sandbox[sid],
107
+ "hat": "steady-gold" if st.sandbox[sid] == "stable" else "red-glow"})
 
 
108
 
109
 
110
  async def handle_stream(req):
111
  sid = str(uuid.uuid4())[:8]
112
+ d = await req.json(); prompt = d.get("prompt", "")
 
 
113
  st.sessions[sid] = {"id": sid, "prompt": prompt, "status": "init"}
114
+ st.sandbox[sid] = "building"; st.publish_ready[sid] = False
115
+ resp = web.StreamResponse(status=200, headers={
116
+ "Content-Type": "text/event-stream", "Cache-Control": "no-cache",
117
+ "Connection": "keep-alive", "X-Accel-Buffering": "no"})
 
 
 
 
 
 
 
 
118
  await resp.prepare(req)
119
  try:
120
+ async for ev in stream_gen(sid, prompt): await resp.write(ev.encode())
 
121
  await resp.write(b"event: close\ndata: {}\n\n")
122
  except Exception as e:
123
+ await resp.write(f"event: error\ndata: {json.dumps({'error':str(e)})}\n\n".encode())
 
 
124
  return resp
125
 
126
 
127
  async def handle_publish(req):
128
+ d = await req.json(); sid = d.get("session_id")
 
129
  vibe_name = d.get("vibe_name", f"omni-vibe-{sid}")
 
130
  if not st.publish_ready.get(sid):
131
+ return web.json_response({"success":False,"error":"Sandbox not stable"}, status=400)
 
 
 
 
132
  code = st.codes.get(sid, "")
133
+ if not code: return web.json_response({"success":False,"error":"No code"}, status=400)
134
+ description = d.get("description", st.sessions.get(sid,{}).get("prompt",""))
 
 
135
  result = await ghost.publish(code, vibe_name, description[:200] if description else "", port=PORT)
 
136
  if result.get("success"):
137
  st.sessions[sid]["published"] = True
138
  st.sessions[sid]["deploy_url"] = result.get("space_url")
 
 
139
  return web.json_response(result)
140
 
141
 
142
  async def handle_health(req):
143
  return web.json_response({
144
+ "status": "alive", "engine": "Omni-Vibe Studio β€” Specialized Swarm",
 
145
  "protocol": "omni-vibe / tokenless / shadow-hosting",
146
+ "registry": CANONICAL_REPO, "lifert": True,
147
+ "poses": {"architect": "full-stack + zero-DB + Google OAuth",
148
+ "painter": "Liquid Glass design system",
149
+ "auditor": "step-by-step verification"},
150
+ "hat": WIZARD_HAT_COLOR, "hf_token": bool(os.environ.get("HF_TOKEN")),
151
+ "binding": "0.0.0.0:7860",
 
 
 
152
  })
153
 
154
 
155
  async def handle_agent(req):
156
  return web.json_response(ghost.generate_agent_card(
157
  "omni-vibe-studio",
158
+ "Omni-Vibe Studio β€” Specialized AI Swarm. 0.0.0.0:7860. Zero-config DB, Google OAuth, Liquid Glass.",
159
+ "https://dryymatt-wizard-vibe-studio-v2.hf.space"))
 
160
 
161
 
162
  async def handle_preview(req):
163
  sid = req.query.get("session_id", "")
164
+ return web.Response(text=st.codes.get(sid, "<!-- Omni-Vibe -->"), content_type="text/html")
 
 
 
165
 
166
 
167
  async def handle_status(req):
168
  sid = req.query.get("session_id", "")
169
  if sid in st.sessions:
170
  s = st.sessions[sid]
171
+ return web.json_response({"status":s["status"],"sandbox":st.sandbox.get(sid),
172
+ "ready":st.publish_ready.get(sid),"deploy_url":s.get("deploy_url"),
173
+ "hat":"steady-gold" if st.publish_ready.get(sid) else "cyan-blink"})
174
+ return web.json_response({"sessions":len(st.sessions),"engine":"omni-vibe","hat":WIZARD_HAT_COLOR})
 
 
 
 
 
 
 
 
 
175
 
176
 
177
  async def handle_vibes(req):
178
  vibes = await ghost.list_vibes()
179
+ return web.json_response({"vibes":vibes,"count":len(vibes),"registry":CANONICAL_REPO})
 
 
 
 
180
 
181
 
182
  async def handle_tunnel(req):
 
183
  try:
184
  url = await ghost.tunnel.up(PORT, timeout=10.0)
185
+ return web.json_response({"success":bool(url),"url":url})
186
  except Exception as e:
187
+ return web.json_response({"success":False,"error":str(e)}, status=500)
188
 
189
 
190
  async def handle_schema(req):
 
191
  sid = req.query.get("session_id", "")
192
+ return web.json_response(st.sessions.get(sid,{}).get("schema",{}))
 
 
193
 
194
 
195
+ # ═════════════════ ROOT HANDLER ═════════════════
196
+ # Returns valid HTML at / β€” no redirects, no auth.
197
+ # aiohttp auto-registers HEAD β†’ 200 OK for health probes.
 
 
 
 
198
 
199
  async def handle_root_get(req):
 
200
  fp = STATIC / "index.html"
201
  if fp.exists():
202
  return web.Response(text=fp.read_text(), content_type="text/html")
203
  return web.Response(text=INDEX_FALLBACK, content_type="text/html")
204
 
205
+
206
  INDEX_FALLBACK = """<!DOCTYPE html>
207
  <html lang="en">
208
  <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
 
219
  <body><div class="glass">
220
  <h1 class="gradient-text">Omni-Vibe Studio</h1>
221
  <p>Specialized Swarm β€” Steady Gold</p>
222
+ <p style="font-size:.85rem;color:#606080;margin-top:2rem">0.0.0.0:7860 Β· Pose Architect Β· Pose Painter Β· Pose Auditor</p>
223
  </div></body></html>"""
224
 
225
 
 
 
226
  async def handle_static(req):
227
  path = req.match_info.get("path", "index.html")
228
  fp = STATIC / path
229
  if not fp.exists():
230
  return web.Response(text="Not found", status=404)
231
+ ct = {".html":"text/html",".css":"text/css",".js":"application/javascript"}
232
+ return web.Response(text=fp.read_text(), content_type=ct.get(fp.suffix,"text/plain"))
233
 
234
 
235
  def create_app():
236
  app = web.Application()
 
 
237
  app.router.add_get("/", handle_root_get)
 
 
238
  app.router.add_post("/api/stream", handle_stream)
239
  app.router.add_post("/api/publish", handle_publish)
240
  app.router.add_get("/api/status", handle_status)
 
244
  app.router.add_get("/api/tunnel", handle_tunnel)
245
  app.router.add_get("/api/schema", handle_schema)
246
  app.router.add_get("/.well-known/agent.json", handle_agent)
 
 
247
  app.router.add_get("/{path:.*}", handle_static)
 
248
  return app
249
 
250
 
 
255
  print(f" Pose Auditor : step-by-step verification")
256
  print(f" Athanor : LiteRT engine, sub-second latency")
257
  print(f" Registry : {CANONICAL_REPO}")
258
+ print(f" Binding : 0.0.0.0:{PORT}")
259
  print(f" Hat : {WIZARD_HAT_COLOR}")
260
 
 
261
  tunnel = ghost.tunnel
262
  loop = asyncio.new_event_loop()
263
  asyncio.set_event_loop(loop)
264
 
265
  async def tunnel_watchdog(interval: float = 30.0):
266
+ await asyncio.sleep(3)
 
267
  retries = 0
268
  while True:
269
  try:
270
  if tunnel.url is None:
271
+ print(f"πŸ”— Tunnel: connecting (retry #{retries})…")
272
  url = await tunnel.up(PORT, timeout=10.0)
273
  if url:
274
  print(f"πŸ”— Tunnel: LIVE β†’ {url}")
275
  retries = 0
276
+ else: retries += 1
 
277
  else:
278
+ import aiohttp
279
+ async with aiohttp.ClientSession() as s:
280
+ async with s.get(tunnel.url, timeout=aiohttp.ClientTimeout(total=5)) as r:
281
+ if r.status >= 500:
282
+ print(f"πŸ”— Tunnel: DEAD β€” restarting…")
283
+ tunnel.down(); retries += 1
284
  except Exception as e:
285
+ print(f"πŸ”— Tunnel: error β€” {e}"); retries += 1
 
286
  await asyncio.sleep(interval * (1 + min(retries, 5) * 0.5))
287
 
 
 
 
 
 
 
 
 
 
288
  loop.create_task(tunnel_watchdog())
289
  web.run_app(create_app(), host="0.0.0.0", port=PORT, handle_signals=True)
290