Web-based remote interface for pi, the terminal coding agent
Project description
Piface
A web-based remote interface for pi, a terminal coding agent. Piface wraps one or more pi --mode rpc subprocesses and exposes them through a FastAPI + WebSocket backend and a Svelte web frontend — letting you use pi from any browser over VPN/Tailscale.
TL;DR
# 1. Install pi (the underlying agent — required, npm-based)
npm install -g @mariozechner/pi-coding-agent
# 2. Run piface (no install needed — uvx fetches and runs the latest)
uvx piface
# Optional: explicit subcommand form
uvx piface serve
# Optional: text-to-speech for assistant messages
uvx --with 'piface[tts]' piface
# macOS: use Python 3.12 explicitly; TTS also needs espeak-ng
brew install espeak-ng # only needed for text-to-speech
uvx --python 3.12 piface
uvx --python 3.12 --with 'piface[tts]' piface
# Upgrade to the newest piface release
uvx --refresh piface
# Or, if you've installed it persistently with `uv tool install piface`:
uv tool upgrade piface
Note: the
[speech]and[audio]extras are not installable from PyPI right now because an upstream dependency (lightning, required bynemo-toolkit) was quarantined on PyPI on 2026-04-30 after a supply-chain attack on versions 2.6.2/2.6.3. The[tts]extra works fine from PyPI.Workaround for git-clone installs:
uv sync --extra audioworks today becausepyproject.tomloverrideslightningvia[tool.uv.sources]to pull2.4.0straight from the official GitHub tag (well before the compromise). The override is uv-only and is stripped from PyPI wheels.
Features
- Multi-session management — run multiple pi instances in parallel across different project directories
- Real-time streaming — WebSocket relay of pi events with live message updates
- Full reconnect/replay — reconnect to any session and get complete history via
get_messagesRPC - Large-history replay resilience — backend pi stream buffer is sized for large replay payloads (up to 64 MiB per JSON line) to avoid replay hangs on long sessions
- Mobile Safari resilience — session view auto-reconnects on tab return (
visibilitychange/pageshow/online) with throttled lifecycle reconnect attempts, restores short-lived snapshots fromsessionStoragewithlocalStoragefallback, and only applies replay payloads when chat history actually changed - Completion attention cues — when pi finishes a turn, the session plays a short ding; if the tab is backgrounded, the document title appends
(awaiting instructions)until you return - Extension UI support — modal dialogs for pi extension UI requests (
select,confirm,input,editor); attention badges when no browser is watching - Session-scoped file attachments — attach one or more files (images, audio, text docs, etc.) in the session composer; files are stored under pi's session directory and referenced in prompts via local paths
- Voice transcription in composer — two mic actions in live sessions:
Mic → Text(speech-to-text only) andMic → Clean(speech-to-text followed by a low-thinking pi cleanup pass). Both append text into the composer without auto-sending. - Assistant text-to-speech playback — every assistant message exposes a compact
Listentrigger that expands into an inline mini-player withPlay/Pause, progress,Restart, and adaptive rewind controls; Coqui TTS generates cached MP3 audio on the server and the browser reuses immutable audio URLs from cache after first playback. - Composer bash shortcuts —
!commandruns via pi RPC bash (included in next-model context),!!commandruns locally in piface and is excluded from model context - Composer slash shortcuts —
/compact [instructions]maps to pi RPC compaction,/copycopies the latest assistant message locally, and/reloadruns a compatibility restart flow (kill + resume) with an explicit warning that semantics differ from native pi/reload - Session persistence — on restart, piface auto-resumes all previously-live sessions (ephemeral sessions excluded)
- Archived session viewing + resume — closed sessions open in read-only mode from persisted
session_file, and can be resumed back to live mode on demand - Remote access — bind to
0.0.0.0by default; access over Tailscale/VPN with no extra auth needed - Directory browser — server-side directory listing API with autocomplete for the new-session form
- Session file viewer tab — browse project files from each session, with syntax-highlighted code/text preview, Markdown + MathJax + Mermaid rendering, image preview, HTML iframe rendering for files within size limits, a fullscreen preview toggle, and a top-right source-copy button (with path fallback); Mermaid fences render inline with a collapsible source block, and markdown links/images are resolved from the current document path so relative references work
- Session git diff tab — inspect per-session repository status and side-by-side git diffs for changed files (including untracked files)
- Session scratch tab — per-session scratchpad editor stored in browser local storage and auto-saved after a short typing pause, with automatic reload on page refresh
- Refresh-safe session view restore — the session page remembers the selected tab, chat scroll position, and any unsent composer draft across refreshes/tab switches, using URL-backed tab state and persisted per-session browser view state
- Per-session chat Markdown rendering — each session has a
Render Markdowntoggle for assistant outputs (default: on), including MathJax rendering for LaTeX-style math expressions and Mermaid diagram rendering for fencedmermaidblocks with collapsible source - Theme + skin modes — light/dark support with
auto(system), plus dashboard skin presets (default,beos,facebook2007,nintendo,nintendo-nes,windows98,macos8,winamp,protoss,terran,zerg,synthwave) persisted in browser storage, and optional per-workspace session visual overrides (theme mode + skin) configurable in Workspace Config to visually distinguish workspaces at a glance
Architecture
Browser (Svelte)
↕ WebSocket (JSON)
FastAPI / piface server
↕ asyncio subprocess pipes (stdin/stdout)
pi --mode rpc (N processes)
↕ filesystem
~/.pi/sessions/*.jsonl
Backend (piface/)
| Module | Responsibility |
|---|---|
cli.py |
CLI entry point via typer |
server.py |
FastAPI app factory, startup/shutdown lifecycle, static file serving |
session_manager.py |
Owns all PiSession instances; handles spawn, kill, restart-on-boot |
pi_session.py |
Wraps a single pi --mode rpc subprocess with asyncio stream reading/writing |
rpc_types.py |
Pydantic models for all pi RPC commands and events |
state.py |
Persistent JSON state file (session metadata, status) |
speech.py |
Parakeet speech-to-text utilities |
tts.py |
Coqui text-to-speech synthesis + cache |
platform_dirs.py |
Platform-appropriate data directory resolution |
build.py |
Auto-build Svelte frontend if frontend/dist is stale |
routes/sessions.py |
REST endpoints: list/create/kill sessions, session uploads/history, and filesystem browsing/preview APIs |
routes/ws.py |
WebSocket endpoint: /ws/sessions/{id} — relays pi events to browser, forwards commands to pi |
Frontend (frontend/)
Svelte/SvelteKit app with two main pages:
- Dashboard (
/) — session list grouped by working directory, sorted by last active time. Each card shows session name, model, status, and actions (kill for live, resume for resumable archived sessions, both with confirmation prompts). Session name links open in a new browser tab. Per-directory pagination shows 10 sessions at a time with Back/Forward controls when needed. Includes a theme selector (auto/light/dark) and skin selector (default/beos/facebook2007/nintendo/nintendo-nes/windows98/macos8/winamp/protoss/terran/zerg/synthwave) persisted per browser. The per-directory Workspace Config dialog (gear icon) supports optional session theme mode overrides (none/light/dark) and skin overrides (none/any skin preset) for workspace identity cues. Red attention icon when an extension UI dialog is pending. - Session view (
/session/:id) — tabbed UI with Chat (message feed, thinking/tool-call blocks, file attachments, thinking-level control, per-sessionRender Markdowntoggle for assistant output, live working indicator, per-tool progress rows during execution, completion attention cues, dual mic buttons for speech transcription, per-assistant-message Coqui TTS playback controls, an auto-growing composer capped at 40% viewport height, persisted chat scroll restoration after refresh, and automatic recovery of any unsent composer draft), Files (directory browser + file preview that remembers the last opened file/path and fullscreen-preview preference within the browser session), Diff (repository status + side-by-side git patch viewer), Terminal (embedded terminal), and Scratch (per-session browser-persisted notes with debounce autosave). The selected session tab is also mirrored into the page URL (?tab=...) so refreshes/bookmarks reopen the same view. On mobile, focusing the composer enters a fullscreen compose mode with an explicit Reference chat exit action plus a Fullscreen re-entry action in normal mode. File previews support syntax-highlighted code/text, rendered Markdown with MathJax math and Mermaid diagrams (including relative path resolution for links/images plus collapsible Mermaid source blocks), image display, HTML iframe rendering, a preview-toolbar Fullscreen toggle that hides the file browser while keeping the current file open, and a preview-toolbar Copy source button that copies opened file source for code/markdown files, then falls back to relative/absolute path variants when source text is unavailable. Live sessions include header actions to create a new session from the current session defaults (including working directory), branch, and close (kill + return to dashboard, via an in-app confirmation modal). Closed sessions automatically open chat in archived read-only mode and can be resumed from the header (with confirmation); TTS playback remains available for archived assistant messages because synthesis only depends on the visible text. When a workspace config setsui.theme_overrideand/orui.skin_override, session view enforces those overrides for quick workspace recognition.
Requirements
- Python 3.12.x
- uv (Python project/package manager)
- Node.js + pnpm (for building the frontend)
- pi installed and available on
PATH ffmpeginstalled onPATH(required for browser-recorded audio conversion)- For speech-to-text only: install
uv sync --extra speech - For text-to-speech only: install
uv sync --extra tts- Piface uses the maintained
coqui-ttspackage; the olderTTSpackage only supports Python<3.12
- Piface uses the maintained
- For both audio features together: install
uv sync --extra audio - A CUDA-capable GPU is optional but improves both Parakeet transcription and Coqui TTS latency
Installation
# Clone and set up
git clone <repo-url> piface && cd piface
uv sync
# Optional: install speech-to-text stack (Parakeet v3 + torch)
uv sync --extra speech
# Optional: install text-to-speech stack (Coqui TTS + torch)
uv sync --extra tts
# Optional: install both audio stacks together
uv sync --extra audio
Note: Piface is now pinned to Python 3.12.x. Coqui TTS support comes from the maintained
coqui-ttspackage; the olderTTSpackage only supports Python<3.12. The audio stacks also still depend on packages (for examplekaldialign) that do not publish Python 3.14 wheels yet.
Usage
Start the server
uv run piface
Equivalent explicit form:
uv run piface serve
Default: binds to 0.0.0.0:7832. On first run (or when frontend source changes), the Svelte app is auto-built.
Tailscale HTTPS for microphone input
Browser microphone APIs require a secure context. localhost works locally, but remote phones/tablets should use HTTPS. Tailscale Serve is the easiest way to put Piface behind a tailnet-only HTTPS URL:
# Git-clone/dev install: install the speech stack first if you want Mic → Text / Mic → Clean
uv sync --extra speech
# Run Piface on the machine that will do transcription
uv run piface serve --host 127.0.0.1 --port 7832
# Or, without a checkout, run the published package with the speech extra
uvx --python 3.12 --with 'piface[speech]' piface serve --host 127.0.0.1 --port 7832
# In another terminal, publish it over tailnet HTTPS
tailscale serve --bg 7832
# Show the HTTPS URL, e.g. https://<machine>.<tailnet>.ts.net
tailscale serve status
Open the reported https://...ts.net URL from a device on your tailnet, then grant microphone permission in the browser.
CLI options
piface [OPTIONS]
piface serve [OPTIONS]
Options:
--port PORT Server port (default: 7832)
--host HOST Bind address (default: 0.0.0.0)
--dev Proxy frontend to Svelte dev server at localhost:5173 (skip auto-build)
--direnv / --no-direnv Enable/disable direnv for spawned sessions
--speech / --no-speech Enable/disable voice transcription endpoints
--speech-model TEXT Speech model id (default: nvidia/parakeet-tdt-0.6b-v3)
--speech-gpu / --speech-cpu Prefer GPU or force CPU for speech transcription
--tts / --no-tts Enable/disable assistant text-to-speech endpoints
--tts-model TEXT Coqui TTS model id (default: tts_models/en/ljspeech/vits)
--tts-gpu / --tts-cpu Prefer GPU or force CPU for assistant text-to-speech
piface with no subcommand is an alias for piface serve.
Development mode
Run the Svelte dev server and piface backend separately for hot-reload:
# Terminal 1: Svelte dev server (must match piface's dev proxy target)
cd frontend && pnpm dev --host 127.0.0.1 --port 5173 --strictPort
# Terminal 2: piface backend in dev mode
uv run piface serve --dev
In --dev mode, all non-API requests are reverse-proxied to http://127.0.0.1:5173.
GTX 1080 Ti audio pinning helpers
If you need to keep another GPU (for example an RTX 4090) free for training work, use these Makefile helpers:
make install-torch-cu126 # one-time: install Torch + torchaudio cu126 builds (supports GTX 1080 Ti sm_61)
make serve-audio-1080 # production mode, STT + TTS pinned to the 1080 Ti
make dev-audio-1080 # foreground dev mode, STT + TTS pinned to the 1080 Ti
make background-dev-audio-1080 # background dev mode, STT + TTS pinned to the 1080 Ti
Legacy *-speech-1080 targets remain as aliases. If no GTX 1080 Ti is detected, these targets fail fast with an actionable error. If you recreate/sync the virtualenv later, re-run make install-torch-cu126 before using the audio pinning targets.
Session Lifecycle
- Create — user fills out the new-session form (working dir, model, provider, thinking level, session name, ephemeral flag) →
POST /api/sessions→ piface spawnspi --mode rpcin the given directory (viadirenv exec <working_dir> ...whendirenvis installed) and the UI immediately navigates to/session/{id}for the new session. If provider/model/thinking are omitted, defaults areopenai-codex,gpt-5.5, andmedium. On server startup, piface warms the provider/model list by starting a temporarypi --mode rpc --no-sessionprobe, so the new-session dropdown can be populated before any real session exists; when available, the UI selects the supportedgpt-5.5Codex model by default. In the web UI, new sessions default to Shared mode, so they run in the original directory unless you explicitly opt into a worktree-backed workspace. The UI exposes pi's full documented thinking-level set, includingxhighwhen the selected model supports it. Direnv launching is on by default and can be disabled globally withpiface serve --no-direnvor per session withuse_direnv: falseinPOST /api/sessions. If the project's.piface/workspace.tomlsets[ui].theme_overrideand/or[ui].skin_override, session view uses those overrides for worktree sessions. If pi exits immediately during startup (for exampledirenvblocked by an unapproved.envrc), the API returns409with the startup error text instead of creating a dead session; the UI offers an allow-and-retry flow. Assistant model/API errors are rendered in chat asModel request failed: ...instead of an empty assistant bubble. - Active — pi subprocess runs; piface reads stdout events and broadcasts to subscribed WebSocket clients; browser commands are forwarded to pi's stdin
- Kill —
POST /api/sessions/{id}/kill→ sends abort, terminates process, marks session as closed - Restart — on piface restart, all previously-live sessions are re-spawned with the same session directory so pi loads existing history; ephemeral (
--no-session) sessions are dropped - Stale-live reconciliation — if persisted metadata says a session is
livebut no live subprocess exists (for example after an unexpected process death), piface auto-reconciles it toclosedon read so the UI falls back to archived history instead of showing an empty live chat. - Archived view — closed sessions are viewable from
/session/{id}in read-only mode; history is loaded from the saved pisession_filevia REST (no pi subprocess needed). Session-file paths are normalized (including~expansion) for compatibility with older/alternate pi path formats. - Resume archived session —
POST /api/sessions/{id}/resumerespawns pi for that record (using--session <path>when available), flips status back tolive, and re-enables WebSocket chat. If startup fails immediately, resume returns409with the startup error text; the UI can approve.envrcand retry. - Branch —
POST /api/sessions/{id}/branchforks from a selected user-message entry into a brand-new live session, preserving lineage (parent_piface_id,branch_entry_id) and inheriting upload references. In the chat UI, per-message “Branch from here” actions are hidden by default and toggled withB.
File Attachments
Piface supports session-scoped file attachments from the session composer (file picker, drag/drop, and clipboard paste).
- Upload endpoint:
POST /api/sessions/{id}/uploads - Storage path (shared blob store):
<pi-session-dir>/uploads/_shared/...- Example:
~/.pi/agent/sessions/--home-user-project--/uploads/_shared/...
- Example:
- Prompt behavior: for
prompt/steer/follow_up, browser can includepiface_upload_ids; piface resolves them and appends a path manifest to the message text before forwarding to pi - Session-file requirement: uploads are accepted only after
session_fileis known (typically after the first prompt) - Branching behavior: branched sessions inherit upload references from the source session
- Cleanup: uploads are ref-counted by session; blobs are deleted only when no sessions reference them
Audio Features
Voice Transcription
Piface exposes POST /api/sessions/{id}/transcribe for live sessions.
- Input: base64-encoded browser-recorded audio (
audio_base64,mime_type) - Flow:
- server converts audio to 16kHz mono WAV via
ffmpeg - Parakeet v3 (
nvidia/parakeet-tdt-0.6b-v3) transcribes speech → text - optional cleanup pass (
clean_with_pi: true) runs a short-livedpi --thinking low --no-sessionprompt to polish transcript wording
- server converts audio to 16kHz mono WAV via
- Output:
{ text, raw_text, cleaned }
In the chat composer, two mic buttons map to this endpoint:
- Mic → Text: plain transcription
- Mic → Clean: transcription + cleanup pass
On iOS/iPadOS browsers, the client prefers audio/mp4, records chunked slices, and applies a short stop-delay flush to reduce empty-audio captures.
Both modes append text to the current composer input and leave final send under user control.
Assistant Text-to-Speech
Piface exposes two endpoints for assistant-message playback:
POST /api/sessions/{id}/tts— ensure a Coqui-generated MP3 exists for a message and return a stable content-addressed audio URLGET /api/audio/tts/{cache_key}.mp3— serve the cached MP3 with long-lived immutable browser-cache headers
Flow:
- Browser user clicks
Listenon an assistant message - Backend normalizes the visible assistant text and hashes
(model_name, text)into a cache key CoquiTtsSynthesizerloadstts_models/en/ljspeech/vitson demand, synthesizes a temporary WAV on the configured device (GPU by default when available), then transcodes it to MP3 for playback- MP3 is cached on disk under Piface's data directory (
tts-cache/), then served through a content-addressed URL - Browser audio playback exposes a per-message
Listentrigger plus an inline mini-player withPlay/Pause, progress,↺ Restart, and adaptive-10s/-15srewind controls; repeated playback reuses the immutable URL from browser cache after the first fetch
Archived sessions are supported too, because TTS only needs the assistant text already visible in the browser.
WebSocket Protocol
/ws/sessions/{id} is for live sessions only. Closed sessions are rendered from archived history over REST.
Browser → Piface
- Any pi RPC command object (forwarded directly to pi's stdin)
{"type": "piface_get_history"}— request full message replay- For
prompt/steer/follow_up, an optionalpiface_upload_ids: string[]can be included; piface rewrites these into a session-local file path manifest inmessage - For
prompt/steer/follow_up,messagevalues that start with!are treated as bash shortcuts:!command→ rewritten to RPC{type:"bash",command:"..."}!!command→ executed locally by piface and returned as a bash-style response withdata.excludeFromContext = truepiface_upload_idscannot be combined with!/!!
- In the session composer UI, selected slash commands are intercepted before WebSocket send:
/compact [instructions]→ sent as RPC{type:"compact",customInstructions?:"..."}/copy→ handled locally in the browser (copies latest assistant text to clipboard)/reload→ shows a warning dialog about semantic differences, then performs a compatibility restart (POST /api/sessions/{id}/killfollowed byPOST /api/sessions/{id}/resume)
Piface → Browser
- Pi event objects (forwarded directly from pi's stdout)
{"type": "piface_replay", "messages": [...]}— full history on connect{"type": "piface_session_state", "session": {...}}— metadata update{"type": "piface_error", "message": "..."}— server-level error{"type": "piface_attention_required", "session_id": "...", "request": {...}}— extension UI dialog pending (broadcast to dashboard)
REST API
| Method | Path | Description |
|---|---|---|
GET |
/api/sessions |
List all sessions |
POST |
/api/sessions |
Create a new session |
POST |
/api/direnv/allow |
Approve a directory .envrc in the server's direnv environment |
GET |
/api/workspace/config?path=... |
Load effective workspace config (.piface/workspace.toml) for a project directory |
PUT |
/api/workspace/config |
Save workspace config for a project directory |
GET |
/api/sessions/{id} |
Get one session record |
PATCH |
/api/sessions/{id} |
Update session metadata (currently render_markdown) |
GET |
/api/sessions/{id}/history |
Get archived history from the session file |
GET |
/api/sessions/{id}/fork-messages |
List branchable user-message entry IDs/text |
POST |
/api/sessions/{id}/branch |
Create a new branched live session from a selected entry |
POST |
/api/sessions/{id}/uploads |
Upload one or more files for that session |
POST |
/api/sessions/{id}/transcribe |
Transcribe browser-recorded audio (optional pi cleanup pass) |
POST |
/api/sessions/{id}/tts |
Prepare cached Coqui TTS audio for an assistant message |
GET |
/api/audio/tts/{cache_key}.mp3 |
Stream cached assistant TTS audio with immutable cache headers |
POST |
/api/sessions/{id}/kill |
Kill a session |
POST |
/api/sessions/{id}/resume |
Resume a closed non-ephemeral session |
GET |
/api/sessions/{id}/git/status |
Get repository status for the session working directory |
GET |
/api/sessions/{id}/git/diff?path=...&staged=... |
Get unified diff text for a changed file (side-by-side rendered in UI) |
GET |
/api/fs/list?path=... |
List directory entries |
GET |
/api/fs/complete?prefix=... |
Autocomplete directory paths |
GET |
/api/fs/file?path=... |
Get file preview metadata + text/markdown content (size-limited) |
GET |
/api/fs/raw?path=... |
Stream raw file content for iframe/image previews (size-limited) |
File Preview Limits
- Text/Markdown preview (
/api/fs/file): up to 512 KiB - Raw preview (
/api/fs/raw, used by image/HTML viewers): up to 8 MiB
Larger files return HTTP 413 so the UI can avoid loading oversized content.
State Storage
Session metadata is persisted as JSON in a stable platform path:
- Linux:
~/.local/share/piface/state.json - macOS:
~/Library/Application Support/piface/state.json
On Linux, piface intentionally keeps this path stable even when launch environments override XDG_DATA_HOME (for example sandboxed/snap terminals). On startup, piface also merges legacy state files it finds in older environment-specific locations (including snap revision directories) into the canonical path.
This tracks session IDs, lineage metadata (parent_piface_id, branch_entry_id), working directories, model config, per-session chat render preferences (like render_markdown), status, session file paths, and shared upload reference indexes — enabling auto-restart of live sessions across piface restarts and safe shared-upload cleanup.
Dependencies
Python
- fastapi, uvicorn[standard], websockets, httpx, pydantic, typer, platformdirs, anyio
- optional speech extra: nemo-toolkit[asr], torch
- optional tts extra: coqui-tts, torch, torchaudio, torchcodec
- optional audio extra: nemo-toolkit[asr], coqui-tts, torch, torchaudio, torchcodec
Frontend (pnpm)
- svelte, @sveltejs/kit, vite, marked, mermaid, dompurify, highlight.js, diff2html
- MathJax v3 loaded via CDN in
frontend/src/app.htmlfor client-side TeX rendering
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file piface-0.0.4.tar.gz.
File metadata
- Download URL: piface-0.0.4.tar.gz
- Upload date:
- Size: 3.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8219fc428b6c222214af81763143bf1bac199cfc3f83d9bbe9e5eddd036204c9
|
|
| MD5 |
f1a754bb8728280bf9b10ceb46f94f0f
|
|
| BLAKE2b-256 |
37a08a624942bd9b3b44d3323d923b8cb972cdb2f489b3cea7d414fa0755c758
|
File details
Details for the file piface-0.0.4-py3-none-any.whl.
File metadata
- Download URL: piface-0.0.4-py3-none-any.whl
- Upload date:
- Size: 1.9 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fcbef0e9bb99a75d03ac14be3466b41650b9418eeed6ec605500943615144257
|
|
| MD5 |
87e4fbbb5933b291ad3066851769c65e
|
|
| BLAKE2b-256 |
45a12e7d2156834bc594347f0dbd3c134532d8fdbd8786f521c809fe24987a97
|