5.5 KiB
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
wscator 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
# 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=<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:
{
"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=<TOKEN>query param (same token as the startup URL)Authorization: Bearer <TOKEN>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)
# 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 <id>
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/<session-id>.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
# 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:
tmux -C attach -t <session>
# 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.