Running and access
Register projects, reach the UI from another device on your network, and use a bearer token whenever it is reachable from anywhere but localhost.
alc ui # http://127.0.0.1:8642
alc ui --lan # reachable from other devices on your network
alc ui --port 9000
alc ui --no-ui # API and WebSocket only
alc ui --ui-dist ./ui/dist # serve a specific frontend build| Flag | Default |
|---|---|
--host H | 127.0.0.1 |
--lan | off — binds every interface and prints the address to type elsewhere |
--port P | 8642 |
--ui-dist PATH | falls back to ALC_UI_DIST, then the bundled build, then API-only |
--no-ui | off |
--token T | none (env: ALC_UI_TOKEN) |
--host and --lan are mutually exclusive: asking for both is a contradiction, and argparse rejects it rather than silently picking one.
The server needs the optional ui extra. Without it, alc ui prints an install hint and exits 1.
Reaching it from another device
By default the server answers only on loopback, which means only the machine running it can open the UI. --lan binds every interface and, more usefully, prints the address to type on anything else on the same network — a second laptop, a desktop, a tablet, a phone:
alc ui --lanServing alc ui (frontend: …)
Local: http://127.0.0.1:8642
Network: http://192.168.1.42:8642The Network line is the one to type on the other device. 0.0.0.0 is a bind address, not a destination — it is deliberately never printed as a URL.
When the machine has no route to a network, the line says so instead of offering an address that goes nowhere.
--lan puts the UI on your network, so read Authentication before using it on a network you do not control. The warning it prints is not decoration.
The project registry
The UI is multi-project. The registry is a small JSON file at ~/.alc/ui/projects.json, outside any project.
Adding a project means typing its absolute path. There is no directory picker — the server validates that the path exists and contains a .alc/manifest.yaml, and rejects it otherwise. Registering a path that is already registered is idempotent.
A project's id is derived from its directory name and a hash of its resolved path, so it is stable across restarts: the same directory always gets the same URL.
Removing a project deregisters it only. It never touches files on disk.
If a registered project's .alc/manifest.yaml later disappears, the project shows as unavailable rather than vanishing — with the reason distinguished between "not registered" and "gone from disk".
Writes to the registry are atomic. A registry file that exists but is corrupt is left untouched and surfaces as an error, deliberately, rather than being silently overwritten.
Authentication
By default there is none. alc ui binds loopback and answers unauthenticated requests, which is correct for a tool you run on your own machine.
The moment it is reachable from anywhere else, set a token:
alc ui --lan --token "$(openssl rand -hex 32)"Or via the environment, which --token overrides:
export ALC_UI_TOKEN=…
alc ui --lanWith a token set, every /api request needs an Authorization: Bearer <token> header, and the WebSocket handshake needs the token in its first frame.
Binding a non-loopback host — which --lan does by definition — without a token prints a warning to stderr. It is never refused — but read the warning: anyone who can reach that port can read every registered project and dispatch runs.
Handing the token to a browser
alc ui prints the URL to use once:
Hand the token to a browser once: http://HOST:PORT/?t=<token>Open that once. The page reads the t parameter before the first render, stores the token, and rewrites the address bar to drop the parameter — so the secret does not end up in browser history, a bookmark, or a URL you paste to someone.
The token is kept in localStorage. If localStorage is unavailable — private browsing, a blocked origin — it degrades to memory for that session only.
If the token is ever rejected, the client drops it and replaces the whole app with a prompt for a new one, rather than showing empty views. That distinction matters: a UI that renders an idle-looking project when it is really unauthenticated is lying to you.
There is no logout button, no session cookie, and no expiry. Rotate by restarting with a new token.
The WebSocket token goes in the first frame, never in the URL query string. A query-string token would land in every proxy and server log along the way.
Reaching it remotely
Two options, in order of preference.
Tunnel it. Leave the server on 127.0.0.1 and forward the port:
ssh -N -L 8642:127.0.0.1:8642 you@your-boxNothing is exposed, and no token is needed.
Bind it, with a token. If you must bind a routable interface, always pass --token, and put it behind TLS — the token is a bearer credential and travels in a header.
Live updates
One WebSocket at /ws carries everything: run events, queue changes, new reports, loop state, config changes, signal intake, working-tree status, and the output of any exec the UI spawned. The client subscribes per project and reconnects on its own with capped backoff.
Every /api response is served with Cache-Control: no-store, so nothing you read is a stale copy from a proxy.
Next
- The control room — what each view is for.
- CLI commands — everything the UI dispatches, and the parts it does not.