Metadata-Version: 2.4
Name: piface
Version: 0.0.4
Summary: Web-based remote interface for pi, the terminal coding agent
Project-URL: Homepage, https://github.com/jbn/piface
Project-URL: Repository, https://github.com/jbn/piface
Project-URL: Issues, https://github.com/jbn/piface/issues
Author-email: generativist <jbn@abreka.com>
License: MIT
License-File: LICENSE
Keywords: agent,ai,coding-agent,fastapi,pi,remote,svelte,websocket
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Code Generators
Requires-Python: <3.13,>=3.12
Requires-Dist: anyio>=4.8
Requires-Dist: fastapi>=0.115
Requires-Dist: httpx>=0.28
Requires-Dist: platformdirs>=4.3
Requires-Dist: pydantic>=2.10
Requires-Dist: typer>=0.15
Requires-Dist: uvicorn[standard]>=0.34
Requires-Dist: websockets>=14.0
Provides-Extra: audio
Requires-Dist: coqui-tts>=0.27.5; extra == 'audio'
Requires-Dist: lightning<=2.4.0,>2.2.1; extra == 'audio'
Requires-Dist: ml-dtypes>=0.5.0; extra == 'audio'
Requires-Dist: nemo-toolkit[asr]>=2.7.0; extra == 'audio'
Requires-Dist: torch<2.11,>=2.10; extra == 'audio'
Requires-Dist: torchaudio<2.11,>=2.10; extra == 'audio'
Requires-Dist: torchcodec<0.11,>=0.10; extra == 'audio'
Provides-Extra: speech
Requires-Dist: lightning<=2.4.0,>2.2.1; extra == 'speech'
Requires-Dist: ml-dtypes>=0.5.0; extra == 'speech'
Requires-Dist: nemo-toolkit[asr]>=2.7.0; extra == 'speech'
Requires-Dist: torch<2.11,>=2.10; extra == 'speech'
Provides-Extra: tts
Requires-Dist: coqui-tts>=0.27.5; extra == 'tts'
Requires-Dist: torch<2.11,>=2.10; extra == 'tts'
Requires-Dist: torchaudio<2.11,>=2.10; extra == 'tts'
Requires-Dist: torchcodec<0.11,>=0.10; extra == 'tts'
Description-Content-Type: text/markdown

# Piface

![Piface Logo](./logo.png)

A web-based remote interface for [pi](https://github.com/badlogic/pi-mono), 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

```bash
# 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 by `nemo-toolkit`) was [quarantined on PyPI on 2026-04-30](https://thehackernews.com/2026/04/pytorch-lightning-compromised-in-pypi.html) 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](https://docs.astral.sh/uv/) (Python project/package manager)
- [Node.js](https://nodejs.org/) + [pnpm](https://pnpm.io/) (for building the frontend)
- [pi](https://github.com/badlogic/pi-mono) 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

```bash
# 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

```bash
uv run piface
```

Equivalent explicit form:

```bash
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:

```bash
# 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:

```bash
# 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:

```bash
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. **Kill** — `POST /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 session** — `POST /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. **Branch** — `POST /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](./LICENSE).
