# Pi Web UI A full-stack web interface for the [Pi coding agent](https://github.com/badlogic/pi-mono), providing browser access to a system-level Pi agent with bash, file read/write/edit, and extension tools. Read the writeup on Sleeping Robots: [Pi Web UI: A Browser Interface for the Pi Coding Agent](https://sleepingrobots.com/dreams/pi-web-ui/). ## Architecture ``` Browser (Lit + pi-web-ui components) │ WebSocket (/api/ws) ▼ Node.js server (Express + ws) │ ▼ Session broker (runtime ownership and WebSocket attachments) │ Pi SDK (createAgentSession) ▼ System tools (bash, files, skills, extensions) ``` - **Server** (`server/index.ts`): Owns broker runtimes that contain Pi SDK `AgentSession` instances with full system access. WebSocket clients attach to and detach from runtimes. A disconnect does not stop active work. Loads auth and model config from `~/.pi/agent` through `ModelRuntime`. Model discovery can use LiteLLM. - **Client** (`client/main.ts`): Lit-based UI using `@earendil-works/pi-web-ui` components (`MessageList`, `MessageEditor`, `StreamingMessageContainer`). Communicates only through WebSocket. - **Protocol** (`shared/protocol.ts`): Typed JSON message definitions for the WebSocket wire format. ## Setup ```bash npm install npm run build ``` ### Environment variables | Variable | Default | Description | |---|---|---| | `PORT` | `3001` | Server listen port | | `HOST` | `127.0.0.1` | Server listen address. Use `0.0.0.0` only on a trusted network and with `PI_WEBUI_TOKEN` set. | | `PI_WEBUI_TOKEN` | *(unset)* | **Required by default** shared secret for `/api/ws` and the skills editor API. If set, open `/?token=` once on first load; the client stores it in `localStorage` and removes the query parameter from the URL. | | `PI_WEBUI_ALLOW_ANONYMOUS` | `false` | Local development escape hatch only: tokenless mode requires this flag and a loopback `HOST`. Non-loopback/public binding always requires `PI_WEBUI_TOKEN`. | | `PI_WEBUI_ALLOWED_ORIGINS` | same host as request | Additional comma-separated WebSocket `Origin` allowlist values. | | `PI_WEBUI_TITLE` | `Pi Web UI` | Browser title and visible application name. | | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | Pi agent config directory for auth, models, settings, sessions, and shared skills. | | `PI_WEBUI_SKILL_MAX_FILES` | `220` | Max SKILL parse candidates per skill directory scan. | | `PI_WEBUI_SKILL_PARSE_BYTE_BUDGET` | `3000000` | Max total bytes read while scanning skills for a list call. | | `PI_WEBUI_SKILL_FILE_MAX_BYTES` | `240000` | Max bytes per individual skill file. | | `PI_WEBUI_SKILL_MAX_DEPTH` | `8` | Max recursive directory depth while scanning skill directories. | | `PI_WEBUI_SKILL_DIRECTORY_ENTRY_LIMIT` | `1000` | Max entries inspected in any one visited skill directory. | | `PI_WEBUI_SESSION_DIRECTORY_SCAN_LIMIT` | `800` | Max directory entries considered for a session listing scan before sorting and pagination. | | `PI_WEBUI_SESSION_MAX_FILES` | `240` | Max session files parsed per listing request. | | `PI_WEBUI_SESSION_PARSE_BYTE_BUDGET` | `6000000` | Max total bytes read for session list parsing per request. | | `PI_WEBUI_SESSION_FILE_MAX_BYTES` | `220000` | Max leading bytes read from each session for bounded listing/search; large sessions remain listable but searches do not inspect content beyond this prefix. | | `PI_WEBUI_USER_SKILLS_DIR` | `~/.pi/skills` | Private skills edited from `/skills`. With the default working directory this is loaded by Pi as a project-local skills directory. | | `PI_WEBUI_SHARED_SKILLS_DIR` | `$PI_CODING_AGENT_DIR/skills` | Shared read-only skills listed in `/skills` and available to all agents using this agent dir. | | `PI_WEBUI_RESTRICT_MODELS_TO_SCOPED` | *(unset)* | If true/yes/on/1, only models from `PI_WEBUI_MODEL`, `PI_MODEL`, `settings.defaultProvider/defaultModel`, or `settings.enabledModels` are listed/selectable. | | `PI_WEBUI_FORCE_MODEL_ALIAS_LABELS` | *(unset)* | If true/yes/on/1, show configured model aliases (`name`) instead of raw provider/model ids in the UI and response metadata. | | `SESSION_LIST_LIMIT` | `30` | Default sessions fetched per sidebar page. | | `SESSION_LIST_MAX_LIMIT` | `100` | Maximum sessions accepted per sidebar page request. | | `MESSAGE_PAGE_LIMIT` | `60` | Default newest messages sent for an opened conversation. | | `MESSAGE_PAGE_MAX_LIMIT` | `200` | Maximum messages accepted per conversation page request. | | `SESSION_LIST_CACHE_MS` | `10000` | Sessions sidebar cache TTL. | | `LITELLM_URL` | *(empty)* | Optional LiteLLM API base URL for extra model listing. | | `LITELLM_KEY` | *(from ~/.pi/agent)* | LiteLLM API key (auto-detected from Pi config if unset) | ### Development ```bash npm run dev ``` Starts the server (`tsx watch`) on port 3001 and Vite dev server on port 5173 with WebSocket proxy. ### Production ```bash npm run build HOST=127.0.0.1 PORT=8085 PI_WEBUI_TOKEN=change-me npm start ``` ## Features - Full Pi agent with system access (bash, read, edit, write, extensions) - Broker-owned sessions with persistence, history, reconnect support, and multi-tab observation - Session search, rename, deletion, stable session links, and paged loading - Model switching with scoped/default models first and an optional full model list - Optional scoped-only model selection and alias-only model labels - Configurable thinking level with saved session settings restored on resume - Startup resource summary for the model, session, tools, and advertised skills - Activity and compaction status during long turns - Prompt history through the Up and Down keys - Structured tool-result summaries and one-toggle Input and Result details - Authenticated previews and downloads for files in the agent home directory - Built-in skills manager at `/skills` for private and shared skills - Expanded-by-default responsive session sidebar and mobile top-bar controls - Dark/light theme toggle with green accent ## Project structure ``` ├── client/ │ ├── index.html Entry point │ ├── main.ts Client application (state, WebSocket, rendering) │ └── app.css Theme overrides and sidebar styles ├── server/ │ ├── index.ts Express + WebSocket server and Pi SDK adapter │ └── session-broker.ts Runtime ownership, attachments, commands, and cleanup ├── shared/ │ └── protocol.ts WebSocket message type definitions ├── vite.config.ts Vite config (build, dev proxy) ├── tsconfig.json TypeScript config └── package.json Dependencies and scripts ```