# kbagent web UI Two ways to run the UI: - **End-user / single-process** -- one Python command, no Node runtime needed: `kbagent serve --ui`. FastAPI mounts the built React SPA on `/`, injects the bearer token into `index.html`, and rewrites `/api/*` to bare API routes. Use this for "just give me the UI" deployments. - **Dev mode (3 terminals)** -- Vite dev server + Node BFF + kbagent serve. Used when iterating on the React app: hot reload, source maps, the works. See [Dev mode](#dev-mode-three-terminals) below. ## Single-process mode (recommended for users) ### Install from git (recommended) ```bash # Node 20+ on PATH so the build hook can compile the SPA into the wheel: uv tool install --with 'keboola-cli[server]' 'git+https://github.com/keboola/cli' # Run: kbagent serve --ui # Open the URL printed at startup -- the browser is auto-authenticated. ``` The wheel ships with the React SPA inside it (`keboola_agent_cli/_ui_dist/`). A hatchling build hook (`hatch_build.py`) runs `npm ci && npm run build` during wheel creation so `uv tool install git+...` produces a self-contained install. If Node is unavailable on the install host the CLI still works but `kbagent serve --ui` will exit with a "no UI bundled" error and instructions to rebuild. ### From a local checkout (editable install) ```bash uv pip install -e ".[server]" (cd web/frontend && npm install && npm run build) # produces web/frontend/dist uv run kbagent serve --ui --port 8001 --config-dir ~/.config/keboola-agent-cli ``` What `--ui` does on top of `kbagent serve`: 1. Mounts `web/frontend/dist` at `/` so the SPA loads from the same port as the API. 2. Server-renders `index.html` with `` so the SPA boots already authenticated. No paste step, no Node BFF. 3. Adds a path-rewrite middleware so the SPA's existing `/api/*` calls reach the bare endpoints (`/projects`, `/configs`, ...). 4. Accepts `?_kbagent_token=...` as a fallback because `EventSource` (the SSE primitive used on the Jobs page) cannot send custom headers. `--ui-dist PATH` overrides the dist location; `$KBAGENT_UI_DIST` works the same way. Both imply `--ui`. The bearer token is still printed at startup so curl / scripts / `kbagent http get` keep working alongside the browser session. ## Architecture ``` React SPA (web/frontend, Vite + TanStack Query + Tailwind + Monaco + Mermaid) | | Single-process mode: SPA + API on the same FastAPI port, | token injected into index.html. | | Dev mode: SPA via Vite dev server, /api/* proxied to ↓ v Node BFF (web/backend, Fastify + undici) (DEV / OPTIONAL) | REST + SSE injects bearer token, no business logic v kbagent serve (Python, FastAPI + uvicorn) | v Keboola APIs (Storage, Queue, Manage, AI, ...) ``` The BFF is now optional -- it exists to enable the Vite dev workflow (hot reload across all three layers) and to host the SPA from a different process when that's preferred. **Production deployments can run a single uvicorn process via `--ui`.** **Building your own client** instead of the bundled SPA? Run plain `kbagent serve` (no `--ui`). It exposes the same REST + SSE endpoints and an OpenAPI schema at `/openapi.json`; authenticate with the bearer token printed at startup or via `$KBAGENT_SERVE_TOKEN`. The `kbagent http get|post|patch|delete` CLI is a thin wrapper for shell use. ## Dev mode (three terminals) Use this when editing React / TypeScript and you want hot reload. Prerequisite: `uv pip install -e ".[server]"`, Node 20+, and `(cd web/backend && npm install)` / `(cd web/frontend && npm install)`. ### Terminal 1 -- the kbagent kernel ```bash uv run kbagent serve --port 8001 --config-dir /tmp/kbagent/.kbagent # Prints a bearer token on startup -- copy it. ``` ### Terminal 2 -- the Node BFF ```bash cd web/backend KBAGENT_SERVE_TOKEN= PORT=8000 npm run dev ``` ### Terminal 3 -- the React dev server ```bash cd web/frontend npm run dev # Open http://localhost:5173/ ``` The Vite dev server proxies `/api/*` and `/__bff/*` to the BFF; the BFF attaches the bearer token and forwards to `kbagent serve`. Hot reload works for all three layers. ## Production-only BFF mode (legacy) If you'd rather keep the BFF in production (e.g. you've got auth / logging middleware in Fastify you want to preserve), the original flow still works: ```bash make web-build # builds the React app into web/frontend/dist uv run kbagent serve --port 8001 --config-dir ~/.config/keboola-agent-cli & cd web/backend STATIC_DIR=../frontend/dist \ KBAGENT_SERVE_TOKEN= \ PORT=8000 npm start # Open http://localhost:8000/ ``` ## Architecture notes - **No business logic in the BFF**: it injects the bearer token and proxies (REST + SSE) verbatim. If you want a different UI language, swap the BFF -- the React app talks to it through `/api/*`, the BFF talks to kbagent serve through OpenAPI. - **Manage tokens are per-request**: writing operations that need `KBC_MANAGE_API_TOKEN` (org setup, member invites/removes) prompt for the token in a modal and pass it as `X-Manage-Token` for that one request. Never persisted, never logged. - **SSE streams**: jobs page subscribes to `/api/jobs/{project}/{id}/stream` for live status + log events. The BFF passes the chunks through; the React `EventSource` API receives them directly. - **SQL workspaces**: Monaco editor + `/api/workspaces/.../query` with CSV-rendering of statement results. - **Lineage**: cross-project bucket-sharing graph rendered as a Mermaid diagram + tabular edge list. ## Repo layout ``` web/ backend/ Node 20 + Fastify (TypeScript) src/ server.ts entrypoint proxy.ts REST + SSE proxy with bearer auth injection config.ts env-driven config package.json tsconfig.json frontend/ React 18 + Vite + Tailwind + TanStack Query src/ api/client.ts thin fetch wrapper + SSE helper state.tsx global UI state (selected project, branch, page) types.ts permissive mirror of kbagent service shapes layout/ Sidebar, TopBar, StatusBar, Shell components/ Empty, Table, JsonView, ManageTokenModal pages/ one file per feature area (20 pages) App.tsx page router (state-driven) main.tsx React entry index.css Tailwind base + NERD theme components index.html vite.config.ts tailwind.config.ts package.json tsconfig.json ```