pi-web-ui 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,414 +1,397 @@
1
- # pi-web-ui
2
-
3
- **English** | [简体中文](README.zh-CN.md)
4
-
5
- A web chat interface for the [pi coding agent](https://pi.dev), built directly on
6
- the **pi SDK** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) —
7
- no subprocess, no JSON-RPC shim. The agent runs in the server process and streams
8
- events to the browser over WebSocket.
9
-
10
- Inspired by [Pintra (pi-vsc)](https://github.com/bilalbentoumi/pi-vsc), which does
11
- the same thing inside VS Code by spawning `pi --mode rpc`. This project instead
12
- uses the SDK's `createAgentSessionRuntime` API in-process (the SDK docs recommend
13
- this over RPC for Node.js apps), so you get type safety, direct state access, and
14
- your existing pi auth/config/extensions — nothing extra to install or configure.
15
-
16
- ## Features
17
-
18
- - 🧠 Full agent loop with **thinking** blocks (collapsible) and streaming text
19
- - 🛠 Tool execution cards with **live output streaming**, status (queued → running → done/error), and copyable arguments
20
- - 💬 Session **history persisted per browser** (localStorage clientId + per-client session dirs) — refresh or restart and your chats come back. The conversation panel also lists the pi CLI/TUI sessions for the current folder (tagged `TUI`), so you can resume a terminal conversation from the web UI
21
- - 📂 **Project memory**: the last workspace of each browser is remembered and restored on restart; a "Recent projects" list in the left panel switches workspaces in one click, and each project keeps its own sessions so you can always pick up an old conversation
22
- - ✏️ **Edit & re-ask**: every past question has an edit button — change it and re-ask from that point. The server forks a new branch session (keeping the full history before that question) while the original conversation stays untouched in the session list
23
- - ⚡ **Long chats stay fast**: past 30 messages, older messages collapse into summary rows (role + first-line preview + block counts — no Markdown/thinking/tool output rendered); click to expand the full content. The latest 15 messages always render in full
24
- - ⬇️ **Self-update**: the top-right corner shows the running version; the update panel checks npm for the latest release and can run `npm i -g` in one click (a restart is required to take effect)
25
- - 🔄 Model & thinking-level cycling (same as pi's TUI), new chat, abort/stop
26
- - 📎 Markdown rendering with GFM tables, syntax-highlighted code blocks and copy buttons
27
- - 📁 Workspace-aware: the agent reads/edits/runs code in a configurable directory using **your** `~/.pi/agent` auth, models, skills and extensions
28
- - 🌐 Multiple browser clients each get an isolated session (private session dir per clientId)
29
- - 🖥 Built-in **terminal** (xterm.js + node-pty, no VS Code needed): three panes — a
30
- **command list** on the left (user-defined commands with `${pwd}` support, persisted in
31
- the project's `.pi/commands.json`), the **terminal** in the middle, and a VSCode-style
32
- **tab strip** on the right for multiple concurrent shells. Switch between chat and
33
- terminal views with the toggle in the top bar.
34
-
35
- ## Quick start
36
-
37
- Requires Node.js ≥ 22.19 (the pi SDK requires it; older Node fails with
38
- `Unexpected token 'with'` when loading the SDK) and a configured pi install
39
- (run `pi` once to log in).
40
-
41
- ```bash
42
- npm install
43
- npm run dev # server on :8787, web UI on :5173 (auto-proxied)
44
- # open http://localhost:5173
45
- ```
46
-
47
- Production:
48
-
49
- ```bash
50
- npm run build # compiles server (tsc) + frontend (vite)
51
- npm start # serves everything on http://localhost:8787
52
- ```
53
-
54
- ## npm package (install / start / stop / update / uninstall)
55
-
56
- The package is published on npm as [`pi-web-ui`](https://www.npmjs.com/package/pi-web-ui).
57
-
58
- ### Install
59
-
60
- ```bash
61
- # install globally (recommended)
62
- npm i -g pi-web-ui
63
-
64
- # or run without installing (pulls the latest, starts on :8787)
65
- npx pi-web-ui
66
-
67
- # or install the local checkout (for testing changes before publishing)
68
- npm i -g .
69
- ```
70
-
71
- > **pi-managed npm?** If your `npm` is the pi wrapper that blocks dependency
72
- > install scripts, approve node-pty's native build once after installing:
73
- > `npm approve-scripts node-pty@1.1.0` (standard npm does this automatically).
74
-
75
- ### Start
76
-
77
- ```bash
78
- pi-web-ui # foreground, http://localhost:8787
79
- PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # custom port / workspace
80
- ```
81
-
82
- Foreground start automatically opens your default browser at the URL once the
83
- server is up; pass `--no-browser` to skip that (scripted / headless starts).
84
-
85
- > **Always open the UI via `http://localhost`** (or HTTPS). Plain-HTTP access
86
- > from a LAN IP (e.g. `http://192.168.1.10:8787`) is not a secure context, so
87
- > the browser can't use the File System Access API for file downloads — and
88
- > on Windows, Chrome/Edge may then silently block downloads of no-reputation
89
- > file types (.zip/.exe/…) with no error message. Via localhost, downloads
90
- > open a native save dialog that always works.
91
-
92
- To run it in the background or auto-start on boot, use a system service —
93
- see [Deploy & auto-start on boot](#deploy--auto-start-on-boot) (systemd /
94
- launchd / Docker).
95
-
96
- The `pi-web-ui` command serves the built frontend and the WebSocket API from
97
- wherever the package is installed no repo checkout needed. It uses **your**
98
- `~/.pi/agent` config (auth/models/skills) and stores per-client sessions under
99
- `<PI_WEB_CWD>/.pi-web`.
100
-
101
- ### Stop
102
-
103
- - **Foreground**: press `Ctrl+C` in the terminal running it.
104
- - **systemd**: `sudo systemctl stop pi-web-ui`
105
- - **launchd**: `launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui`
106
- - **Windows (scheduled task)**: `pi-web-ui server stop` (or
107
- `schtasks /End /TN pi-web-ui`; stops the running instance, auto-start stays
108
- until `server uninstall`)
109
- - **Docker**: `docker compose stop` (stop + remove the container: `docker compose down`)
110
-
111
- (Background processes should be managed by a system service, not `nohup` —
112
- service stop commands above also stop and disable auto-start.)
113
-
114
- ### Verify / version
115
-
116
- ```bash
117
- pi-web-ui --version # CLI version
118
- npm ls -g pi-web-ui # installed? which version?
119
- which pi-web-ui # executable location
120
- ```
121
-
122
- ### Update
123
-
124
- ```bash
125
- npm i -g pi-web-ui@latest # upgrade to the latest published version
126
- # restart the server afterwards for the new version to take effect
127
- ```
128
-
129
- ### Uninstall
130
-
131
- ```bash
132
- npm uninstall -g pi-web-ui
133
- ```
134
-
135
- Uninstalling does **not** delete your chats: session data lives in
136
- `<PI_WEB_CWD>/.pi-web` (or `PI_WEB_DATA_DIR`) and survives uninstall/upgrade.
137
-
138
- ### Manage as a system service (auto-start)
139
-
140
- Install the server as a system service that starts on boot, with a custom
141
- port and workspace:
142
-
143
- ```bash
144
- pi-web-ui server install --port 9000 --cwd /path/to/project # install + start
145
- pi-web-ui server status # running? auto-start?
146
- pi-web-ui server restart # restart (also applies config changes)
147
- pi-web-ui server stop # stop + disable auto-start
148
- pi-web-ui server start # start again
149
- pi-web-ui server uninstall # remove the service entirely
150
- ```
151
-
152
- - **macOS** launchd agent (no sudo): writes and loads
153
- `~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist`, restarts on crash
154
- (`KeepAlive`), logs to `/tmp/pi-web-ui.log` / `/tmp/pi-web-ui.err`.
155
- - **Linux** systemd unit (auto-sudo): writes
156
- `/etc/systemd/system/pi-web-ui.service` and runs `systemctl enable --now`,
157
- logs via `journalctl -u pi-web-ui -f`.
158
- - **Windows** Task Scheduler: creates a user task that starts at logon
159
- (same as a launchd agent; usually no admin needed, but on some machines
160
- `schtasks /Create` requires an elevated PowerShell — if `install` fails
161
- with `ERROR: Access is denied`, rerun it from an admin shell). It runs a
162
- PowerShell launcher generated at
163
- `%APPDATA%\pi-web-ui\pi-web-ui.ps1` via `powershell.exe -WindowStyle
164
- Hidden` no black console window stays open, so there's nothing to
165
- accidentally close/kill. The launcher sets env, cd's to the workspace,
166
- launches node, appends logs to `%USERPROFILE%\pi-web-ui.log`. The task XML
167
- is saved next to it; restarts on failure. **Always pass `--cwd`
168
- explicitly** — the task inherits the installing shell's directory, and an
169
- admin shell defaults to `C:\WINDOWS\system32`, which the non-elevated
170
- task cannot write to (EPERM at startup). See
171
- [Windows Task Scheduler](#windows--task-scheduler) for details.
172
- - Options: `--port` (default 8787 or `$PORT`), `--cwd` (default `$PI_WEB_CWD`
173
- or the current directory), `--data-dir` (sessions), `--name` (custom service
174
- name; on macOS the label is `com.xingshuyin.pi-web-ui`, custom names become
175
- `com.<name>.server`). `--print` previews the generated unit/plist/task files
176
- without applying it.
177
- - Rerunning `install` with new options regenerates the config and restarts the
178
- service — that's how you change the port/cwd of an installed service.
179
-
180
- ## Configuration
181
-
182
- | Env var | Default | Description |
183
- | -------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
184
- | `PORT` | `8787` | HTTP/WebSocket port |
185
- | `PI_WEB_CWD` | server's cwd | The workspace directory the agent operates in (read/edit/bash/write) |
186
- | `PI_WEB_DATA_DIR` | `<cwd>/.pi-web` | Where per-client session dirs are stored |
187
- | `PI_WEB_INLINE_FILE_MAX` | `12288` (12KB) | Text attachments at or below this size are inlined into the model context; larger files are passed as path references and the model reads them on demand (saves tokens for small edits) |
188
- | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi config dir (auth.json, models.json, skills, extensions) |
189
-
190
- Example — point the agent at a project:
191
-
192
- ```bash
193
- PI_WEB_CWD=/path/to/your/project npm run dev
194
- ```
195
-
196
- ## Architecture
197
-
198
- ```text
199
- Browser (React + Vite)
200
- │ WebSocket JSON — snapshot-driven protocol (server/protocol.ts)
201
-
202
- server/index.ts express static + ws endpoint
203
-
204
- server/agent-service.ts per-client ClientSession:
205
- │ createAgentSessionRuntime({ sessionManager: SessionManager.continueRecent(cwd, sessionDir) })
206
- │ session.subscribe(events) throttled full-state snapshots + live tool deltas
207
-
208
- @earendil-works/pi-coding-agent (SDK, in-process)
209
- │ ModelRuntime (auth from ~/.pi/agent) · tools · extensions · skills
210
-
211
- your LLM provider
212
- ```
213
-
214
- Key design points:
215
-
216
- - **Snapshot-driven UI.** The server is the source of truth: after every SDK
217
- event it schedules a throttled (60 ms) full-state snapshot, and the browser
218
- renders purely from snapshots. Reconnects just re-request `get_state`. Large
219
- payloads (tool output, text) are capped during serialization (`server/serialize.ts`).
220
- - **Live assistant streaming.** The in-progress message (SDK
221
- `agent.state.streamingMessage`) is serialized into every snapshot, so thinking
222
- blocks and answer text appear in the browser as they are generated with a
223
- blinking cursor instead of only after the turn finishes. The partial message
224
- gets a stable `stream-<ts>` id so it stays mounted (open thinking/tool blocks
225
- keep their state) across snapshots.
226
- - **Size-aware attachments.** Clicking + on a file queues it as an attachment
227
- (shown as chips above the input). On send, the server attaches each file as an
228
- independent custom message (SDK `sendCustomMessage` + `nextTurn` asides) the
229
- user message stays clean, and each file renders as its own collapsible card:
230
- small text files (≤ `PI_WEB_INLINE_FILE_MAX`, default 12KB) are inlined so the
231
- model sees them immediately; larger files are passed as a `<file path=...>`
232
- reference and the model reads them on demand with its `read` tool, so attaching
233
- a 5 MB file costs only a few tokens until the model actually looks at it.
234
- Images are always attached as image content.
235
- - **File preview with line selection.** Click a file name (or its 👁 button) in
236
- the right panel to open a preview modal with line numbers. Click / drag /
237
- Shift+click to select a line range, then “添加到对话” to queue it as a `lines`
238
- attachment the server inlines only the selected range
239
- (`<file path=... lines="2-3">`), so you can point the agent at exactly the
240
- code you mean without dumping the whole file. Preview reads are capped at
241
- 512 KB and binary files are detected and refused.
242
- - **Live tool output.** `bash_execution_update` / `tool_execution_update` events
243
- are forwarded as lightweight `tool_delta` messages so terminal output streams
244
- in real time; the final output arrives in the toolResult message on the next
245
- snapshot, which supersedes the delta buffer.
246
- - **Isolated sessions.** Each browser client gets `sessions/<clientId>/` under
247
- the data dir, resumed on reconnect via `SessionManager.continueRecent`.
248
- - **Everything you already have.** No separate auth step — the SDK reads
249
- `~/.pi/agent/auth.json` and loads your global extensions/skills automatically.
250
-
251
- ## Terminal
252
-
253
- Toggle the terminal view from the top bar (对话/终端). It replaces the chat layout
254
- with three panes:
255
-
256
- - **Left — 命令 (commands)**: click a command to open a terminal tab in its directory and
257
- run it. Add/edit/delete commands in the panel; they are saved to
258
- `<project>/.pi/commands.json` (committed to the repo, shared with teammates):
259
-
260
- ```json
261
- {
262
- "commands": [
263
- { "name": "dev", "command": "npm run dev", "cwd": "${pwd}" },
264
- { "name": "test", "command": "npm test", "cwd": "${pwd}/server" },
265
- { "name": "build", "command": "npm run build", "cwd": "~/other-project" }
266
- ]
267
- }
268
- ```
269
-
270
- `${pwd}` resolves to the agent's current working directory (the one shown in the chat
271
- view's file panel, changeable via set_cwd); `~` and relative paths also work. The `+`
272
- button at the top of the command panel creates a new entry.
273
- - **Middle the terminal**: each tab is a real PTY (your `$SHELL` on
274
- macOS/Linux; PowerShell or cmd.exe — `$COMSPEC` — on Windows); output
275
- streams live and you can type, Ctrl+C, resize, etc. exactly like a desktop
276
- terminal. Git Bash users on Windows get their `$SHELL` automatically.
277
- - **Right 终端 (tabs)**: VSCode-style vertical tab strip. `+` opens a plain shell in the
278
- current directory. Closing a tab kills its process.
279
-
280
- Notes:
281
-
282
- - Running commands keep running while you switch back to the chat view.
283
- - Terminals are killed when the last browser tab for a client disconnects (no orphaned
284
- dev servers), so a dropped connection resets the terminal view.
285
-
286
- ## Protocol
287
-
288
- See `server/protocol.ts` for the full wire format. Client → server: `hello`,
289
- `prompt`, `abort`, `new_chat`, `cycle_model`, `cycle_thinking`, `get_state`,
290
- `list_sessions`, `switch_session`, `list_files`, `list_models`, `set_model`,
291
- `set_thinking`, `set_cwd`, `complete_path`, `dialog_response`,
292
- `terminal_create`, `terminal_input`, `terminal_resize`, `terminal_kill`,
293
- `run_command`, `list_commands`, `save_commands`.
294
- Server → client: `ready`, `snapshot` (full `UiState`), `tool_delta`, `notice`,
295
- `terminal_output`, `terminal_exit`, `commands`.
296
-
297
- ## Scripts
298
-
299
- | Script | What it does |
300
- | ---------------------------------- | ------------------------------------------------------- |
301
- | `npm run dev` | server (tsx watch) + Vite dev server with WS proxy |
302
- | `npm run build` | type-check + build frontend and server |
303
- | `npm start` | run the production server (serves`web/dist`) |
304
- | `npm run typecheck` | `tsc --noEmit` for both server and web |
305
- | `node terminal-smoke-test.mjs` | WS-level terminal/commands protocol test (build first) |
306
- | `node terminal-browser-test.mjs` | headless-browser E2E of the terminal view (build first) |
307
-
308
- ## Deploy & auto-start on boot
309
-
310
- Quickest path: `pi-web-ui server install --port 8787 --cwd /path` — installs
311
- and starts the service on boot (see [Manage as a system service](#manage-as-a-system-service-auto-start)).
312
- The manual alternatives below are kept for reference / non-standard setups.
313
-
314
- ### Docker (one command)
315
-
316
- ```bash
317
- docker compose up -d # builds, starts on :8787, auto-restarts on boot
318
- docker compose stop # stop (keeps the container)
319
- docker compose down # stop and remove the container
320
- ```
321
-
322
- `restart: unless-stopped` in `docker-compose.yml` brings the server back up
323
- whenever the Docker daemon starts (boot, crashes, reboots). Mount a volume for
324
- `/app/.pi-web` (sessions persist) and, optionally, your `~/.pi/agent` config
325
- and a workspace see the comments in `docker-compose.yml`.
326
-
327
- > **Access via `http://localhost:8787`** even with Docker: on the same machine
328
- > the port mapping makes it a secure context, so file downloads use the native
329
- > save dialog (see the note in [Start](#start)). Accessing from another device
330
- > over plain HTTP (`http://<host-ip>:8787`) loses that — use an SSH tunnel
331
- > (`ssh -L 8787:localhost:8787 host`) for the same benefit.
332
-
333
- ### Linux — systemd
334
-
335
- ```bash
336
- sudo npm i -g pi-web-ui
337
- sudo cp deploy/pi-web-ui.service /etc/systemd/system/
338
- # edit User/WorkingDirectory/Environment in the unit first
339
- sudo systemctl daemon-reload
340
- sudo systemctl enable --now pi-web-ui # starts now + on every boot
341
- sudo systemctl stop pi-web-ui # stop
342
- sudo systemctl disable pi-web-ui # stop auto-start on boot
343
- journalctl -u pi-web-ui -f # logs
344
- ```
345
-
346
- ### macOS launchd
347
-
348
- ```bash
349
- npm i -g pi-web-ui
350
- cp deploy/com.xingshuyin.pi-web-ui.plist ~/Library/LaunchAgents/
351
- # edit ProgramArguments / WorkingDirectory / PI_WEB_CWD (which pi-web-ui)
352
- launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist
353
- launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui # stop + remove auto-start
354
- # logs: /tmp/pi-web-ui.log, /tmp/pi-web-ui.err
355
- ```
356
-
357
- ### Windows Task Scheduler
358
-
359
- Easiest path (generates everything, no manual XML editing):
360
-
361
- ```bat
362
- npm i -g pi-web-ui
363
- pi-web-ui server install --port 8787 --cwd C:\path\to\project
364
- pi-web-ui server status
365
- pi-web-ui server restart
366
- pi-web-ui server stop :: stop the running instance (auto-start stays)
367
- pi-web-ui server uninstall :: remove the task entirely
368
- ```
369
-
370
- What it does: writes `%APPDATA%\pi-web-ui\pi-web-ui.ps1` (a PowerShell launcher
371
- that sets `PORT`/`PI_WEB_CWD`, cd's to the workspace, launches node and
372
- appends output to `%USERPROFILE%\pi-web-ui.log`) plus the Task Scheduler XML,
373
- then registers a **logon** task (`schtasks /Create /XML` the task runs when
374
- you log in, same as a launchd agent; usually no admin needed, but see the
375
- troubleshooting note below if you get access denied). The task invokes
376
- `powershell.exe -WindowStyle Hidden`, so the server runs with **no black
377
- console window** there is nothing to accidentally close or kill. Preview
378
- both generated files without installing: `pi-web-ui server install --print`.
379
-
380
- Manual alternative with `deploy/pi-web-ui-task.xml`: edit the paths, save the
381
- file as **UTF-16 LE** (schtasks requires it), then
382
- `schtasks /Create /TN "pi-web-ui" /XML pi-web-ui-task.xml /F` and
383
- `schtasks /Run /TN "pi-web-ui"`.
384
-
385
- > **Windows troubleshooting**
386
- >
387
- > - **`install` fails with `ERROR: Access is denied` (错误: 拒绝访问)** on
388
- > some machines Task Scheduler refuses to let a non-elevated token create
389
- > tasks (deleting your own task with `schtasks /Delete` still works, which
390
- > is why `server uninstall` succeeds). Fix: run
391
- > `pi-web-ui server install` from an **elevated (admin) PowerShell**.
392
- > - **Always pass `--cwd` explicitly, and point it at a user-writable
393
- > directory.** The task inherits the installing shell's current directory
394
- > as its working directory. Installing from an elevated shell without
395
- > `--cwd` registers the task with `C:\WINDOWS\system32`, and the server
396
- > then fails at startup with
397
- > `EPERM: operation not permitted, mkdir 'C:\WINDOWS\system32\.pi-web\sessions\...'`
398
- > because the logon task runs with a least-privilege token that cannot
399
- > write under `system32`. Use e.g. `--cwd C:\Users\<you>` (sessions then
400
- > go to `C:\Users\<you>\.pi-web`).
401
- > - **Fix an already-broken task** (task created with the wrong directory):
402
- > `pi-web-ui server uninstall`, then
403
- > `pi-web-ui server install --cwd C:\Users\<you>` from an elevated shell.
404
- > Rerunning `install` with new options also regenerates the task in place.
405
-
406
- > **Boot-start without login?** A logon task needs an interactive session, just
407
- > like a launchd agent. For headless/always-on Windows use Docker (see above).
408
-
409
- The three templates use `KeepAlive` / `Restart=on-failure` / `RestartOnFailure`
410
- so the server survives crashes, and start at login/boot automatically.
411
-
412
- ## License
413
-
414
- MIT
1
+ # pi-web-ui
2
+
3
+ **English** | [简体中文](README.zh-CN.md)
4
+
5
+ A web chat interface for the [pi coding agent](https://pi.dev), built directly on
6
+ the **pi SDK** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) —
7
+ no subprocess, no JSON-RPC shim. The agent runs in the server process and streams
8
+ events to the browser over WebSocket.
9
+
10
+ Inspired by [Pintra (pi-vsc)](https://github.com/bilalbentoumi/pi-vsc), which does
11
+ the same thing inside VS Code by spawning `pi --mode rpc`. This project instead
12
+ uses the SDK's `createAgentSessionRuntime` API in-process (the SDK docs recommend
13
+ this over RPC for Node.js apps), so you get type safety, direct state access, and
14
+ your existing pi auth/config/extensions — nothing extra to install or configure.
15
+
16
+ ## Features
17
+
18
+ - 🧠 Full agent loop with **thinking** blocks (collapsible) and streaming text
19
+ - 🛠 Tool execution cards with **live output streaming**, status (queued → running → done/error), and copyable arguments
20
+ - 💬 Session **history persisted per browser** (localStorage clientId + per-client session dirs) — refresh or restart and your chats come back. The conversation panel also lists the pi CLI/TUI sessions for the current folder (tagged `TUI`), so you can resume a terminal conversation from the web UI
21
+ - 📂 **Project memory**: the last workspace of each browser is remembered and restored on restart; a "Recent projects" list in the left panel switches workspaces in one click, and each project keeps its own sessions so you can always pick up an old conversation
22
+ - ✏️ **Edit & re-ask**: every past question has an edit button — change it and re-ask from that point. The server forks a new branch session (keeping the full history before that question) while the original conversation stays untouched in the session list
23
+ - ⚡ **Long chats stay fast**: past 30 messages, older messages collapse into summary rows (role + first-line preview + block counts — no Markdown/thinking/tool output rendered); click to expand the full content. The latest 15 messages always render in full
24
+ - ⬇️ **Self-update**: the top-right corner shows the running version; the update panel checks npm for the latest release and can run `npm i -g` in one click (a restart is required to take effect)
25
+ - 🔄 Model & thinking-level cycling (same as pi's TUI), new chat, abort/stop
26
+ - 📎 Markdown rendering with GFM tables, syntax-highlighted code blocks and copy buttons
27
+ - 📁 Workspace-aware: the agent reads/edits/runs code in a configurable directory using **your** `~/.pi/agent` auth, models, skills and extensions
28
+ - 🌐 Multiple browser clients each get an isolated session (private session dir per clientId)
29
+ - 🖥 Built-in **terminal** (xterm.js + node-pty, no VS Code needed): three panes — a
30
+ **command list** on the left (user-defined commands with `${pwd}` support, persisted in
31
+ the project's `.pi/commands.json`), the **terminal** in the middle, and a VSCode-style
32
+ **tab strip** on the right for multiple concurrent shells. Switch between chat and
33
+ terminal views with the toggle in the top bar.
34
+
35
+ ## Quick start
36
+
37
+ Requires Node.js ≥ 22.19 (the pi SDK requires it; older Node fails with
38
+ `Unexpected token 'with'` when loading the SDK) and a configured pi install
39
+ (run `pi` once to log in).
40
+
41
+ ```bash
42
+ npm install
43
+ npm run dev # server on :8787, web UI on :5173 (auto-proxied)
44
+ # open http://localhost:5173
45
+ ```
46
+
47
+ Production:
48
+
49
+ ```bash
50
+ npm run build # compiles server (tsc) + frontend (vite)
51
+ npm start # serves everything on http://localhost:8787
52
+ ```
53
+
54
+ ## npm package (install / start / stop / update / uninstall)
55
+
56
+ The package is published on npm as [`pi-web-ui`](https://www.npmjs.com/package/pi-web-ui).
57
+
58
+ ### Install
59
+
60
+ ```bash
61
+ # install globally (recommended)
62
+ npm i -g pi-web-ui
63
+
64
+ # or run without installing (pulls the latest, starts on :8787)
65
+ npx pi-web-ui
66
+
67
+ # or install the local checkout (for testing changes before publishing)
68
+ npm i -g .
69
+ ```
70
+
71
+ > **pi-managed npm?** If your `npm` is the pi wrapper that blocks dependency
72
+ > install scripts, approve node-pty's native build once after installing:
73
+ > `npm approve-scripts node-pty@1.1.0` (standard npm does this automatically).
74
+
75
+ ### Start
76
+
77
+ ```bash
78
+ pi-web-ui # foreground, http://localhost:8787
79
+ PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # custom port / workspace
80
+ ```
81
+
82
+ To run it in the background or auto-start on boot, use a system service —
83
+ see [Deploy &amp; auto-start on boot](#deploy--auto-start-on-boot) (systemd /
84
+ launchd / Docker).
85
+
86
+ The `pi-web-ui` command serves the built frontend and the WebSocket API from
87
+ wherever the package is installed no repo checkout needed. It uses **your**
88
+ `~/.pi/agent` config (auth/models/skills) and stores per-client sessions under
89
+ `<PI_WEB_CWD>/.pi-web`.
90
+
91
+ ### Stop
92
+
93
+ - **Foreground**: press `Ctrl+C` in the terminal running it.
94
+ - **systemd**: `sudo systemctl stop pi-web-ui`
95
+ - **launchd**: `launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui`
96
+ - **Windows (scheduled task)**: `pi-web-ui server stop` (or
97
+ `schtasks /End /TN pi-web-ui`; stops the running instance, auto-start stays
98
+ until `server uninstall`)
99
+ - **Docker**: `docker compose stop` (stop + remove the container: `docker compose down`)
100
+
101
+ (Background processes should be managed by a system service, not `nohup` —
102
+ service stop commands above also stop and disable auto-start.)
103
+
104
+ ### Verify / version
105
+
106
+ ```bash
107
+ pi-web-ui --version # CLI version
108
+ npm ls -g pi-web-ui # installed? which version?
109
+ which pi-web-ui # executable location
110
+ ```
111
+
112
+ ### Update
113
+
114
+ ```bash
115
+ npm i -g pi-web-ui@latest # upgrade to the latest published version
116
+ # restart the server afterwards for the new version to take effect
117
+ ```
118
+
119
+ ### Uninstall
120
+
121
+ ```bash
122
+ npm uninstall -g pi-web-ui
123
+ ```
124
+
125
+ Uninstalling does **not** delete your chats: session data lives in
126
+ `<PI_WEB_CWD>/.pi-web` (or `PI_WEB_DATA_DIR`) and survives uninstall/upgrade.
127
+
128
+ ### Manage as a system service (auto-start)
129
+
130
+ Install the server as a system service that starts on boot, with a custom
131
+ port and workspace:
132
+
133
+ ```bash
134
+ pi-web-ui server install --port 9000 --cwd /path/to/project # install + start
135
+ pi-web-ui server status # running? auto-start?
136
+ pi-web-ui server restart # restart (also applies config changes)
137
+ pi-web-ui server stop # stop + disable auto-start
138
+ pi-web-ui server start # start again
139
+ pi-web-ui server uninstall # remove the service entirely
140
+ ```
141
+
142
+ - **macOS** → launchd agent (no sudo): writes and loads
143
+ `~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist`, restarts on crash
144
+ (`KeepAlive`), logs to `/tmp/pi-web-ui.log` / `/tmp/pi-web-ui.err`.
145
+ - **Linux** systemd unit (auto-sudo): writes
146
+ `/etc/systemd/system/pi-web-ui.service` and runs `systemctl enable --now`,
147
+ logs via `journalctl -u pi-web-ui -f`.
148
+ - **Windows** Task Scheduler: creates a user task that starts at logon
149
+ (same as a launchd agent; usually no admin needed, but on some machines
150
+ `schtasks /Create` requires an elevated PowerShell — if `install` fails
151
+ with `ERROR: Access is denied`, rerun it from an admin shell). It runs a
152
+ PowerShell launcher generated at
153
+ `%APPDATA%\pi-web-ui\pi-web-ui.ps1` via `powershell.exe -WindowStyle Hidden` — no black console window stays open, so there's nothing to
154
+ accidentally close/kill. The launcher sets env, cd's to the workspace,
155
+ launches node, appends logs to `%USERPROFILE%\pi-web-ui.log`. The task XML
156
+ is saved next to it; restarts on failure. **Always pass `--cwd`
157
+ explicitly** the task inherits the installing shell's directory, and an
158
+ admin shell defaults to `C:\WINDOWS\system32`, which the non-elevated
159
+ task cannot write to (EPERM at startup). See
160
+ [Windows Task Scheduler](#windows--task-scheduler) for details.
161
+ - Options: `--port` (default 8787 or `$PORT`), `--cwd` (default `$PI_WEB_CWD`
162
+ or the current directory), `--data-dir` (sessions), `--name` (custom service
163
+ name; on macOS the label is `com.xingshuyin.pi-web-ui`, custom names become
164
+ `com.<name>.server`). `--print` previews the generated unit/plist/task files
165
+ without applying it.
166
+ - Rerunning `install` with new options regenerates the config and restarts the
167
+ service that's how you change the port/cwd of an installed service.
168
+
169
+ ## Configuration
170
+
171
+ | Env var | Default | Description |
172
+ | -------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
173
+ | `PORT` | `8787` | HTTP/WebSocket port |
174
+ | `PI_WEB_CWD` | server's cwd | The workspace directory the agent operates in (read/edit/bash/write) |
175
+ | `PI_WEB_DATA_DIR` | `<cwd>/.pi-web` | Where per-client session dirs are stored |
176
+ | `PI_WEB_INLINE_FILE_MAX` | `12288` (12KB) | Text attachments at or below this size are inlined into the model context; larger files are passed as path references and the model reads them on demand (saves tokens for small edits) |
177
+ | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi config dir (auth.json, models.json, skills, extensions) |
178
+
179
+ Example — point the agent at a project:
180
+
181
+ ```bash
182
+ PI_WEB_CWD=/path/to/your/project npm run dev
183
+ ```
184
+
185
+ ## Architecture
186
+
187
+ ```text
188
+ Browser (React + Vite)
189
+ │ WebSocket JSON — snapshot-driven protocol (server/protocol.ts)
190
+
191
+ server/index.ts express static + ws endpoint
192
+
193
+ server/agent-service.ts per-client ClientSession:
194
+ │ createAgentSessionRuntime({ sessionManager: SessionManager.continueRecent(cwd, sessionDir) })
195
+ │ session.subscribe(events) → throttled full-state snapshots + live tool deltas
196
+
197
+ @earendil-works/pi-coding-agent (SDK, in-process)
198
+ │ ModelRuntime (auth from ~/.pi/agent) · tools · extensions · skills
199
+
200
+ your LLM provider
201
+ ```
202
+
203
+ Key design points:
204
+
205
+ - **Snapshot-driven UI.** The server is the source of truth: after every SDK
206
+ event it schedules a throttled (60 ms) full-state snapshot, and the browser
207
+ renders purely from snapshots. Reconnects just re-request `get_state`. Large
208
+ payloads (tool output, text) are capped during serialization (`server/serialize.ts`).
209
+ - **Live assistant streaming.** The in-progress message (SDK
210
+ `agent.state.streamingMessage`) is serialized into every snapshot, so thinking
211
+ blocks and answer text appear in the browser as they are generated — with a
212
+ blinking cursor — instead of only after the turn finishes. The partial message
213
+ gets a stable `stream-<ts>` id so it stays mounted (open thinking/tool blocks
214
+ keep their state) across snapshots.
215
+ - **Size-aware attachments.** Clicking + on a file queues it as an attachment
216
+ (shown as chips above the input). On send, the server attaches each file as an
217
+ independent custom message (SDK `sendCustomMessage` + `nextTurn` asides) the
218
+ user message stays clean, and each file renders as its own collapsible card:
219
+ small text files ( `PI_WEB_INLINE_FILE_MAX`, default 12KB) are inlined so the
220
+ model sees them immediately; larger files are passed as a `<file path=...>`
221
+ reference and the model reads them on demand with its `read` tool, so attaching
222
+ a 5 MB file costs only a few tokens until the model actually looks at it.
223
+ Images are always attached as image content.
224
+ - **File preview with line selection.** Click a file name (or its 👁 button) in
225
+ the right panel to open a preview modal with line numbers. Click / drag /
226
+ Shift+click to select a line range, then “添加到对话” to queue it as a `lines`
227
+ attachment the server inlines only the selected range
228
+ (`<file path=... lines="2-3">`), so you can point the agent at exactly the
229
+ code you mean without dumping the whole file. Preview reads are capped at
230
+ 512 KB and binary files are detected and refused.
231
+ - **Live tool output.** `bash_execution_update` / `tool_execution_update` events
232
+ are forwarded as lightweight `tool_delta` messages so terminal output streams
233
+ in real time; the final output arrives in the toolResult message on the next
234
+ snapshot, which supersedes the delta buffer.
235
+ - **Isolated sessions.** Each browser client gets `sessions/<clientId>/` under
236
+ the data dir, resumed on reconnect via `SessionManager.continueRecent`.
237
+ - **Everything you already have.** No separate auth step the SDK reads
238
+ `~/.pi/agent/auth.json` and loads your global extensions/skills automatically.
239
+
240
+ ## Terminal
241
+
242
+ Toggle the terminal view from the top bar (对话/终端). It replaces the chat layout
243
+ with three panes:
244
+
245
+ - **Left 命令 (commands)**: click a command to open a terminal tab in its directory and
246
+ run it. Add/edit/delete commands in the panel; they are saved to
247
+ `<project>/.pi/commands.json` (committed to the repo, shared with teammates):
248
+
249
+ ```json
250
+ {
251
+ "commands": [
252
+ { "name": "dev", "command": "npm run dev", "cwd": "${pwd}" },
253
+ { "name": "test", "command": "npm test", "cwd": "${pwd}/server" },
254
+ { "name": "build", "command": "npm run build", "cwd": "~/other-project" }
255
+ ]
256
+ }
257
+ ```
258
+
259
+ `${pwd}` resolves to the agent's current working directory (the one shown in the chat
260
+ view's file panel, changeable via set_cwd); `~` and relative paths also work. The `+`
261
+ button at the top of the command panel creates a new entry.
262
+ - **Middle — the terminal**: each tab is a real PTY (your `$SHELL` on
263
+ macOS/Linux; PowerShell or cmd.exe `$COMSPEC` on Windows); output
264
+ streams live and you can type, Ctrl+C, resize, etc. exactly like a desktop
265
+ terminal. Git Bash users on Windows get their `$SHELL` automatically.
266
+ - **Right — 终端 (tabs)**: VSCode-style vertical tab strip. `+` opens a plain shell in the
267
+ current directory. Closing a tab kills its process.
268
+
269
+ Notes:
270
+
271
+ - Running commands keep running while you switch back to the chat view.
272
+ - Terminals are killed when the last browser tab for a client disconnects (no orphaned
273
+ dev servers), so a dropped connection resets the terminal view.
274
+
275
+ ## Protocol
276
+
277
+ See `server/protocol.ts` for the full wire format. Client server: `hello`,
278
+ `prompt`, `abort`, `new_chat`, `cycle_model`, `cycle_thinking`, `get_state`,
279
+ `list_sessions`, `switch_session`, `list_files`, `list_models`, `set_model`,
280
+ `set_thinking`, `set_cwd`, `complete_path`, `dialog_response`,
281
+ `terminal_create`, `terminal_input`, `terminal_resize`, `terminal_kill`,
282
+ `run_command`, `list_commands`, `save_commands`.
283
+ Server client: `ready`, `snapshot` (full `UiState`), `tool_delta`, `notice`,
284
+ `terminal_output`, `terminal_exit`, `commands`.
285
+
286
+ ## Scripts
287
+
288
+ | Script | What it does |
289
+ | ---------------------------------- | ------------------------------------------------------- |
290
+ | `npm run dev` | server (tsx watch) + Vite dev server with WS proxy |
291
+ | `npm run build` | type-check + build frontend and server |
292
+ | `npm start` | run the production server (serves`web/dist`) |
293
+ | `npm run typecheck` | `tsc --noEmit` for both server and web |
294
+ | `node terminal-smoke-test.mjs` | WS-level terminal/commands protocol test (build first) |
295
+ | `node terminal-browser-test.mjs` | headless-browser E2E of the terminal view (build first) |
296
+
297
+ ## Deploy & auto-start on boot
298
+
299
+ Quickest path: `pi-web-ui server install --port 8787 --cwd /path` — installs
300
+ and starts the service on boot (see [Manage as a system service](#manage-as-a-system-service-auto-start)).
301
+ The manual alternatives below are kept for reference / non-standard setups.
302
+
303
+ ### Docker (one command)
304
+
305
+ ```bash
306
+ docker compose up -d # builds, starts on :8787, auto-restarts on boot
307
+ docker compose stop # stop (keeps the container)
308
+ docker compose down # stop and remove the container
309
+ ```
310
+
311
+ `restart: unless-stopped` in `docker-compose.yml` brings the server back up
312
+ whenever the Docker daemon starts (boot, crashes, reboots). Mount a volume for
313
+ `/app/.pi-web` (sessions persist) and, optionally, your `~/.pi/agent` config
314
+ and a workspace — see the comments in `docker-compose.yml`.
315
+
316
+ ### Linux — systemd
317
+
318
+ ```bash
319
+ sudo npm i -g pi-web-ui
320
+ sudo cp deploy/pi-web-ui.service /etc/systemd/system/
321
+ # edit User/WorkingDirectory/Environment in the unit first
322
+ sudo systemctl daemon-reload
323
+ sudo systemctl enable --now pi-web-ui # starts now + on every boot
324
+ sudo systemctl stop pi-web-ui # stop
325
+ sudo systemctl disable pi-web-ui # stop auto-start on boot
326
+ journalctl -u pi-web-ui -f # logs
327
+ ```
328
+
329
+ ### macOS launchd
330
+
331
+ ```bash
332
+ npm i -g pi-web-ui
333
+ cp deploy/com.xingshuyin.pi-web-ui.plist ~/Library/LaunchAgents/
334
+ # edit ProgramArguments / WorkingDirectory / PI_WEB_CWD (which pi-web-ui)
335
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist
336
+ launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui # stop + remove auto-start
337
+ # logs: /tmp/pi-web-ui.log, /tmp/pi-web-ui.err
338
+ ```
339
+
340
+ ### Windows Task Scheduler
341
+
342
+ Easiest path (generates everything, no manual XML editing):
343
+
344
+ ```bat
345
+ npm i -g pi-web-ui
346
+ pi-web-ui server install --port 8787 --cwd C:\path\to\project
347
+ pi-web-ui server status
348
+ pi-web-ui server restart
349
+ pi-web-ui server stop :: stop the running instance (auto-start stays)
350
+ pi-web-ui server uninstall :: remove the task entirely
351
+ ```
352
+
353
+ What it does: writes `%APPDATA%\pi-web-ui\pi-web-ui.ps1` (a PowerShell launcher
354
+ that sets `PORT`/`PI_WEB_CWD`, cd's to the workspace, launches node and
355
+ appends output to `%USERPROFILE%\pi-web-ui.log`) plus the Task Scheduler XML,
356
+ then registers a **logon** task (`schtasks /Create /XML` — the task runs when
357
+ you log in, same as a launchd agent; usually no admin needed, but see the
358
+ troubleshooting note below if you get access denied). The task invokes
359
+ `powershell.exe -WindowStyle Hidden`, so the server runs with **no black
360
+ console window** — there is nothing to accidentally close or kill. Preview
361
+ both generated files without installing: `pi-web-ui server install --print`.
362
+
363
+ Manual alternative with `deploy/pi-web-ui-task.xml`: edit the paths, save the
364
+ file as **UTF-16 LE** (schtasks requires it), then
365
+ `schtasks /Create /TN "pi-web-ui" /XML pi-web-ui-task.xml /F` and
366
+ `schtasks /Run /TN "pi-web-ui"`.
367
+
368
+ > **Windows troubleshooting**
369
+ >
370
+ > - **`install` fails with `ERROR: Access is denied` (错误: 拒绝访问)** — on
371
+ > some machines Task Scheduler refuses to let a non-elevated token create
372
+ > tasks (deleting your own task with `schtasks /Delete` still works, which
373
+ > is why `server uninstall` succeeds). Fix: run
374
+ > `pi-web-ui server install` from an **elevated (admin) PowerShell**.
375
+ > - **Always pass `--cwd` explicitly, and point it at a user-writable
376
+ > directory.** The task inherits the installing shell's current directory
377
+ > as its working directory. Installing from an elevated shell without
378
+ > `--cwd` registers the task with `C:\WINDOWS\system32`, and the server
379
+ > then fails at startup with
380
+ > `EPERM: operation not permitted, mkdir 'C:\WINDOWS\system32\.pi-web\sessions\...'`
381
+ > because the logon task runs with a least-privilege token that cannot
382
+ > write under `system32`. Use e.g. `--cwd C:\Users\<you>` (sessions then
383
+ > go to `C:\Users\<you>\.pi-web`).
384
+ > - **Fix an already-broken task** (task created with the wrong directory):
385
+ > `pi-web-ui server uninstall`, then
386
+ > `pi-web-ui server install --cwd C:\Users\<you>` from an elevated shell.
387
+ > Rerunning `install` with new options also regenerates the task in place.
388
+
389
+ > **Boot-start without login?** A logon task needs an interactive session, just
390
+ > like a launchd agent. For headless/always-on Windows use Docker (see above).
391
+
392
+ The three templates use `KeepAlive` / `Restart=on-failure` / `RestartOnFailure`
393
+ so the server survives crashes, and start at login/boot automatically.
394
+
395
+ ## License
396
+
397
+ MIT