pi-remote-control/docs/reference/OPERATOR.md

5.5 KiB
Raw Permalink Blame History

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

# 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.