A Manus-style, zero-dependency local web UI for the pi coding agent.
Dark three-column layout: sessions sidebar, streaming chat, and an "Agent Computer" panel with Steps, Terminal, and Files tabs that follow the agent as it works.
pi already has a great terminal UI. pi-panel is for when you want a browser tab instead — multiple sessions side by side, streaming tool output, and a click-through view of what the agent is doing (steps, shell output, edited files), without leaving localhost.
That's it. No npm install, no package.json, no third-party dependencies.
The entire backend is one file (server.mjs) built on Node's standard library
(http, child_process, crypto, fs, path, url), and the frontend
(web/) is plain HTML/CSS/JS with no framework and no CDN scripts. This is a
deliberate design choice, not a limitation — see Security.
git clone https://github.com/<you>/pi-panel.git
cd pi-panel
node server.mjsThe server prints something like:
pi-panel listening on http://127.0.0.1:8788
Bearer token stored at /Users/you/.pi-panel-token
Use: Authorization: Bearer <token>
Open this URL to sign in automatically:
http://127.0.0.1:8788/#token=<token>
Open the last URL (the one with #token=...) in your browser — the page reads the
token from the URL fragment, stores it in localStorage, and strips it from the
address bar. You only need to do this once per browser; after that, reloading
http://127.0.0.1:8788 just works. The same token also lives at
~/.pi-panel-token if you ever need to paste it back in manually (see
Troubleshooting).
pi-panel gives a browser tab the ability to spawn pi subprocesses that can read,
write, and execute code in your filesystem. Treat it accordingly:
- Bound to
127.0.0.1only. The server never listens on0.0.0.0or any externally reachable interface. It is not reachable from other devices on your network by default. - Bearer token on every API call. All
/api/*endpoints (including the SSE event stream) requireAuthorization: Bearer <token>, or?token=for the SSE connection. Static files are served without auth, but they contain no data. - Zero third-party dependencies, on purpose. There is no
npm installstep, so there is no dependency tree that can auto-update, get compromised upstream, or pull in a malicious transitive package. Every line of code that runs is in this repository. This matters more than usual for a tool that shells out to an AI agent with filesystem and command execution access — we'd rather not runnpx some-web-ui@latestand trust that whatever version resolves today is safe.
Do not port-forward, reverse-proxy, or otherwise expose pi-panel to the internet. There is no TLS, no per-user auth, and no rate limiting — it is built exclusively for local, single-user use on your own machine.
- New Task in the sidebar creates a new session. Each session spawns its own
pi --mode rpcsubprocess, rooted at a working directory you choose (default:~/pi-workspace). - The sidebar shows all sessions with a status dot that pulses while a session is streaming a response.
- Above the composer, pick the model and thinking level for the active session.
- Cmd+K / Ctrl+K opens a command palette to jump between sessions.
- The settings modal (gear icon) holds your bearer token and the default working directory used for new sessions.
- The right-hand panel mirrors what the agent is doing in real time:
- Steps — a timeline of tool calls (spinner → check/x).
- Terminal — live output from shell/bash tool calls.
- Files — files the agent has edited or written, with diffs when available.
┌─────────┐ HTTP + SSE ┌────────────┐ stdin/stdout JSONL ┌───────────────────┐
│ browser │ ─────────────▶ │ server.mjs │ ─────────────────────▶│ pi --mode rpc │
│ (SPA) │ ◀───────────── │ │ ◀──────────────────── │ (one child/session)│
└─────────┘ └────────────┘ └───────────────────┘
server.mjsis a plain Nodehttpserver. Every REST call maps to an RPC request written to a session'spi --mode rpcchild (matched by id, 30s timeout).- Each session keeps a ring buffer of the last 5000 RPC events. When a browser
tab (re)connects to
GET /api/sessions/:id/events(Server-Sent Events), the buffer is replayed first so the UI can catch up, then live events stream in. - pi's RPC stdout is framed by splitting on
\nonly (not Node'sreadline, which also splits on line-separator Unicode characters and would corrupt the protocol). - Sessions and their child processes live only in server memory; all
pichildren are killed onSIGINT/SIGTERM.
See SPEC.md for the full API surface and event wiring.
401 Unauthorized— your stored token doesn't match the server's. Open Settings and paste the token from~/.pi-panel-token, or reopen the printed#token=URL from server startup.- Port already in use — run with a different port:
node server.mjs --port 8899(or setPORT=8899). - "pi not found" / spawn errors — pi-panel shells out to the
pibinary on yourPATH. Make sure pi is installed andpi --versionworks in your terminal before starting pi-panel.
MIT — see LICENSE.
