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

217 lines
5.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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=<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=<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)
```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 <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
```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 <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.