public-mode¶
Expose the dashboard + API on a public IP behind one password and TLS — one command, no domain, no extra infrastructure.
raw source¶
The bar this node is held to: a developer with only a public IP — no domain, no reverse proxy, no extra tooling — runs one command and trusted people use the SpexCode dashboard over the internet behind a password — or, when they choose, with no gate at all. If that is not true, the design is wrong. SpexCode is a technical fast-lane between people who trust each other, not a public service; it must never require the apparatus of one (DNS, a CA-issued cert, a separate proxy) to stand up. The gate is opt-in: a password makes a real login appear; without one the dashboard is served open. Because access to the dashboard is effectively remote code execution through the agents, an open public deployment is loud-warned — the operator chooses, but the choice is never silent in either direction.
expanded spec¶
spex serve --public raises a gateway on 0.0.0.0:PORT that is the only thing facing the internet. It
terminates TLS, serves the built dashboard, reverse-proxies /api/* and the terminal WebSocket to the
loopback supervisor, and — when --password <pw> is given — gates every request behind a login. The supervisor and its
child stay bound to 127.0.0.1; loopback is the trust boundary, the gateway is the internet face.
Locally launched agents reach the loopback supervisor directly, so they never carry the password — only
outside traffic meets the gate. Without --public nothing changes: dev stays plain loopback, no TLS, no
gate. This is a pure additive switch over spec-cli's supervisor; the dashboard needs no change (it
already calls /api same-origin and opens its socket as wss:// under HTTPS).
When a password is set, the gate is a designed login, not the browser's Basic dialog. An unauthenticated
visitor gets a styled SpexCode login page; the posted password is compared in constant time and, on success,
mints a signed httpOnly cookie (derived from the password via HMAC, so it survives a restart and stores no
server-side session) named per public port (spex_auth_<port>) so two same-host gateways — cookies are
host-scoped — don't evict each other's login. The cookie authorises every later request including the WebSocket upgrade — the browser
sends it on the same-origin handshake, so the terminal socket is gated by the same secret with no query-token
hack. With no password the whole login layer is absent — no /login, no cookie check — and every request
is served straight through; the operator has chosen open access (and was warned).
The certificate is a resolved value, never hardcoded. Precedence: --tls-cert/--tls-key flags > the
SPEXCODE_TLS_CERT/SPEXCODE_TLS_KEY env > spexcode.json serve.public.tls > a self-signed default,
generated once and cached so visitors accept it only once. Point the flags at a real cert (e.g. Let's
Encrypt fullchain.pem/privkey.pem) and the same gateway is warning-free HTTPS the moment a domain
exists. --http drops TLS entirely — loud-warned, because the password then crosses the network in clear
and secure-context features (clipboard) break — for someone who knowingly wants zero friction. The one
unavoidable cost: web PKI will not issue a browser-trusted cert for a bare IP, so the self-signed default
costs a single "proceed" click per visitor — that is the price of needing no domain, and it is the default,
not a requirement.
Secrets stay out of the repo, and failures are loud. The password is taken only from the flag or env,
never the committable spexcode.json; config holds cert file paths, the key file lives outside git.
--public with no password serves open with a loud warning (never a silent exposure); a cert file that does
not exist is a named error pointing at the repair, never a silent fallback to insecure serving.
The same gateway also powers local serve. packaging's spex dashboard is this gateway on loopback,
ungated, no TLS; the internet face here is the other configuration of one engine. And the dist it serves is
a resolved location, never hardcoded: an installed spexcode serves its bundled dashboard-dist, the
dogfood monorepo falls back to the sibling spec-dashboard/dist.
A busy port fails loudly, the same way the supervisor's does. Binding a public port, the gateway obeys
spec-cli's port-ownership contract: a port already in use (or permission-denied) is a non-zero exit
naming the port and the repair, never a silent or half-up serve. Both surfaces route listen through one
shared bind helper, so spex serve and spex dashboard answer the identical busy-port condition identically.