Reference hyperframes hub
What it is¶
A Flask app at https://hyperframes.justinsforge.com/ that:
- Shows a card grid of Hyperframes projects (auto-discovered by scanning forge/data/video-edit-eval/round1-hyperframes/*/hyperframes.json).
- Gates everything behind a PIN (currently 6225, hardcoded constant in app.py).
- Each card links to /projects/<name> — a CapCut-style viewer with: header (hamburger / project name / reload), iframe of that project's Studio, chat panel that sends prompts to claude -p and polls status.
- Hub home button rule: every page links to https://justinsforge.com/ (in the drawer for project pages, fixed pill on the hub page).
Where the code lives¶
| File | Role |
|---|---|
forge/data/video-edit-eval/_hub/app.py |
Flask app (~200 lines). Routes: /, /login, /logout, /projects/ |
forge/data/video-edit-eval/_hub/templates/base.html |
Layout shell. Versioned CSS link via static_v('hub.css') context processor. |
forge/data/video-edit-eval/_hub/templates/login.html |
PIN entry form. |
forge/data/video-edit-eval/_hub/templates/hub.html |
Card grid + sign-out link. |
forge/data/video-edit-eval/_hub/templates/project.html |
Header + drawer + iframe + chat. JS for drawer open/close, reprompt POST, status polling. |
forge/data/video-edit-eval/_hub/templates/not_found.html |
404. |
forge/data/video-edit-eval/_hub/static/hub.css |
All styles. Mobile breakpoint at 1024px (project page) and 800px (hub page). |
forge/data/video-edit-eval/_hub/state/sessions/<token> |
File-backed PIN session tokens (touch the file = active session). |
forge/data/video-edit-eval/_hub/state/jobs/<id>.json |
Per-reprompt job state (status, stdout/stderr tails). |
forge/scripts/forge_hyperframes_hub.py |
Project registry + filesystem scanner. PROJECT_REGISTRY dict maps folder name → port + subdomain. Imported by app.py and the new-project script. |
forge/scripts/forge_hyperframes_preview_runner.py |
systemd ExecStart wrapper for each project's preview server. |
forge/scripts/forge_hyperframes_new_project.py |
End-to-end project bootstrap (init + port pick + registry update + Cloudflare ingress + systemd enable + hub regen). |
forge/.claude/skills/hyperframenewproject/SKILL.md |
User-facing skill that wraps the bootstrap script. |
systemd units (durable across reboots)¶
| Unit | What it runs |
|---|---|
forge-hyperframes-hub.service |
python3 _hub/app.py on 127.0.0.1:4900. Cloudflare ingress at hyperframes.justinsforge.com. |
forge-hyperframes-preview@<name>.service |
Template unit. One instance per project, runs forge_hyperframes_preview_runner.py <name> which looks up port in PROJECT_REGISTRY and execs npx hyperframes preview --port N. |
sudo systemctl list-units 'forge-hyperframes-*' to list all instances.
Cache strategy¶
- Static files served with
SEND_FILE_MAX_AGE_DEFAULT = 0. - CSS/JS links in templates are versioned via
?v={{ static_v('hub.css') }}(mtime-stamp). Every edit auto-invalidates the browser cache. - Cloudflare edge may still cache for a few hours, but the version stamp sidesteps that since the URL changes on every edit.
PIN auth (file-backed)¶
- Login posts to
/loginwith fieldpin. Constant-time compared againstPIN = "6225"inapp.py. - Success: writes a random URL-safe token file at
state/sessions/<token>, setsforge_hf_pincookie (HttpOnly, Secure, SameSite=Lax, 30-day expiry). - Every request before the gate checks
cookie token → file exists. Logout deletes the file + cookie. - This is not production-grade auth. Fine for a private hub. For broader sharing, swap to Cloudflare Access.
Reprompt loop (real, not stubbed)¶
- Project page POSTs
/api/reprompt/<name>with{prompt: "..."}. - Backend writes a job file with
status: "queued", then spawns a thread that runs: in the project directory. The wrapper prompt constrains: edit onlyindex.html, runnpm run checkafter. - Status polls every 1.5s. Statuses:
queued→running→done(rc=0),failed(rc≠0, captures stderr tail),timeout(10 min cap),error. - On
done, the iframe is reloaded from the client side so the new render shows. - Cost: every reprompt burns Claude subscription quota. ~one small interactive turn per call. No external API spend.
How to add a new project¶
Use the skill: /hyperframenewproject <kebab-case-name> [--aspect 16:9|9:16].
The skill (script: forge_hyperframes_new_project.py) does:
1. npx hyperframes init <name> --tailwind in round1-hyperframes/.
2. Patches index.html to the requested aspect.
3. Picks the next free port starting at 3003.
4. Appends to PROJECT_REGISTRY in forge_hyperframes_hub.py (regex insert, idempotent).
5. cf-add <name>-hf justinsforge.com http://localhost:<port> --tunnel "VR Alliance".
6. sudo systemctl enable --now forge-hyperframes-preview@<name>.service.
7. Regenerates the hub static fallback (legacy, not required by Flask app).
How to remove a project¶
sudo systemctl disable --now forge-hyperframes-preview@<name>
/home/justinwieb/forge/scripts/forge_cloudflare_cf.py rm <name>-hf justinsforge.com --tunnel "VR Alliance"
rm -rf /home/justinwieb/forge/data/video-edit-eval/round1-hyperframes/<name>
# Edit forge_hyperframes_hub.py and remove the PROJECT_REGISTRY entry
sudo systemctl restart forge-hyperframes-hub # picks up the registry change
Doctrine that applies¶
feedback_home_button_on_subdomains.md— every authored x.justinsforge.com page must have a link home (drawer satisfies this on the project page).feedback_new_subdomain_links_home.md— when adding a new subdomain anywhere, also add a tile toforge/sites/justinsforge.com/landing/index.html. Per-project Hyperframes children are exempt (they live inside the hub, not on home).
Common ops¶
| Task | Command |
|---|---|
| Restart hub after editing app.py | sudo systemctl restart forge-hyperframes-hub |
| View hub logs | sudo journalctl -u forge-hyperframes-hub -f |
| View a project's preview logs | sudo journalctl -u forge-hyperframes-preview@<name> -f |
| Restart a project's preview | sudo systemctl restart forge-hyperframes-preview@<name> |
| Audit ingress vs registry | see feedback_new_subdomain_links_home.md for the diff one-liner |
| Local debug (Flask dev server) | cd _hub; python3 app.py (waits on :4900) |
[Claude Code]