---
slug: "khimaros-pi-webui"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/khimaros/pi-webui@master/README.md"
repo: "https://github.com/khimaros/pi-webui"
source_file: "README.md"
branch: "master"
---
# pi-webui

a simple, standalone webui for [pi.dev](https://pi.dev)

![screencast](https://github.com/khimaros/pi-webui/raw/HEAD/docs/screencast.gif)

## getting started

prerequisites:

- node.js 20+
- a working pi installation

install as a pi extension:

```bash
pi install npm:@khimaros/pi-webui
```

control from the pi tui:

```bash
> /webui start    # start the server
> /webui status   # view server status
> /webui open     # open webui in browser
> /webui stop     # stop the server
```

or auto-start when pi launches (server is terminated when pi exits):

```bash
pi --webui                              # start with defaults
pi --webui-listen 0.0.0.0:3000          # start with a custom bind address
```

run without installing:

```bash
npx @khimaros/pi-webui
```

or install globally:

```bash
npm install -g @khimaros/pi-webui
pi-webui
```

then open <http://127.0.0.1:4096>.

### from a source checkout

```bash
make            # install deps + compile the server to dist/ (tsc)
make start      # run the server
make test       # build + run tests
```

`make` compiles the server to `dist/` with `tsc`; the standalone `pi-webui` bin is
the emitted `dist/server/index.js`, run with plain node (node >= 20). a build is
required because node refuses to type-strip `.ts` files under `node_modules/`, so an
installed bin must ship as javascript. the pi extension is the exception: pi loads
it through its bundled jiti loader, which transpiles `.ts` on the fly with no such
restriction, so the extension ships and loads as `.ts` source
(`src/extension/index.ts`) and is never compiled.

## configuration

command-line flags:

| flag | purpose |
| --- | --- |
| `--listen <host:port>` | http bind address; takes precedence over `PI_WEBUI_LISTEN`. use `:port` for default host, or `[::1]:port` for ipv6. |

environment variables:

| variable | default | purpose |
| --- | --- | --- |
| `PI_WEBUI_LISTEN` | `127.0.0.1:4096` | http bind address (`host:port`, `:port`, or `port`) |
| `PI_PROJECT_CWD` | `process.cwd()` | project directory used for sessions |
| `PI_AGENT_DIR` | pi default (`~/.pi/agent`) | pi agent config directory |
| `PI_SESSION_DIR` | pi default | session storage directory |
| `PI_WEBUI_CWD_ALLOW_ANY` | `0` | allow `/cwd` to switch to paths outside `$HOME` |

examples:

```bash
pi-webui --listen 0.0.0.0:3000
PI_WEBUI_LISTEN=0.0.0.0:3000 PI_PROJECT_CWD=/path/to/project npm start
```

## attachments

paste images into the composer (Ctrl/Cmd+V) or drag and drop them onto the
window. thumbnails appear above the input and ride along with the next
prompt. up to 8 images per turn, 10 MB each. PNG, JPEG, GIF, and WebP are
accepted.

## queued messages

sending a message while the agent is still responding queues it as a follow-up
that runs after the current turn, rather than cancelling the turn. queued
messages appear dimmed at the bottom of the log and are sent in order as the
agent finishes. to stop the current turn instead, press Esc, or send with an
empty composer (the send button shows a stop icon when the composer is empty).

## roadmap

see [ROADMAP.md](https://github.com/khimaros/pi-webui/blob/HEAD/ROADMAP.md) for implemented and planned features.

## architecture

```
src/
  extension/   pi extension entry (slash command + auto-start flag), shipped as .ts
                 index.ts -- spawns the bin, handshakes on base_url=
  server/      http + websocket server hosting the pi sdk runtime, compiled to dist/
                 index.ts (standalone entrypoint), event-log.ts, log.ts,
                 watch.ts, ext-ui.ts, hub.ts
public/        browser client (vanilla js, no build step)
test/          node --test files (.mjs) + python e2e (fake_openai_test.py)
```

the server compiles to `dist/` (the bin runs under plain node, which will not
type-strip `.ts` under `node_modules/`); the extension stays `.ts` and is loaded by
pi's jiti loader.

## development

```bash
make                # install deps + compile the server to dist/ (tsc)
make start          # run the server
make install        # install pi-webui globally from this checkout (onto PATH)
make update         # update dependencies (npm update)
make test           # build + run tests
make test-integration  # black-box python e2e against fake-openai
make lint           # tsc --noEmit + node --check on .mjs sources
make precommit      # lint + test + test-integration
make vendor         # refresh public/vendor (marked, highlight.js)
make clean          # rm -rf dist build
```
