A beautiful, real-time web interface for the PI coding agent.
Built with Bun + Hono + React 19 + Tailwind CSS v4 and PI's native RPC mode for first-class streaming support.
- Project Management — Add local directories as projects
- Session Browser — View all PI sessions for a project, see message count, model, timestamps
- Real-time Chat — Full streaming support via PI's RPC mode over WebSocket
- Tool Execution — Live tool call display (read, bash, edit, write, grep, find, ls)
- Thinking Display — Toggle thinking/reasoning visibility
- Markdown Rendering — Full GFM with code highlighting
- Steer & Abort — Steer conversations mid-stream or abort long operations
- Historical Sessions — Load and browse past sessions with full message history
- Usage Tracking — Aggregate token/cost roll-up across all sessions, per project and per model, in Settings → Usage
- Full PI Settings UI — Structured editor for every PI settings.json option (model, compaction, retry, resources, subagents, …) plus a raw JSON escape hatch
| Layer | Technology |
|---|---|
| Runtime | Bun |
| Server | Hono (HTTP + WebSocket) |
| Frontend | React 19 + Vite + Tailwind v4 |
| Agent | PI Coding Agent (RPC mode) |
| Storage | SQLite (project metadata) |
# Install dependencies
bun install
cd client && bun install && cd ..
# Start the server (port 3069)
bun run server/index.ts
# In another terminal, start the Vite dev server (port 3070)
cd client && bun run devOpen http://localhost:3070 in your browser.
# Build the client
cd client && bun run build && cd ..
# Start the server (serves built client at http://localhost:3069)
bun run server/index.tsAn alternative to the Docker/Podman image: a single manager script that builds PI Web for production and registers it as a native OS service that survives reboot and runs as you (so it sees your ~/.pi sessions and projects).
Linux (arch / debian / ubuntu / fedora) — systemd --user service:
bash scripts/install.sh # interactive menu
# or one-shot, non-interactive:
bash scripts/install.sh install --source git:main # from GitHub
bash scripts/install.sh install --source local:/path/to/pi-web # from a local checkoutmacOS — launchd LaunchAgent:
bash scripts/install.sh install # same script, detects macOSWindows — NSSM Windows Service (Scheduled-Task fallback if NSSM absent):
pwsh -ExecutionPolicy Bypass -File scripts\install.ps1 install
# or from the repo root:
bun run native:install:winYou can also launch either script with no arguments for an interactive menu.
The install source is remembered, so update just works — no flags needed:
bash scripts/install.sh update # re-fetch (git pull or re-copy) + rebuild + restart
./scripts/install.ps1 update # WindowsSwitch the update source with --source git:main or --source local:/path on the update command. The script re-copies your local checkout and rebuilds — save your edits first.
Subcommands: install · update · uninstall · start · stop · restart · status · logs · doctor
Flags: --source git[:ref] (default git:main) · --source local:/path/to/pi-web · --port N (default 33647, matching docker's HOST_PORT for a drop-in URL) · --host H · --domain https://pi.example.com (pre-set public origin; skips the deploy prompt) · --no-domain (force localhost; skips the prompt) · --dry-run · --purge (uninstall also drops ~/.pi-web) · --no-deps
During install the script asks whether PI Web will sit behind a public domain (reverse proxy like nginx/caddy). Answering yes writes HOST=0.0.0.0, PI_WEB_AUTH=on, BETTER_AUTH_URL, and PI_WEB_SECURE_COOKIES=on (plus PI_WEB_ORIGIN if the client is on a different domain). DNS, TLS cert, and the proxy itself are yours to set up — the script only configures what the app needs to trust the public origin. Skip the prompt with --domain or --no-domain.
The script auto-installs missing git / ripgrep / bun per distro (pacman / apt / dnf / brew / bun.sh / winget), clones or copies the source, runs bun install && bun run build, then registers the service.
| Path | Contents |
|---|---|
~/.local/share/pi-web |
app code + built client + run-service.sh wrapper |
~/.config/pi-web/env |
editable env (HOST/PORT/auth) — edit then restart |
~/.config/pi-web/source |
remembered install source (for update) |
~/.pi-web/.pi-web.db |
project metadata (kept on uninstall; --purge removes) |
~/.pi |
PI agent sessions (never touched) |
Exposing beyond localhost: edit ~/.config/pi-web/env (or %USERPROFILE%\.config\pi-web\env.cmd on Windows) — set HOST=0.0.0.0, PI_WEB_AUTH=on, BETTER_AUTH_URL, PI_WEB_SECURE_COOKIES=on, then bash scripts/install.sh restart. Reverse-proxy (nginx/caddy) for TLS.
- systemd
--user: the script runsloginctl enable-lingerso the service runs without an active login. Ifsystemctl --userfails with a D-Bus error, runsudo loginctl enable-linger $USERand setXDG_RUNTIME_DIR=/run/user/$(id -u). - Windows without NSSM: uses a Scheduled Task at logon (runs only while logged in) — install NSSM (
winget install NSSM.NSSM) for an always-on service.
PI Web ships optional auth (email/password + TOTP 2FA + passkey) via better-auth. It is controlled by a single env flag and is off by default on localhost.
| Env var | Purpose |
|---|---|
PI_WEB_AUTH |
on = force on; off = force off; unset = auto (on when HOST is not loopback — e.g. docker's 0.0.0.0; off on 127.0.0.1). |
PI_WEB_ADMIN_EMAIL |
Admin email for first-run seeding. Unset (default) = no admin seeded — sign up via the UI; the first account becomes the single admin. Set it to pre-seed that email instead. |
PI_WEB_ADMIN_PASSWORD |
Admin password (only used when PI_WEB_ADMIN_EMAIL is set). If unset, a random one is generated and printed once to the server log. |
BETTER_AUTH_URL |
The origin clients reach the server at (e.g. https://pi.example.com). Set for any non-localhost deployment; drives passkey rpID + the trusted-origin check. |
PI_WEB_SECURE_COOKIES |
on to mark session cookies Secure behind a TLS-terminating proxy. |
PI_WEB_ORIGIN |
An additional trusted origin (e.g. a separate client domain). |
Enable (docker/remote): set PI_WEB_AUTH=on, plus BETTER_AUTH_URL/PI_WEB_SECURE_COOKIES if behind TLS. Then either sign up via the UI (first account becomes the single admin) or set PI_WEB_ADMIN_EMAIL (and optionally PI_WEB_ADMIN_PASSWORD, else read the generated one from the first-run log) to pre-seed. Sign in, then enable 2FA and (over HTTPS) a passkey.
Disable (localhost dev): PI_WEB_AUTH=off — the auth middleware becomes a no-op and the server behaves exactly as without auth. Never run off on an exposed port: the server has shell, filesystem, and agent access.
Sign-up is capped to a single account (single-user mode); after the first user, /api/auth/sign-up/email is rejected.
These screenshots are auto-generated by bun run screenshots and kept in the repo so the README reflects the current UI.
| First run | Adding a project | Project selected |
|---|---|---|
| Project + session | Channels panel | Files panel | Git panel |
|---|---|---|---|
| Preview panel | Extensions panel | Terminal panel | All panels together |
|---|---|---|---|
The cross-platform source runs natively on Windows with Bun. A few host-side workflows remain Unix-oriented:
bun run build,bun run dev,bun run testinpackages/serverwork directly on Windows.- Git features require Git for Windows (or WSL) so the
gitCLI is onPATH. bun run docker:startruns a bash script; native Windows users should usebun run docker:start:wininstead (PowerShell 7+, Docker Desktop).test-all.shis a bash integration test script; run it under WSL/Git Bash on Windows.
The following tools must be available on the host system or inside the image:
pi— the PI coding agent. The server drives PI's SDK (@earendil-works/pi-coding-agent) in-process viaAgentSession+AgentSessionRuntime— no subprocess, no stdin/stdout JSONL. The Docker image bundles the dependency so no host installation is required.git— required for all Git panels and project status.ripgrep(rg) — required by the in-app Find in files search feature.bash(orcmd.exeon Windows) — required for the in-app terminal and for PI's bash tool.
The packaged Docker image installs all of these automatically.
Browser (React) ←── HTTP/WS ──→ Bun + Hono Server ── in-process SDK ──→ AgentSession
│ │ (AgentSessionRuntime)
│ /api/projects │ SQLite (.pi-web.db) │
│ /api/projects/:id/sessions │ PI session files (~/.pi/agent/) │
│ /api/sessions/detail │ │
│ /ws (WebSocket) │ SDKAgent (AgentSession + runtime)│
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
Health check |
| GET | /api/projects |
List all projects |
| POST | /api/projects |
Add a project directory |
| DELETE | /api/projects/:id |
Remove a project |
| GET | /api/projects/:id/sessions |
List sessions for a project |
| GET | /api/usage |
Aggregate token/cost usage across all sessions |
| GET | /api/sessions/detail?path=... |
Get session detail (entries) |
| WS | /ws/chat |
Real-time chat WebSocket |
The WebSocket uses the same event types as PI's RPC mode:
Client → Server: prompt, steer, follow_up, abort, new_session, get_state
Server → Client: agent_start, agent_end, message_start, message_update (streaming deltas), message_end, tool_start, tool_update, tool_end, turn_start, turn_end, compaction_start, compaction_end, state, error
pi-web/
├── server/
│ ├── index.ts # Hono server + WebSocket
│ ├── db.ts # SQLite project storage
│ ├── pi-sessions.ts # Session listing/parsing
│ └── pi-agent.ts # PI RPC process manager
├── client/
│ ├── src/
│ │ ├── App.tsx # Main app with routing state
│ │ ├── styles.css # Tailwind + custom styles
│ │ ├── hooks/
│ │ │ └── useWebSocket.ts # WebSocket hook
│ │ └── components/
│ │ ├── Sidebar.tsx # Project/session sidebar
│ │ ├── ChatView.tsx # Chat interface
│ │ ├── MessageBubble.tsx # Markdown + tool rendering
│ │ ├── ChatInput.tsx # Input with steer/abort
│ │ └── EmptyState.tsx # Welcome screen
│ └── public/
│ └── pi-logo.svg
├── src/shared/
│ └── types.ts # Shared TypeScript types
└── package.json
"Warm Developer" — deep espresso backgrounds, amber glow accents, newsprint typography, subtle film grain texture. Editorial yet functional. Ink-like depth with warm highlights.
- Fonts: Newsreader (body/serif) + Geist Mono (code/mono)
- Palette: Ink blacks (#0A0908 → #2A2520), warm amber (#D4A020), muted paper (#E2D9CD)
- Subtle CSS animations for message reveals and status indicators