Web UI (bnerd web)¶
bnerd web starts a local web server that serves a browser interface to the bnerd AI agent — the same agent you can drive from the TUI and the MCP server, now reachable from your browser.
It is designed for a single local user: the server binds to loopback only, generates a one-time access token, and opens your browser straight into a chat session.
This prints a URL like http://127.0.0.1:54321/?token=… and (unless --no-open) opens it in your default browser.
What you get¶
- Streaming chat with the agent over Server-Sent Events.
- Tool-call cards showing each tool the agent runs, its parameters, status, and any diff/output (click a card to expand details).
- Write confirmations: when the agent wants to perform a write or destructive action, a dialog asks you to Approve or Deny before it runs — mirroring the TUI's confirmation flow and honouring the active safety mode.
- Interactive dialogs for plan approval, phased-execution checkpoints, structured questions, and ticket / reply proposals.
There is one conversation per server run. The initial mode/agent is chosen at launch with --mode, and you can switch live from the mode selector in the chat header — switching carries the conversation with it: message history, the AI's scratchpad, and the session's saved-session identity all transfer to the newly selected agent, rather than starting a second, separate chat. Only the toolset and system prompt change. Beyond the three built-in modes (chat, code, pa — each using the same tools and system prompt as the corresponding TUI agent), --mode also accepts an agent role slug from your mission repo's orchestrator/roles/, ~/.bnerd/agents/, or <workdir>/.bnerd/agents/ (e.g. --mode go-cli-dev) — every loaded role is registered as a selectable profile automatically, and shows up in the mode selector alongside the built-ins. See Agent Profiles for the role schema and how its tools and permissions are resolved. An unrecognized --mode value is fatal: the server refuses to start and lists the modes it does have, rather than quietly falling back to chat and running with a wider tool surface than the role you asked for.
A live switch can be refused:
- A message is already streaming. The switch returns
409 Conflict("finish the current message before switching modes"); finish or wait for it, then retry. - The OpenAI-compatible backend is active. Carrying history across a switch requires exporting and re-importing the conversation, which only the Anthropic backend supports today. With
ai-provider: openaiconfigured, a switch fails with an error explaining that history carry needs the Anthropic backend — the old agent and conversation are left exactly as they were, never silently dropped or replaced with an empty one.
This mirrors the TUI's own agent switching (:agent, :code, :pa, …) — see AI Assistant → Switching agents for the equivalent TUI-side semantics.
Resource dashboard¶
Alongside chat, the web UI is growing into a browser peer of the TUI for managing cloud resources. The dashboard is reached from the resources → link in the chat header (or /ui/zones), with a left-hand nav per resource.
Each resource view supports browsing, live fuzzy filtering, a detail panel, and — where the resource allows it — create / edit / delete through inline forms with a confirmation step on destructive actions. A project selector in the nav re-scopes project-scoped views (it mirrors the TUI's :projects).
Rolling out by resource
Available today:
- DNS — zones (browse + drill into records) and full record CRUD.
- Compute — servers, volumes, networks, routers, and load balancers (browse, filter, detail; read-only, matching the TUI).
- Kubernetes — clusters (browse, filter, detail) plus create and edit (name, version, machine type, and node-pool sizing).
- Tickets — browse, filter, detail (with the body and comment thread), create, comment, and delete.
- DNS domains — browse, filter, detail (verification status/method).
- Apps — browse, filter, detail (project-scoped).
- Org & access — organizations, members, and invitations (browse, detail).
- Invoices — browse and detail.
- Billing — cost breakdown by service for the active scope.
All AI modes — chat, the code assistant, the personal assistant, and any mission-catalog agent roles — are available in the browser and switchable from the mode selector in the chat header (no restart needed). When Slack/email is configured, the PA's inbox/Slack tools work in chat (the messaging hub runs inside the web server). The dedicated inbox peek UI (live thread list/triage) is still terminal-only and tracked as a follow-up. Read/write views go through the shared pkg/resources service layer, so the dashboard and the TUI stay in lock-step.
Agent teams from chat¶
Like the TUI conversation, the chat in bnerd web can start a persistent agent team: ask it to work with a team on something, and it calls team_start (recipe slug or an ad-hoc roster) to launch one, with the chat session itself becoming the coordinator — see AI Assistant → Working with a team for the full behavior (one team per conversation, the driver toolset, the "Team status" block, inline milestones). team.max_members caps a web-launched team's roster the same way it caps bnerd team run and a conversation-launched team in the TUI.
A launched team's roster, tasks, and per-teammate transcripts are also observable at /team — the same read-only panel a bnerd team run team uses; a team launched from a TUI or web conversation shows up there the same way, since it's the same team engine underneath. Not linked from the resources nav today, but reachable directly at http://127.0.0.1:<port>/team while a team is active.
Approvals cannot be answered on the web
Unlike the TUI, a teammate's tool confirm, question, or task-completion approval does not pause on the web UI for you to answer, and this iteration offers no way to resolve one from anywhere else — there is no attach/resume path into a live web-launched team, and the operator MCP calls (bnerd_team_approve, bnerd_team_answer, bnerd_team_task_approve) run in a different process against a coordinator registry the web server never populates. The chat stream just records a system line:
The requesting teammate stays blocked, and its request is denied when the team is torn down. Run write-capable teams from the TUI, where the prompt surfaces inline in the conversation and you answer it with a keypress — see AI Assistant → Approvals stay yours. On the web, keep teams to read-only work.
Web-launched team state persists under ~/.bnerd/teams/web-<slug>/ (or ~/.bnerd/teams/web-adhoc/ for an explicit roster) with the same lease.json liveness marker as any other team, so the state stays on disk for inspection (bnerd team status) after the server stops. Each teammate is also recorded as a child session of the web conversation, the same as a TUI-launched team — see bnerd sessions → Agent-team teammates.
Exit grace¶
Stopping the server (Ctrl-C, or a SIGTERM) with a live chat-driven team doesn't just drop it. bnerd web's signal handler runs the same bounded exit grace the TUI runs on quit (see AI Assistant → Exit grace) before shutting the HTTP server down:
^C
shutting down…
team web-cloud-app: finishing up (up to 30s)…
team web-cloud-app: interrupted, 2 teammate(s) flushed to disk
Pending teammate approvals/questions are denied first, then any teammate genuinely mid-turn gets up to 30 seconds to finish, then every teammate's history is flushed to disk, the task board is persisted, the team is marked interrupted, and its lease is released — the same underlying sequence and 30-second bound the TUI's quit path uses, and that bnerd team run now also catches a terminal Ctrl-C/SIGTERM to run — see The bnerd team CLI → Exit behavior. A second Ctrl-C/signal during that window abandons the wait and shuts down immediately instead of making you wait out the full grace:
The web UI itself has no part in any of this — it's the terminal running bnerd web that prints these lines, since the browser connection is already gone by the time the server is shutting down.
The run does not resume
Restarting bnerd web does not bring a team back: the persisted state is a record, not a checkpoint, and there is no resume offer on the web surface. A web-launched team's meta.json never carries a conversation ID the way a TUI-launched one does, so even reopening the same conversation later in the TUI (bnerd x --ai-continue) won't offer to resume it — see AI Assistant → Resuming an interrupted team. The only way forward for an interrupted web-launched team is to start a new one.
Flags¶
| Flag | Default | Description |
|---|---|---|
--listen | 127.0.0.1:0 | Address to bind (host:port; port 0 picks a random free port). |
--mode | chat | AI mode: chat, code, pa, or an agent role slug from the mission catalog (see Agent Profiles). |
--no-open | false | Do not open the browser automatically (just print the URL). |
--auth-token | (generated) | Use a fixed UI access token instead of a random one. |
--continue | false | Resume the most recent web session for this directory and mode. |
AI provider, model, keys, organization/project scope, and integrations are read from the usual configuration (flags, BNERD_* env vars, or ~/.bnerd.yaml) — the same keys the TUI uses. See Configuration.
Security model¶
Local-only by design
The server is meant to run on your own machine. Its safeguards assume a trusted localhost:
- Loopback bind. Defaults to
127.0.0.1, so it is not reachable from other machines. - One-time token. A random token is generated per run and exchanged for an
HttpOnly,SameSite=Strictcookie on first visit. Every request requires it; a missing or wrong token returns401. - Origin / Host guard. Requests whose
OriginorHostis not the bound loopback address are rejected with403, blocking cross-site and DNS-rebinding attempts.
Binding to a non-loopback address with --listen exposes the UI to your network. Only do this on a trusted network, and keep the token secret — anyone with the URL and token can drive the agent with your credentials.
Session persistence¶
Like the TUI, bnerd web saves its session to the global session store (~/.bnerd/sessions/, see bnerd sessions) on server shutdown. Pass --continue to resume the newest saved web session for the current directory and mode:
The browser transcript starts empty
--continue restores the AI's conversation context — the agent remembers what you discussed and can pick up where you left off — but the chat panel in the browser opens blank. Replaying the previous transcript into the web UI is not implemented yet; use bnerd sessions show to read back an earlier conversation.
A session is only written to the store once you send a first message, so starting bnerd web and closing it again leaves nothing behind.
Sessions started in bnerd web are stored the same way as TUI sessions, so they also show up in bnerd sessions list and are restorable from the TUI with bnerd x --ai-continue.
Notes & limitations¶
- Single session. The server runs one chat session and processes one message at a time; a second send while one is in flight is rejected. Multiple browser tabs share the same session and stream.
- The resource dashboard is being filled in one resource at a time (DNS first); a documented local REST API is still deferred — see the roadmap.