What this does: exposes your local control-panel-server (running at
http://localhost:8080on your Mac) to the public internet via a Cloudflare Tunnel, so the PWA at https://kiwimaddog2020.github.io/ensemble-dashboard/ can reach it from any device, anywhere.Cost: $0 if you use a Quick Tunnel (ephemeral URL). $0 + ~$10/yr for a domain if you upgrade to a Named Tunnel (permanent URL + Cloudflare Access auth gate).
TL;DR
# Install (once)
brew install cloudflared
# Path A — Quick Tunnel (no domain, ephemeral URL)
cloudflared tunnel --url http://localhost:8080
# → grab the https://xxx-yyy-zzz.trycloudflare.com URL it prints
# → paste into /pair.html along with your DASHBOARD_TOKEN
# → URL changes every time cloudflared restarts (Mac reboot, etc.)
# Path B — Named Tunnel (permanent URL, custom domain, optional auth gate)
# Requires: a Cloudflare account + a registered domain on Cloudflare DNS
# See "Path B" section below.
Prerequisites
- macOS with Homebrew installed
- Free Cloudflare account: https://dash.cloudflare.com/sign-up
- The Ensemble control-panel-server running at
localhost:8080(bash bin/control-panel-server.shor via launchd) - Your
DASHBOARD_TOKENfrom~/.claude/orchestrator/.env.local
Install cloudflared
brew install cloudflared
cloudflared --version # should print 2026.x or newer
Path A — Quick Tunnel (free, no domain, ephemeral)
Best for: testing the flow end-to-end, or if you don’t have a domain yet.
Run
cloudflared tunnel --url http://localhost:8080
cloudflared prints a URL like https://random-words-1234.trycloudflare.com
and registers a tunnel connection (you’ll see “Registered tunnel connection”
in the log when it’s live).
Pair the PWA
- Open
https://kiwimaddog2020.github.io/ensemble-dashboard/pair.htmlfrom any device (your phone, another laptop, anywhere). - Server URL field: paste the trycloudflare.com URL from cloudflared.
- Token field: paste your
DASHBOARD_TOKEN. - Submit. The PWA stores both in localStorage on this device.
Now /terminal.html, /character-creator.html, etc. all reach your Mac via
the tunnel.
Caveats
- URL rotates on cloudflared restart. Mac reboot, killing cloudflared, network change → new URL. You’ll need to re-pair.
- No Cloudflare Access auth gate. Your
DASHBOARD_TOKENis the only thing protecting the server from anyone who guesses the URL — fine for short testing windows, not production. - Quick Tunnels can drop. Cloudflare reserves the right to terminate trycloudflare.com tunnels. Production needs Path B.
Run as a background service (still ephemeral URL)
nohup cloudflared tunnel --url http://localhost:8080 --no-autoupdate \
> /tmp/cloudflared-quick.log 2>&1 &
Then tail -f /tmp/cloudflared-quick.log to watch for the URL when you
need to re-pair.
Path B — Named Tunnel (permanent URL + custom domain)
Best for: ongoing personal use. Requires a domain on Cloudflare.
1. Register / move a domain to Cloudflare
If you already have a domain elsewhere, transfer DNS to Cloudflare (free, ~10 min, no migration of email or anything else needed).
If you don’t have one, register through Cloudflare directly (~$8-10/yr for .com).
You don’t need to actually USE the apex — you’ll just point a subdomain like
ensemble.your-domain.com at the tunnel.
2. Authorize cloudflared once
cloudflared tunnel login
Opens a browser tab. Pick the zone for your domain, click Authorize.
Saves a cert to ~/.cloudflared/cert.pem.
3. Create the tunnel
cloudflared tunnel create ensemble-mac
Prints a tunnel UUID. Saves credentials to
~/.cloudflared/<UUID>.json. The tunnel is now registered with Cloudflare
but not yet wired to a hostname.
4. Point a hostname at the tunnel
cloudflared tunnel route dns ensemble-mac ensemble.your-domain.com
Replace ensemble.your-domain.com with the subdomain you actually want.
5. Write the config file
Create ~/.cloudflared/config.yml:
tunnel: ensemble-mac
credentials-file: /Users/kevin/.cloudflared/<UUID>.json
ingress:
- hostname: ensemble.your-domain.com
service: http://localhost:8080
- service: http_status:404
Replace <UUID> with the actual UUID from step 3 (or use ~/.cloudflared/
relative path if cloudflared resolves it — verify per its current docs).
6. Run the tunnel
cloudflared tunnel run ensemble-mac
The hostname is now permanent. Next time you reboot, just run this command again — the URL stays the same.
7. Re-pair the PWA (one final time)
/pair.html → paste https://ensemble.your-domain.com + your DASHBOARD_TOKEN.
After this, the PWA never needs re-pairing on this device until you change the
URL or the token.
8. (Optional) Add Cloudflare Access auth gate
For an extra layer beyond the DASHBOARD_TOKEN:
- Cloudflare Zero Trust dashboard → Access → Applications
- Add an application → Self-hosted
- Application domain:
ensemble.your-domain.com - Policy: “Allow” + Include “Emails ending in @your-email-domain.com” (or just your literal email)
- Save.
Now any HTTP request to ensemble.your-domain.com first goes through a
Cloudflare-hosted login (one-time-PIN to email or Google SSO). Browsers
get redirected through it transparently; your DASHBOARD_TOKEN still
guards the server itself.
9. (Optional) Run cloudflared as a launchd service
So the tunnel survives reboots automatically:
sudo cloudflared service install
This sets up cloudflared to run as a system service. You can stop with:
sudo launchctl stop com.cloudflare.cloudflared
Troubleshooting
“Could not reach the server” in the PWA
Most common cause: stale localStorage.ENSEMBLE_SERVER_URL from a previous
pair attempt (especially if you used a Quick Tunnel that’s since rotated).
Safari fix:
- Develop menu → Show Web Inspector
- Storage tab → Local Storage →
kiwimaddog2020.github.io - Delete
ENSEMBLE_SERVER_URLandDASHBOARD_TOKEN - Hard-refresh: Cmd+Option+E (empty cache) then Cmd+R
- Re-pair via
/pair.htmlwith the current URL + token
Chrome fix: same idea, Application tab → Local Storage.
cloudflared tunnel --url ... exits immediately
Usually means port 8080 isn’t actually serving anything. Verify with:
curl -s http://localhost:8080/state
If that fails, your control-panel-server isn’t running. Start it with
bash bin/control-panel-server.sh.
Quick Tunnel URL responds 502
The tunnel is registered with Cloudflare but cloudflared can’t reach
localhost:8080. Check that the Mac firewall isn’t blocking loopback,
and that the server is actually listening on 0.0.0.0:8080 (not just
127.0.0.1 — control-panel-server does the right thing by default).
CORS error in DevTools console
The server already sends Access-Control-Allow-Origin: * on every
endpoint, so this is rare. If you see it, hard-refresh after re-pairing.
If it persists, check whether a browser extension is stripping headers.
Cloudflared SIGTERM / connection drops
Quick Tunnels die periodically (it’s free, no SLA). Check /tmp/cloudflared-quick.log
for “Retrying connection” loops; just kill + restart cloudflared. For
permanent reliability, switch to Path B.
Verify it’s working
From a different network than your Mac (e.g. your phone on cellular):
1. Open https://kiwimaddog2020.github.io/ensemble-dashboard/terminal.html
2. The status pill in the top-right should be "live" (green) within a few seconds
3. The chats list on the left should populate with your projects
4. Click a chat — the scrollback should load + new messages should stream in
If any step fails, open DevTools Console — the runtime logs diagnostic
[ensemble:terminal-controller] and [ensemble:character-creator] warnings
that tell you exactly which thing broke.
Where the secrets live
DASHBOARD_TOKEN:~/.claude/orchestrator/.env.local- Cloudflared cert:
~/.cloudflared/cert.pem(only aftertunnel login) - Tunnel credentials:
~/.cloudflared/<tunnel-UUID>.json - Per-device PWA state: browser localStorage at the GH Pages origin
(
ENSEMBLE_SERVER_URL+DASHBOARD_TOKEN)
Don’t commit any of these to git. The .env.local is already in .gitignore;
the ~/.cloudflared/ directory lives outside the repo.