Commit Graph

3 Commits

Author SHA1 Message Date
gsxdsm
6f461a4744 fix: take the dev tunnel's token from the dev server, not a guessed file
The banner re-derived the token from ~/.fusion/settings.json. That is simply
the wrong source: on a real run the file contained no daemonToken while the
dashboard's own banner, two lines above, printed a working one — so the
tunnel claimed no token existed next to a token that plainly did.

The dashboard already holds the resolved token at the point where it reports
its bound port, so it now reports both over the same IPC message and the
wrapper prefers that over anything it could derive. The env/settings lookup
survives only for targets that report nothing, such as an explicit
--tunnel=PORT aimed at a server the dev child knows nothing about. The token
crosses the existing parent/child channel only; it is never logged or sent
onward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 20:16:44 -07:00
gsxdsm
ee57f8a3b9 fix: print the dashboard token with the dev tunnel URL
`pnpm dev --tunnel` published a bare URL under a "public, unauthenticated"
header. That label was wrong for the flag's own default target: --tunnel with
no port aims at the dashboard, which is bearer-token gated, so the recipient
of a shared link got a 401 with no token to supply.

resolveDevTunnelAuth() now classifies the target and the banner says what is
actually true of it:

  token         dashboard with auth on — prints the token and a ?token= link,
                resolved from FUSION_DASHBOARD_TOKEN, FUSION_DAEMON_TOKEN,
                then ~/.fusion/settings.json
  token-pending first run, token not minted yet — defers to the dashboard's
                own startup banner
  no-auth       --no-auth is on; the dashboard really is open
  foreign       a non-dashboard port; Fusion has no auth to lend it, the only
                genuinely ungated case

Auth resolves at banner time rather than flag-parse time so a token the dev
child mints on a first authenticated run is already readable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 18:21:50 -07:00
gsxdsm
7423555c46 feat(dev): pnpm dev --tunnel publishes the dev server over a quick tunnel
Operator case: someone works inside a remote Fusion (a container, a shared box),
starts a dev server there, and needs to view it from their own browser. The dev
server binds inside that machine, so without a tunnel the only options are port
publishing or a VPN — both needing cooperation from whoever owns the host.

  pnpm dev --tunnel            # tunnels the dashboard port (PORT, default 4040)
  pnpm dev --tunnel=5173       # tunnels a Vite dev server instead
  pnpm dev --tunnel dashboard  # tunnel the default port AND run the dashboard
  FUSION_DEV_TUNNEL=1 pnpm dev

Cloudflare QUICK tunnels are usable here precisely because a dev server is HTTP:
no account, no domain, no card. The TCP endpoints that SSH would have needed
require a card (ngrok) or a domain plus Zero Trust (Cloudflare) — that asymmetry
is why this exists for HTTP only, and it is recorded in the module header so the
next person does not retry the SSH variant.

Design decisions:
- Tunnel failure is NON-FATAL. A missing cloudflared or a tunnel that never
  publishes a URL logs and is skipped; losing a preview URL must never cost the
  operator their dev loop.
- Watch-mode restarts reuse the existing tunnel. A fresh quick tunnel hands out a
  different hostname each time, which would invalidate an already-shared link.
- `--tunnel` consumes a following token only when it is numeric, so
  `--tunnel dashboard` forwards `dashboard` to the dev command rather than
  tunnelling port NaN. That is the bug this flag shape invites, so it is tested.

Verified end to end in a container: a dev server bound to 127.0.0.1 inside it was
fetched from the public internet through the tunnel (200, correct body). Also
confirmed that tunnelling the DASHBOARD port does not weaken auth — unauthenticated
requests through the tunnel return 401 for /api/tasks, /api/settings and
/api/artifacts, with only /api/health open by design.

Adding two fields to parseDevWrapperArgs' return broke two existing strict toEqual
assertions; those were updated rather than loosened to toMatchObject. 27 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 17:18:56 -07:00