# Operator Guide — pi-remote-control sidecar > **Phase 1 state:** all sidecar modules implemented (T-1.0..T-1.10). > The iOS app (Phase 2) is not yet built; use `wscat` or the legacy > browser HTML UI to verify a running sidecar. --- ## Requirements | Dependency | Minimum | Notes | |---|---|---| | Node.js | 18 | Must be on PATH (pi's runtime) | | tmux | 2.5 | Control mode + `pane-died` events | | openssl | any | Self-signed TLS cert generation | | Python 3 | any | Smoke-test PTY workaround only | --- ## Quick start ```bash # 1. Start pi with remote-control and auto-start the sidecar pi -e extensions/remote-control --remote-control # The sidecar prints its URL: # Remote-control started: http://127.0.0.1:7777/?token= ``` The server binds to `127.0.0.1:7777` by default (configurable — see below). --- ## Configuration Create `~/.pi/remote-control/config.json` to override defaults: ```json { "bindAddress": "0.0.0.0:7777", "publicBaseUrl": "https://pi.example.com" } ``` IC-4 (config.toml, Phase 1.7 wiring) will migrate this to TOML. For now, use the JSON format above. **Key fields:** | Field | Default | Description | |---|---|---| | `bindAddress` | `127.0.0.1:0` | Host:port to bind (`:0` = random free port) | | `publicBaseUrl` | (none) | URL printed in QR / UI — use your tunnel URL | --- ## API reference (IC-2) All endpoints require bearer token auth. Pass via: - `?token=` query param (same token as the startup URL) - `Authorization: Bearer ` header ### REST endpoints ``` GET /health → { ok, uptime, sessions, sessionIds, bufferMb, diskFreeGb, warnings } POST /sessions body: { name?: string } → 201 { id, name } GET /sessions → [{ id, name, description, state, lastOutputAt }] PATCH /sessions/:id body: { description?: string } → 200 { id, description } DELETE /sessions/:id → 204 (also deletes session buffer) GET /sessions/:id/commands → [{ name, description, args? }] GET /sessions/:id/thumbnail → text/plain, 40×12 capture-pane snapshot POST /sessions/:id/input body: { type: "key"|"keys"|"paste", name?:string, data?:string } → 204 ``` ### WebSocket endpoints ``` WS /sessions/:id/stream Client→Server (JSON text frames): { type:"resume"; lastSeq: number|null } — connect/reconnect { type:"key"; name: string } — single named key { type:"keys"; data: string } — literal text { type:"paste"; data: string } — bracketed-paste { type:"snapshot-request" } — request a snapshot Server→Client (binary frames): [seq: 8 bytes BE uint64][raw ANSI bytes] Server→Client (JSON text frames): { type:"state"; value:"thinking"|"tool"|"idle"|"awaiting-input"; tool?:string; ts:number } { type:"snapshot"; seq:number; data:string } — base64 ANSI snapshot { type:"session-meta"; name:string; description?:string; createdAt:string } { type:"error"; code:string; message:string } WS /sessions/:id/side State-only side-channel (no binary output). Server→Client: same JSON frames as /stream (state, session-meta, error). Client→Server: (none expected) ``` --- ## Pairing (iOS / CLI) ```bash # Generate a QR code to pair the iOS app node extensions/remote-control/cli/index.js pair # List bearer tokens node extensions/remote-control/cli/index.js auth list # Create a named token node extensions/remote-control/cli/index.js auth create "Jay's iPhone" # Revoke a token node extensions/remote-control/cli/index.js auth revoke ``` The QR encodes a `pi-remote://` URL (IC-3) containing host, port, pairing token, TLS fingerprint, and sidecar name. The iOS app scans it and calls `POST /pair` to exchange a bearer token. --- ## TLS Self-signed cert is generated on first run at: ``` ~/.local/share/pi-remote/tls/cert.pem ~/.local/share/pi-remote/tls/key.pem ``` The SHA-256 fingerprint is included in the QR code. The iOS app pins to this fingerprint — no CA needed. > **Note:** T-1.3 implements cert generation. T-1.5 wiring TLS into the > HTTP server is a Phase 2 task (server currently runs plain HTTP; > terminate TLS at your reverse proxy for now). --- ## Disk buffer Each session's output is written to: ``` ~/.local/share/pi-remote/buffers/.buf ``` Caps (defaults, configurable via IC-4 TOML in T-1.7): - Per-session: 100 MB - Global: 1 GB - Minimum free disk: 1 GB (writes suspended below this) - Idle cleanup: sessions inactive > 30 days are deleted on startup --- ## Smoke tests ```bash # Basic smoke (server start, HTML, manifest, icon, WS) npm run smoke # Stream integration (session CRUD, send-keys, reconnect, thumbnail) npm run smoke:stream # Both npm run smoke:all ``` Requires `python3` on PATH (PTY workaround for pi's TUI requirement). --- ## Troubleshooting **Server doesn't start / port conflict** Set `bindAddress` in config.json to a free port. **`tmux >= 2.5 required`** Upgrade tmux: `brew upgrade tmux` (macOS) or `apt upgrade tmux`. **`can't find window`** Older code bug: was using hardcoded `:0.0` pane targets, which fails if tmux `base-index` is 1. Fixed in T-1.1 — target is now just the session name. **Marker not appearing in stream** The ControlClient uses `-C` (not `-CC`) for control mode. If you see no `%output` events, check tmux version and that `-C` works: ```bash tmux -C attach -t # Should print: %begin ... %end ... %output ... ``` **APNs push not working** APNs is scaffolded (T-1.10) but device tokens are only available once the iOS app pairs in Phase 2. Check `[apns]` config and that `.p8` key path is correct.