@narumitw/pi-webui
Experimental Radix UI web companion for the current Pi terminal session.
Package details
Install @narumitw/pi-webui from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@narumitw/pi-webui- Package
@narumitw/pi-webui- Version
0.42.0- Published
- Aug 1, 2026
- Downloads
- 2,296/mo · 1,760/wk
- Author
- narumitw
- License
- MIT
- Types
- extension
- Size
- 1.3 MB
- Dependencies
- 11 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
🌐 pi-webui — Current-session Web Companion for Pi
[!WARNING] This extension is experimental. Its browser workflow and package API may change between releases.
@narumitw/pi-webui adds a private, focused browser companion to the current terminal-owned Pi Coding Agent session. It displays Pi's semantic conversation and tool activity as they happen and can send text or sanitized images back into that same session.
This package is intentionally different from the broader, separately maintained @narumitw/pi-web application. WebUI has one current-session chat page and no session manager, shell, file browser, git UI, control room, or task board.
✨ Features
- Streams current-branch user and assistant messages, assistant text updates, tool activity/results, errors, and busy/idle state over Server-Sent Events.
- Renders a safe Markdown subset for headings, lists, emphasis, code, blockquotes, and HTTP(S) links without executing model-provided HTML.
- Preserves open tool/thinking disclosures during keyed streaming updates and offers Jump to latest when new activity arrives while you read earlier messages.
- Sends immediately while Pi is idle and automatically queues Queue next as a follow-up while Pi is busy.
- Provides a separate Steer action while Pi is working; steering is never the default submit action.
- Accepts pasted, dropped, or selected PNG, JPEG, WebP, GIF, BMP, TIFF, HEIC/HEIF, and AVIF images, strips metadata server-side, applies Pi-compatible size limits, and provides ordered thumbnails plus an enlarged preview.
- Reconnects from an ordered event cursor and replaces state from an authoritative snapshot after a gap.
- Keeps a failed browser draft and prevents rapid duplicate submission with request IDs.
- Uses React with Radix Primitives, Themes, Colors, and Icons for accessible controls, disclosures, overlays, adaptive color, and consistent iconography. Published browser assets are bundled locally; runtime use needs no CDN, remote service, browser storage, or automatically launched browser.
📦 Install
pi install npm:@narumitw/pi-webui
Try the working tree without installing:
pi -e ./experimental/pi-webui
# or
just try webui
The package targets the latest Pi release.
🚀 Usage
- Start Pi in a terminal and run
/webuito open the current-state menu. - Select Open WebUI. If the server is already running, select Get a fresh link instead; this invalidates any earlier unused bootstrap link.
- Open the one-time
http://127.0.0.1:<port>/bootstrap?...link shown by Pi. The extension does not open a browser itself. Use/webui openwhen you intentionally want to skip the menu. - Continue typing in either the terminal or browser. Accepted messages from both surfaces appear in the browser transcript.
- While Pi is idle, Send starts a turn immediately. While Pi is working, Queue next queues a follow-up. Use Steer only when the new instruction should reach Pi after the current tool batch.
- Refreshing or opening the link in another tab takes the editing lease. Older tabs remain readable and clearly become read-only.
If another installed extension also registers /webui, Pi assigns numeric command suffixes according to extension load order. Check Pi's command provenance and invoke the WebUI entry.
💬 Commands
| Command | Behavior |
|---|---|
/webui |
Open the current-state menu in TUI or an interactive RPC client. Opening or cancelling the menu has no side effects. |
/webui open |
Start or reuse the current session server and display a fresh one-time link without opening the menu. Unsupported print/JSON modes remain side-effect free because they cannot expose the link. |
/webui settings |
Open the interactive settings screen in TUI mode. Other modes report the manual settings path when notifications are available. |
/webui status |
Show the effective startup preference and source, settings path, and whether the current session server is running. It never issues a bootstrap link. |
/webui help |
Show command and manual-settings help. |
/webui init |
Create the defaults file without overwriting existing content, then open settings in TUI mode. |
Argument completion is available for all subcommands. Bare /webui is menu-first; scripts and users migrating from the earlier direct behavior should use /webui open.
The standard menu keeps the consequential current state beside each decision: whether the session server is running, whether startup is Manual or Every session, and whether values come from defaults or the settings file. Open WebUI explains that it starts a private session server. Get a fresh link explains that it keeps the server but invalidates any earlier unused bootstrap link. Escape closes the main menu without starting a server, issuing a link, or saving settings. Settings, Status & diagnostics, Repair, and Help are standard one-level screens that return to the menu.
⚙️ Settings
WebUI has one optional, global-only JSON settings file:
<getAgentDir()>/pi-webui.json
The normal default path is ~/.pi/agent/pi-webui.json. Pi installations that use another agent directory are resolved through Pi's getAgentDir() API; WebUI adds no environment variable or project override.
{
"startOnSessionStart": false,
"retainSentImages": false,
"maxRetainedImages": 32,
"maxRetainedBytes": 134217728,
"maxImages": 8,
"maxImageBytes": 10485760,
"maxBatchBytes": 41943040,
"maxImagePixels": 50000000
}
| Setting | Default | Behavior |
|---|---|---|
startOnSessionStart |
false |
Start WebUI and display a fresh one-time link after every Pi session initialization, including startup, reload, new, resume, and fork. It never opens a browser. |
retainSentImages |
false |
Opt in to bounded, session-only retention of sanitized images after Pi accepts their browser message. |
maxRetainedImages |
32 |
FIFO retained-image count ceiling. Positive integers up to the hard ceiling of 128 are accepted. |
maxRetainedBytes |
134217728 (128 MiB) |
FIFO retained provider-ready byte ceiling. Positive integers up to the hard ceiling of 536870912 (512 MiB) are accepted. |
maxImages |
8 |
Images in one active draft; hard ceiling 32. |
maxImageBytes |
10485760 (10 MiB) |
Source bytes per image; hard ceiling 52428800 (50 MiB). Must not exceed maxBatchBytes. |
maxBatchBytes |
41943040 (40 MiB) |
Combined source/processed draft bytes; hard ceiling 209715200 (200 MiB). |
maxImagePixels |
50000000 |
Decoded pixels per image; hard ceiling 100000000. Animated-image frame area participates in this limit. |
A missing file uses defaults. The file must contain a top-level JSON object; recognized booleans and positive integer limits must have the documented types and remain within their hard ceilings. Malformed JSON or an invalid recognized value causes the file to be ignored with a warning and leaves it untouched. Unknown fields are accepted and preserved by the settings screen for forward compatibility. The settings screen exposes only Start WebUI automatically, with the user-facing values Manual and Every session. Changes save immediately; Escape closes or returns and does not roll back changes that already succeeded. The startup behavior applies on the next session initialization or /reload and does not start or stop the current server. There is not yet user-testing evidence that maxImages is adjusted often enough to justify another routine row. Retention and image-limit fields remain in Advanced JSON at the reported path.
If the settings file is malformed or contains an invalid recognized value, the menu presents a read-only Repair settings file flow. Safe defaults remain active, the original bytes stay untouched, and settings writes remain paused until the file is repaired and Pi is reloaded.
Advanced image limits
Omitting all four image-limit fields exactly reproduces the original 8 image / 10 MiB per image / 40 MiB batch / 50 megapixel behavior. Values are byte counts, not Base64 character counts. Raising any image limit above its safe default emits one concise session-start warning and can materially increase Pi-process memory, decoder work, and denial-of-service exposure; use the smallest value that solves the current workflow. Any non-integer, non-positive, above-ceiling, or cross-field-invalid recognized value rejects the whole file and safely restores all defaults without rewriting it. /webui status reports the effective limits and whether they came from defaults or the settings file.
Settings are reloaded on every session_start. Within one Pi process, changes made in /webui settings run in action order, reread the latest valid document, preserve concurrent unknown-field edits, and save atomically. Saves and /webui init also serialize supported Pi writers through pi-webui.json.mutation-lock from the latest read through same-directory temporary-file rename. Editors and older extension versions do not participate in that lock, so avoid manual edits while a settings save is in progress. Malformed or invalid concurrent edits block publication without replacement, and a failed save rolls the displayed and effective value back. A successful save updates the in-memory preference immediately, but intentionally does not start or stop the server in the current session; it takes effect at the next session initialization or /reload. /webui init creates formatted defaults once and refuses to overwrite valid or invalid existing content.
In RPC mode, /webui uses the client's observable selection dialogs without opening custom TUI. In print and JSON modes, bare /webui and /webui open remain side-effect free because those modes cannot safely expose an interactive menu or bootstrap link. /webui settings does not open custom TUI or write protocol-breaking output outside TUI mode. Use an interactive TUI/RPC client for the menu and link, or use /webui status, /webui help, and the reported path where notifications are available.
🔄 What synchronization means
WebUI mirrors Pi's semantic session events, not terminal pixels. It displays conversation content, streaming assistant state, tool calls/results, errors, and activity using browser-native presentation. It does not reproduce ANSI colors, terminal wrapping, footer/widgets, built-in dialogs, arbitrary custom TUI components, or unsent terminal editor text.
The initial transcript comes from the active session branch. Unsent browser message text and ordered attachment references are authoritative in the live Pi process, so refresh, reconnect, and active-tab takeover restore the same draft without creating a second transcript or altering Pi's session tree. Text edits are revisioned and saved with bounded, deduplicated mutations; stale or delayed responses cannot overwrite newer typing.
🖼️ Images
| Input | Provider-ready output |
|---|---|
| PNG | PNG |
| JPEG | JPEG |
| WebP | WebP |
| GIF, including animation | GIF |
| BMP | PNG |
| TIFF | PNG |
| HEIC/HEIF | PNG |
| AVIF | PNG |
The server checks file signatures instead of trusting browser MIME types or filename extensions. It rejects corrupt/unknown formats and applies the effective maxImages, maxImageBytes, maxBatchBytes, and maxImagePixels settings at browser admission, streamed upload, processing, draft accounting, send preflight, and Attach again. Pi's approximately 4.5 MB inline Base64 constraint and 2,000-pixel provider-ready dimension constraint remain fixed and cannot be raised in WebUI settings. Images over 2,000 pixels on either side are resized when Pi's images.autoResize setting is enabled and rejected when it is disabled.
Choosing, pasting, or dropping images first reserves an ordered server-side batch, uploads each source as a bounded raw request, and processes it before Send becomes available. Each thumbnail reports Uploading, Processing, Ready, or Needs attention; upload progress is shown when the browser reports a byte total. A failed item can be retried without reselecting successful siblings, and every item can be removed independently. Refreshing the active tab restores the authoritative staged batch, while another tab taking the editing lease cancels in-flight work safely.
When a draft has two or more images, each card shows its order and can be dragged into place; keyboard users can focus a card and press Alt+↑ or Alt+↓. A single image omits meaningless ordering interaction. Remove images individually with each card's trash button, or use the confirmed Clear attachments action when a draft contains several images. The collection summary shows the attached count against the effective maximum and states the metadata-removal guarantee once, while each card keeps its own upload/processing/ready/error state and any conversion or resize summary. Processing applies image orientation, re-encodes provider-ready output, strips EXIF and other private metadata, preserves ICC color profiles and animated GIF timing where supported, and releases each source after successful sanitation. Failed processing retains only that bounded source for Retry; Ready provider bytes remain in the Pi process until accepted send, removal, clear, lease/session teardown, or failed-send retry. Image bytes are never embedded in the message JSON protocol.
Pi's effective global and trusted-project images.autoResize and images.blockImages settings plus the current model's image capability and authentication are checked again at send time. A failed send leaves the exact Ready batch available for an idempotent retry. BMP and HEIC use bounded portable decoders because the prebuilt sharp/libvips distribution does not decode those inputs consistently across supported platforms.
When retainSentImages is enabled, only provider-ready sanitized bytes transfer into a separate session-memory store after Pi accepts the matching browser message. Content-identical sanitized images share one opaque session reference. Oldest entries are evicted first when either retention ceiling is exceeded. WebUI also reconciles retained, current-draft, and conservative in-flight processing bytes against one aggregate resident-image budget (the larger of the configured retention byte ceiling and the staging store's maximum working set), evicting sent entries before that aggregate can grow. Eligible transcript image chips offer Attach again and Forget; evicted or forgotten references read Expired, and terminal-origin images never gain those actions. Attach again clones the retained bytes into the current authoritative draft, reuses normal count/byte admission, and never mutates the earlier message. Refresh and active-tab takeover recover eligibility from Pi-process state; session replacement, reload, shutdown, or process exit releases all retained bytes.
🔐 Security and privacy
- The server binds only to a random
127.0.0.1port and is owned by one live Pi session. - A rotating bootstrap token is exchanged once for a per-server HttpOnly,
SameSite=Strictcookie and removed from the URL. - Every endpoint requires the cookie. Mutations also require exact Host and Origin values plus the active browser-tab lease.
- Responses use no-store, no-referrer, MIME-sniffing, frame-denial, same-origin resource, and restrictive Content Security Policy headers.
- Transcript projection retains at most the newest 500 messages and 500 tool records, event replay keeps 256 updates, and request-id records keep 128 sends. Unsent message text, ordered attachment references, staged image bytes, and opt-in sanitized sent-image bytes live only in bounded Pi-process memory under their documented lifecycles; the page uses no localStorage, sessionStorage, IndexedDB, script-readable cookies, or image/transcript cache.
- Tool arguments/results, paths, images, and model thinking can be sensitive. Thinking is collapsed by default; only open a link issued by a Pi process you trust.
- Reload, session replacement/fork, or Pi shutdown closes sockets, invalidates old callbacks, ends the page, and releases in-memory state.
A loopback page is local to the operating-system network namespace. WebUI does not support LAN/public binding or a cloud relay. For SSH, containers, or devcontainers, forward the exact printed port and preserve the 127.0.0.1:<port> Host value.
♿ Accessibility and browsers
The terminal menu uses Pi's configured selection keybindings, textual state that does not depend on color, stable focus when returning from a secondary screen, and width-bounded wrapping. Pi's terminal accessibility remains subject to terminal and screen-reader capabilities.
The browser page uses semantic headings, native disclosure/dialog controls, concise status/alert live regions, accessible labels, visible keyboard focus, at least 44 px controls, keyboard image preview/removal/reordering, Ctrl/Command+Enter submission, reduced-motion handling, dark mode, and responsive reflow. It targets current stable desktop Chrome, Edge, Firefox, and Safari.
🚧 Limitations
- One active Pi session and one active browser editing tab only.
- No persistent browser transcript, permanent sent-image gallery, cross-session image history, remote access, PTY/terminal control, model/settings controls, or session switching.
- No SVG, remote image URL, OCR, annotation, or directory upload support.
- Browser acknowledgment means Pi accepted or queued the message. Provider failures follow Pi's normal session/retry behavior.
- Built-in TUI commands and dialogs are not reimplemented in the browser.
🗂️ Package layout
src/index.ts Pi package entrypoint
src/webui.ts extension registration and command orchestration
src/runtime.ts Pi lifecycle, commands, menu orchestration, event projection, and browser routing
src/menu.ts responsive current-state menu and read-only detail presentation
src/settings.ts global WebUI settings validation and atomic persistence
src/conversation.ts bounded transcript snapshot and ordered event replay
src/drafts.ts authoritative in-memory text and attachment-reference revisions
src/attachments.ts revisioned staged-image state, processing queue, and byte ownership
src/sent-images.ts opt-in bounded sanitized sent-image retention
src/server.ts authenticated loopback HTTP/SSE server and raw attachment protocol
src/image-limits.ts shared configurable defaults, ceilings, and provider constraints
src/images.ts bounded provider-ready image processing
src/pi-settings.ts effective Pi image settings reader
src/web/ui/ React source using Radix UI and the browser protocol client
src/web/app.js generated, bundled browser application
src/web/app.css generated Radix Themes, Colors, and local presentation styles
src/web/index.html minimal authenticated browser shell
🧪 Development
From the repository root:
npm --workspace @narumitw/pi-webui run build:web
npm --workspace @narumitw/pi-webui run check
npm test
just try webui
just pack webui
Edit browser code under src/web/ui/, then run build:web; check:web (included in the package typecheck) rejects stale generated assets. The package preview must contain its manifest, license, README, TypeScript and browser source, and bundled static assets, but no tests, fixtures, cache, build scripts, or node_modules.
🔎 Keywords
Pi extension, Pi Coding Agent, browser companion, local web chat, terminal session sync, Server-Sent Events, image prompt, tool activity, local-first AI coding agent.
📄 License
MIT