Skip to main content

Web-based remote interface for pi, the terminal coding agent

Project description

Piface

Piface Logo

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

# 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 by nemo-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 audio works today because pyproject.toml overrides lightning via [tool.uv.sources] to pull 2.4.0 straight 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_messages RPC
  • 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 from sessionStorage with localStorage fallback, 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) and Mic → 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 Listen trigger that expands into an inline mini-player with Play / 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!command runs via pi RPC bash (included in next-model context), !!command runs locally in piface and is excluded from model context
  • Composer slash shortcuts/compact [instructions] maps to pi RPC compaction, /copy copies the latest assistant message locally, and /reload runs 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.0 by 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 Markdown toggle for assistant outputs (default: on), including MathJax rendering for LaTeX-style math expressions and Mermaid diagram rendering for fenced mermaid blocks 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-session Render Markdown toggle 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 sets ui.theme_override and/or ui.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
  • ffmpeg installed on PATH (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-tts package; the older TTS package only supports Python <3.12
  • 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-tts package; the older TTS package only supports Python <3.12. The audio stacks also still depend on packages (for example kaldialign) 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.

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

  1. Create — user fills out the new-session form (working dir, model, provider, thinking level, session name, ephemeral flag) → POST /api/sessions → piface spawns pi --mode rpc in the given directory (via direnv exec <working_dir> ... when direnv is installed) and the UI immediately navigates to /session/{id} for the new session. If provider/model/thinking are omitted, defaults are openai-codex, gpt-5.5, and medium. On server startup, piface warms the provider/model list by starting a temporary pi --mode rpc --no-session probe, so the new-session dropdown can be populated before any real session exists; when available, the UI selects the supported gpt-5.5 Codex 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, including xhigh when the selected model supports it. Direnv launching is on by default and can be disabled globally with piface serve --no-direnv or per session with use_direnv: false in POST /api/sessions. If the project's .piface/workspace.toml sets [ui].theme_override and/or [ui].skin_override, session view uses those overrides for worktree sessions. If pi exits immediately during startup (for example direnv blocked by an unapproved .envrc), the API returns 409 with 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 as Model request failed: ... instead of an empty assistant bubble.
  2. Active — pi subprocess runs; piface reads stdout events and broadcasts to subscribed WebSocket clients; browser commands are forwarded to pi's stdin
  3. KillPOST /api/sessions/{id}/kill → sends abort, terminates process, marks session as closed
  4. 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
  5. Stale-live reconciliation — if persisted metadata says a session is live but no live subprocess exists (for example after an unexpected process death), piface auto-reconciles it to closed on read so the UI falls back to archived history instead of showing an empty live chat.
  6. Archived view — closed sessions are viewable from /session/{id} in read-only mode; history is loaded from the saved pi session_file via REST (no pi subprocess needed). Session-file paths are normalized (including ~ expansion) for compatibility with older/alternate pi path formats.
  7. Resume archived sessionPOST /api/sessions/{id}/resume respawns pi for that record (using --session <path> when available), flips status back to live, and re-enables WebSocket chat. If startup fails immediately, resume returns 409 with the startup error text; the UI can approve .envrc and retry.
  8. BranchPOST /api/sessions/{id}/branch forks 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 with B.

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/...
  • Prompt behavior: for prompt/steer/follow_up, browser can include piface_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_file is 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:
    1. server converts audio to 16kHz mono WAV via ffmpeg
    2. Parakeet v3 (nvidia/parakeet-tdt-0.6b-v3) transcribes speech → text
    3. optional cleanup pass (clean_with_pi: true) runs a short-lived pi --thinking low --no-session prompt to polish transcript wording
  • 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 URL
  • GET /api/audio/tts/{cache_key}.mp3 — serve the cached MP3 with long-lived immutable browser-cache headers

Flow:

  1. Browser user clicks Listen on an assistant message
  2. Backend normalizes the visible assistant text and hashes (model_name, text) into a cache key
  3. CoquiTtsSynthesizer loads tts_models/en/ljspeech/vits on demand, synthesizes a temporary WAV on the configured device (GPU by default when available), then transcodes it to MP3 for playback
  4. MP3 is cached on disk under Piface's data directory (tts-cache/), then served through a content-addressed URL
  5. Browser audio playback exposes a per-message Listen trigger plus an inline mini-player with Play/Pause, progress, ↺ Restart, and adaptive -10s/-15s rewind 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 optional piface_upload_ids: string[] can be included; piface rewrites these into a session-local file path manifest in message
  • For prompt/steer/follow_up, message values 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 with data.excludeFromContext = true
    • piface_upload_ids cannot 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}/kill followed by POST /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.html for 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

piface-0.0.3.tar.gz (3.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

piface-0.0.3-py3-none-any.whl (1.9 MB view details)

Uploaded Python 3

File details

Details for the file piface-0.0.3.tar.gz.

File metadata

  • Download URL: piface-0.0.3.tar.gz
  • Upload date:
  • Size: 3.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.5

File hashes

Hashes for piface-0.0.3.tar.gz
Algorithm Hash digest
SHA256 5830810cb06b23a12188598d28d1cd908a601f05b93830d6bb1a960728644fa3
MD5 5bd6977cd922b9b8c3ff27b791346d2a
BLAKE2b-256 b90cf17e19b27502cbf0e9eaac828f950b5ebf629d3dd657b1d32e981de46266

See more details on using hashes here.

File details

Details for the file piface-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: piface-0.0.3-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

Hashes for piface-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 a45fb233137d034fae8e2bac209096b6d40758eef6c4541d614dad5c31122518
MD5 aef24a926735cc9351d3fc36d5ca343e
BLAKE2b-256 f4b6a5f42b8597ca245804e78ca5b18687e814032d1e3cba6aeacaef0b0ccb5d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page