pi-remote-control/docs/SPEC-ios-app.md

21 KiB
Raw Blame History

pi-remote — iOS Native App Spec (v3)

Status: v3 nach ExtensionAPI-Audit. Audit-Ergebnis: EXTENSION-API-AUDIT.md. Review-Verlauf v1: SPEC-ios-app-review-v1.md.

Changelog v2 → v3:

  • Audit hat S-07, S-08 und Tree-Read als out-of-the-box machbar bestätigt → von PENDING auf firm SHOULD.
  • Tree-Navigation komplett aus iOS entfernt. Gruppe T gestrichen. Begründung: Slash-Command-Dispatch ist in der ExtensionAPI blockiert, Workarounds (Hack oder Re-Implement) sind nicht den Aufwand wert. Tree-Navigation bleibt nativ in pi.
  • Spike-0a abgeschlossen, ist nun referenziertes Audit-Dokument.

1. Vision

Eine iOS-App, die laufende pi-Sessions byte-genau spiegelt, wie sie im SSH-Terminal aussehen — und durch native iOS-Mittel (Touch, Sprache, Notifications, System-Integration) die Bedienung dieser Terminal-Umgebung unterwegs angenehm macht.

Kein Hybrid-Rendering. Was im Terminal ANSI ist, bleibt in der App ANSI. Die App fügt eine Augmentation-Schicht hinzu, kein paralleles UI.

Kernszenarien:

  1. Long-Visit-Sync — Eine Session läuft tagelang. Nutzer "besucht" sie unregelmäßig vom Mac (SSH) und iPhone (App). Beide zeigen jederzeit denselben State. Reattach ist instant.
  2. Session-Lifecycle vom Phone — Sessions vom iPhone aus spawnen, benennen, switchen und beenden — so easy wie native iOS-Tabs.

2. Principles

  • P-1 — Der Terminal-Stream ist die einzige Wahrheit für Inhalt. Strukturierte Events dienen ausschließlich Statusanzeigen, Notifications und Meta-State (z.B. Tree-Navigation). Niemals Re-Rendering von Stream-Inhalt.
  • P-2 — SSH-Erfahrung bleibt unverändert. Der Mac-Workflow ändert sich durch dieses Projekt nicht spürbar.
  • P-3 — Reconnect ist die wichtigste Operation. Sichtbares Ergebnis < 1s nach App-Wake.
  • P-4 — Touch-UX wird zur Terminal-Bedienung gebaut, nicht anstelle von Terminal-Bedienung.
  • P-5 — Solo-Use. Multi-User, Sharing, Org-Features sind out of scope.
  • P-6 — Server-State wird in tmux gehalten, wo möglich. Kein paralleler State-Store im Sidecar.

3. Architecture

┌──────────────────────────────────────────────────────────┐
│  Server                                                  │
│                                                          │
│   pi (Ink TUI) ◄─► tmux session ◄─► SSH-Client (Mac)     │
│                         │                                │
│                         │ pipe-pane (raw bytes)          │
│                         │ send-keys                      │
│                         ▼                                │
│                    pi-remote sidecar                     │
│                         │                                │
│                         │ WebSocket (wss://)             │
│                         │  ├─ raw ANSI stream (binary)   │
│                         │  ├─ control (send-keys, JSON)  │
│                         │  └─ side-channel (state, JSON) │
└─────────────────────────┼────────────────────────────────┘
                          │
                          ▼
                ┌──────────────────────────┐
                │  iOS App                 │
                │  ┌────────────────────┐  │
                │  │ SwiftTerm renderer │  │
                │  └────────────────────┘  │
                │  ┌────────────────────┐  │
                │  │ Augmentation layer │  │
                │  └────────────────────┘  │
                └──────────────────────────┘

Komponenten:

  • tmux — Session-Persistenz, Multi-Client-Attach, Pane-Pipe, Send-Keys. Sessions als stable IDs, Metadaten via tmux User-Options (@description, @project, etc.).
  • Sidecar (pi-remote-control erweitert) — Node-Prozess. Spawn / Reattach tmux, exponiert WebSocket-API, hält Ringbuffer für Replay, leitet ExtensionAPI-Metadaten als Side-Channel weiter.
  • iOS-App — SwiftUI-Shell, SwiftTerm als Renderer-View, eigener Augmentation-Layer.

Datenfluss Stream: pi → tmux pipe-pane → sidecar ringbuffer → WS (binär, permessage-deflate) → iOS SwiftTerm.

Datenfluss Input: iOS → WS → sidecar → tmux send-keys → tmux pane → pi stdin. Mac-SSH-Client sieht denselben Input.

Konventionen:

  • Terminal-Size: tmux pane fixiert auf 120 × 40. Client rendert mit Pinch-Zoom auf die physische Display-Größe.
  • WebSocket-Frames: binär = ANSI-Stream-Chunk; text = JSON-Control (input, side-channel, snapshot-request).
  • Alternate-Screen-Buffer wird vom Client erkannt und nicht in den Scrollback-Cache aufgenommen.
  • Terminal-Title-Sequences werden ignoriert.
  • Mouse-Tracking-Sequenzen werden weitergeleitet, vom Client aber nicht visualisiert (Touch-UX).

4. Server Features (Sidecar)

S-01 — tmux launcher / attach

MUST. CLI pi-remote attach [<session>]. Existiert die Session, reattach; sonst neu spawnen (tmux new-session -d -s <name> 'pi') und attach. Default-Session: pi-main. Per Projekt benennbar.

Dependencies:

S-02 — Raw ANSI WebSocket Stream

MUST. Endpoint /stream/<session>.

  • Binäre Frames für ANSI-Bytes aus tmux pipe-pane.
  • WebSocket-Extension permessage-deflate aktiv (35× Compression typisch für ANSI).
  • Eine Verbindung = ein Pane.
  • Alternate-Screen-Sequenzen werden durchgereicht; Sidecar markiert sie nicht separat (Client tracked).

Dependencies: S-01

S-03 — Send-Keys-Endpoint

MUST. WS-Text-Frames als JSON: {type:"keys", data:"..."} für literal-Text, oder {type:"key", name:"escape"|"tab"|"up"|...} für Spezialtasten. Sidecar mapped auf tmux send-keys oder tmux send-keys -l. Bracketed-Paste-Frames ({type:"paste", data:"..."}) wrappen automatisch mit \e[200~ ... \e[201~.

Dependencies: S-01

S-04 — Sequence-Cursor & Delta-Replay

MUST. Sidecar nummeriert Chunks ausgehend (monotone u64). Client sendet bei Reconnect {type:"resume", lastSeq:<n>}. Server liefert ab lastSeq+1 weiter. Falls Lücke → Snapshot (S-05).

Dependencies: S-02

S-05 — Snapshot-Endpoint

MUST. Wenn lastSeq außerhalb des Ringbuffers liegt: tmux capture-pane -p -e -S -10000 als Snapshot + neuen Start-Seq. Snapshot wird komprimiert über WS geliefert.

Dependencies: S-01, S-04

S-06 — Per-Session Disk Buffer

SHOULD. Sidecar persistiert ANSI-Stream pro Session in eine einzelne Datei /var/lib/pi-remote/buffer/<session>.log.

  • Pro Session: hartes Cap = 100MB (configurable). Bei Überlauf wird der Schreibvorgang gestoppt; existierender Inhalt bleibt unangetastet. Neue Bytes ab dem Punkt fehlen im Buffer — Snapshot via S-05 funktioniert weiterhin direkt aus tmux.
  • Global: Hartes Total-Cap = 1GB über alle Sessions; wenn überschritten, schaltet der Sidecar Buffer-Schreiben global ab und meldet degraded über S-13.
  • Idle-Cleanup: Sessions ohne Output UND ohne Client seit > 30 Tagen → Buffer-Datei wird gelöscht (Session selbst nur falls ebenfalls beendet).
  • Disk-Watchdog: Bei freiem Platz < 1GB → Buffer-Schreiben global aus, Health-Status degraded.
  • Konfigurierbar via ~/.config/pi-remote/config.toml.

Dependencies: S-02

S-07 — State Side-Channel

SHOULD. Sidecar abonniert pi-ExtensionAPI-Events (agent_start, agent_end, tool_execution_start, tool_execution_end, session_tree, session_compact) und publiziert als JSON-Control-Frames:

{"type":"state","value":"thinking"|"tool"|"idle"|"awaiting-input",
 "tool":"Edit"|"Read"|...,"ts":1234567890}

awaiting-input wird abgeleitet aus agent_end + ctx.isIdle(), da kein explizites Event existiert (siehe Audit §3.1).

Dependencies: S-01

S-08 — Slash-Command-Registry

SHOULD. Endpoint /sessions/<id>/commands liefert JSON-Liste der verfügbaren Slash-Commands inkl. Beschreibung und Argument-Schema via pi.getCommands() (laut Audit out-of-the-box verfügbar). Dynamisch — Extensions die Commands hinzufügen erscheinen automatisch.

Dependencies: S-01

S-09 — Multi-Session-Management

MUST. Sidecar verwaltet mehrere tmux-Sessions parallel, alle robust gegen Sidecar-Restart (tmux überlebt, Sidecar reattached beim Boot).

Endpoints:

  • GET /sessions — Liste mit Metadaten (Name, Description, Created, letzter Output, Connected-Clients, Pi-State).
  • POST /sessions — neue Session spawnen, Body optional {name, project}.
  • PATCH /sessions/<id> — umbenennen / Description ändern.
  • DELETE /sessions/<id> — Session beenden (tmux kill-session + Buffer optional löschen).

State: Ausschließlich in tmux gehalten. Sessions haben Namen (stable ID), Metadaten via tmux User-Options:

  • @description — Auto- oder manuell vergeben (siehe S-09a)
  • @project — optional, vom User gesetzt
  • @created — Timestamp

Kein eigener JSON-State-Store im Sidecar.

Dependencies: S-01

S-09a — Auto-Naming via pi

NICE. Nach ~3 User-Messages in einer namenlosen Session triggert der Sidecar einen One-Shot-Call via pi-CLI:

pi --one-shot --model claude-haiku-4-5 \
   --prompt "Title for this conversation in 2-4 words: <transcript>"

(genaue CLI-Flags TBD; pi's eigene Anthropic-Auth wird verwendet, keine separaten Credentials nötig)

Ergebnis landet als @description in tmux. Manuelles Umbenennen aus der App jederzeit möglich und überschreibt.

Dependencies: S-09, pi-CLI one-shot mode

S-10 — Pairing & Bearer-Token-Auth

MUST. CLI pi-remote pair generiert ein kurzlebiges (5min) Pairing-Token und druckt einen QR-Code im Terminal (Unicode block chars, via qrcode-terminal).

QR-Inhalt: pi-remote://<host>:<port>?pair=<token>&fp=<sha256-cert-fp>

iOS-App scannt → tauscht Pairing-Token gegen permanenten Bearer-Token, pinnt den TLS-Fingerprint. Token im Keychain (iOS-G-01).

CLI:

  • pi-remote auth list — alle Tokens
  • pi-remote auth revoke <name> — Token widerrufen
  • pi-remote auth name <name> — Token umbenennen (z.B. "jay's iPhone")

Dependencies: S-11

S-11 — TLS via Self-Signed + Trust-on-First-Use

MUST. Sidecar generiert beim ersten Start ein selbstsigniertes Cert (ED25519 oder RSA-2048), 10 Jahre gültig, persistent auf Disk. SHA-256-Fingerprint wird in den QR aus S-10 mit aufgenommen. Client pinnt den Fingerprint beim Pairing. Bei Cert-Rotation (z.B. neuer Host) muss re-paired werden.

Kein Let's Encrypt, keine CA, kein Reverse-Proxy nötig.

Dependencies:

S-12 — Health & Metrics

NICE. /health Endpoint mit Session-Count, Buffer-Size, Connected-Clients, Disk-Watchdog-Status. Für Monitoring und Selbst-Debugging.

Dependencies:

S-13 (Tree-State Side-Channel) wurde in v3 gestrichen. Begründung: siehe Out-of-Scope und Changelog.


5. iOS Client Features

Gruppe A — Rendering & Stream

iOS-A-01 — SwiftTerm-Renderer

MUST. Vollwertiger ANSI-Terminal-View. Renderer-Setup mit Truecolor, 120×40 fixed grid, configurable Font + Theme.

Dependencies: S-02

iOS-A-02 — Sequence-Cursor & Reconnect

MUST. Client speichert lokal pro Session den letzten seq. Bei WS-Reconnect: schickt {type:"resume", lastSeq}, verarbeitet Delta oder Snapshot.

Dependencies: S-04, S-05

iOS-A-03 — Stale-Frame-Display

SHOULD. Während Sync nach App-Wake: letzten Frame einfrieren, "syncing…" als subtile Overlay-Pill anzeigen. Kein leerer Screen.

Dependencies: iOS-A-02

iOS-A-04 — Local Scrollback-Cache

SHOULD. App puffert empfangene Bytes lokal pro Session (rolling, default 5MB) für Offline-Scrolling und Suche. Alternate-Screen-Inhalt wird ausgenommen.

Dependencies: iOS-A-01

iOS-A-05 — Themes & Fonts

SHOULD. Eingebaute Themes:

  • Default-Dark, Default-Light
  • Solarized Light, Solarized Dark
  • Monokai, Dracula
  • Nord, Gruvbox Dark, Gruvbox Light
  • Tomorrow Night
  • GitHub Light, GitHub Dark
  • System (folgt iOS Light/Dark Mode)

Eingebaute Fonts (gebundlet):

  • JetBrains Mono (Default)
  • Hack
  • SF Mono
  • Menlo
  • Fira Code
  • Cascadia Code
  • IBM Plex Mono
  • Monaspace Neon

Custom Themes editierbar, iCloud-Sync nur für Custom.

Dependencies: iOS-A-01

Gruppe B — Input & Modifier

iOS-B-01 — Software-Keyboard, Direct-Passthrough

MUST. Standard-iOS-Keyboard. Mode (b) direct-passthrough: jeder Tastendruck geht sofort als send-keys. Enter sendet \r an pi → pi behandelt selbst. Kein App-eigenes Compose-Feld.

Shift+Enter (newline) via dediziertem ⇧↵-Button in der Modifier-Bar (siehe iOS-B-02).

Dependencies: S-03

iOS-B-02 — Modifier-Bar

MUST. Accessory-Bar über der Tastatur:

[Ctrl] [Esc] [Tab] [←] [↑] [↓] [→] [⇧↵] [🎙] [📋]
  • Ctrl ganz links — Sticky-Toggle: ein Tap → leuchtet → nächste Taste wird als Ctrl+X gesendet. Beliebige Ctrl-Combos möglich (Ctrl-C, Ctrl-D, Ctrl-L, Ctrl-U, ...).
  • Esc — eigener Button, dauerhaft sichtbar.
  • Tab — Autocomplete in pi.
  • Pfeiltasten (Mitte) — History scrollen, in Menüs navigieren, Input-Cursor bewegen.
  • ⇧↵ — Shift+Enter, ein Tap = \n (newline in pi multi-line).
  • 🎙 — Voice-Input (iOS-C-06).
  • 📋 — Paste-Button ganz rechts (öffnet Paste-Sheet, iOS-B-08).

Bei knappem Platz (iPhone-Portrait) ist die Bar horizontal scrollbar.

Dependencies: iOS-B-01

iOS-B-03 — Long-Press-Repeat

SHOULD. Long-Press auf Pfeiltasten oder Backspace → repeat mit Beschleunigung.

Dependencies: iOS-B-02

iOS-B-04 — Selection & Copy

MUST. Doppel-Tap → Wort. Tripel-Tap → Zeile. Long-Press + Drag → Range. Native iOS-Copy-Menu.

Dependencies: iOS-A-01

iOS-B-05 — Pinch-Zoom Font

SHOULD. Pinch in der Terminal-View → Font-Size live.

Dependencies: iOS-A-01

iOS-B-06 — Hardware-Keyboard-Support

SHOULD. Externe iPad-Keyboards:

  • Caps→Esc-Remap (optional)
  • Modifier-Pass-Through (Cmd, Option, Ctrl)
  • App-Shortcuts:
    • Cmd-K — Clear
    • Cmd-T — New session
    • Cmd-1..9 — Session switch
    • Cmd-F — Scrollback-Search
    • Cmd-, — Settings
    • Cmd-Shift-P — Slash-Command-Palette

Dependencies: iOS-B-01

iOS-B-07 — Reachability / One-Hand-Mode

NICE. iPhone-Querformat: Modifier-Bar gespiegelt für einhändige Bedienung.

Dependencies: iOS-B-02

iOS-B-08 — Smart Paste (Confirm)

SHOULD. 📋-Button in der Modifier-Bar zeigt Clipboard-Vorschau- Chip ("📋 12 lines, 847 chars"). Tap → Sheet mit vollem Preview, Insert / Cancel. Verhindert versehentliches Paste großer Blobs.

Dependencies: iOS-B-01

iOS-B-09 — Bracketed-Paste-Compliance

SHOULD. Client trackt im Stream \e[?2004h / \e[?2004l. Wenn aktiv, wird bei Paste die {type:"paste"} Variante an S-03 geschickt, sodass pi den ganzen Block als Paste erkennt.

Dependencies: S-03, iOS-B-08

Gruppe C — Pi-aware Augmentation

iOS-C-01 — Status-Bar

MUST. Top-Bar zeigt: Connection-Status, Session-Name, Pi-State (● thinking / ⏵ tool: Edit / ▶ awaiting / ⏸ idle) basierend auf S-07.

Dependencies: S-07

iOS-C-02 — Push-Notification bei Awaiting-Input

MUST. Wenn App im Background und Pi wechselt zu awaiting-input: Push-Notification "Pi ist fertig". Tap → App öffnet in der richtigen Session.

Dependencies: S-07, APNs

iOS-C-03 — Haptic Feedback bei State-Wechseln

NICE. Subtile Vibration bei thinking → idle oder thinking → awaiting-input.

Dependencies: S-07

iOS-C-04 — Slash-Command-Palette

SHOULD. Long-Press auf die Modifier-Bar → öffnet Sheet mit nativer Liste aller Slash-Commands (aus S-08), Fuzzy-Search. Tap → injiziert via send-keys. Commands mit Argumenten → Formular-View.

Dependencies: S-03, S-08

iOS-C-05 — Voice-to-Prompt

NICE. 🎙-Button → Speech-Recognition (lokal, iOS native) → editable Preview → Send.

Dependencies: iOS-B-01, Mic-Permission

Gruppe D — Session & Navigation

iOS-D-01 — Session-Switcher (MUST)

MUST. Native iOS-Liste der verfügbaren Sessions (via S-09). Spawn / Rename / Kill direkt aus dem Switcher.

Dependencies: S-09

iOS-D-01a — Background Pre-Connect

SHOULD. App connectet im Hintergrund zu allen bekannten Sessions parallel, hält pro Session einen kleinen In-Memory-Stream-Buffer + letzten Frame. Switch = Renderer wechselt instant.

Kosten: ein Socket pro Session, ein paar MB RAM. Akzeptabel solange < ~10 aktive Sessions.

Dependencies: iOS-D-01

iOS-D-01b — Optimistic-Switch mit Stale-Frame

SHOULD. Swipe zu Session B → sofort gecachten Frame zeigen, parallel live-Sync. Niemals leerer Screen, nur kurze "sync"-Pille.

Dependencies: iOS-D-01, iOS-D-01a

iOS-D-01c — Predictive Thumbnails

SHOULD. Im Session-Switcher zeigt jede Session eine kleine Live- Mini-Vorschau (z.B. 40×12 Zeichen Snapshot via tmux capture-pane). Beim Öffnen des Switchers werden die Thumbnails aktualisiert.

Dependencies: iOS-D-01

SHOULD. Such-Sheet sucht im lokalen Scrollback-Cache (iOS-A-04) mit Highlight + Jump-to-Match. Auch offline nutzbar.

Dependencies: iOS-A-04

Gruppe E — Background & Lifecycle

iOS-E-01 — WS-Keepalive

MUST. Ping/Pong-Frames im Foreground halten Connection. iOS suspended WS gerne ohne Notification.

Dependencies: S-02

iOS-E-02 — Wake-up-Sync

MUST. App-Foreground-Event triggert sofort Reconnect + Delta-Pull. Spinner falls > 200ms. Ziel < 1s sichtbarer Sync (P-3).

Dependencies: iOS-A-02

Gruppe F — Security

iOS-F-01 — Token im Keychain

MUST. Bearer-Token aus S-10 wird im iOS Keychain gespeichert.

Dependencies: S-10

iOS-F-02 — Face-ID / Touch-ID Gate

SHOULD. Biometrie-Lock vorm Öffnen der App. Opt-in via Setting, default off.

Dependencies:

iOS-F-03 — TLS-Pinning via QR-Pairing

MUST. Beim Pairing (S-10) wird der Cert-Fingerprint gepinnt. Jede künftige Connection verifiziert dagegen. Re-Pairing bei Cert-Rotation.

Dependencies: S-10, S-11


6. Out of Scope (locked)

  • Rich Chat-Rendering. Wir parsen den Stream nicht. ANSI bleibt ANSI.
  • Embedded Mini-Terminals / Block-Selection. Es gibt einen Stream.
  • Multi-User-Sharing. Solo.
  • Org/Team-Features, Cloud-Hosted-Service. Selbstgehostet.
  • Inline-Image-Rendering (iTerm2 protocol etc.). Eventuell später.
  • Eigenes Mosh. tmux + WebSocket reicht.
  • Cross-platform-Clients (Android / Web). Nur iOS nativ. Die bestehende HTML-UI bleibt als separates Artefakt.
  • Silent-Push-Background-Wake. iOS coalesced unzuverlässig; manueller Wake-up-Sync (iOS-E-02) reicht.
  • Marker-Detection / Jump-to-Marker. Pi liefert keine zuverlässigen Marker.
  • Tap-to-Cursor. Ohne Marker-Detection nicht robust machbar.
  • Eigener Sidecar-State-Store. State lebt in tmux (P-6).
  • Disk-Buffer-Rotation. Ein Cap, drüber hinaus = Daten verloren. Snapshot funktioniert weiter.
  • Bookmarks / manuelle Marker. Pi-interne Tools sollen das übernehmen.
  • Snippet-Library (Prompts mit Variablen). Pi-interne Tools.
  • Tree-View / Tree-Navigation in iOS (jeglicher Form, read-only oder interaktiv). Tree wird ausschließlich nativ in pi bedient. Audit hat gezeigt: Slash-Command-Dispatch ist in der ExtensionAPI nicht sauber zugänglich; Workarounds sind die Komplexität nicht wert.

7. ExtensionAPI-Audit (abgeschlossen)

Das Audit wurde durchgeführt; Ergebnis liegt in EXTENSION-API-AUDIT.md.

Kernergebnisse:

  • S-07, S-08, sowie Tree-Read sind out-of-the-box machbar.
  • Tree-Write (Slash-Command-Dispatch wie /fork, /checkout, /new) ist nicht sauber zugänglich → Gruppe T gestrichen (siehe Out-of-Scope).
  • Tool-Call-Daten sind vollständig verfügbar (Name, Args, Result).

Kein weiterer Spike vor Phase 1 nötig.


8. Phasen

Phase-Aufwände bewusst weggelassen (sind in v1 gestrichen worden).

  • Spike-0 — Stream-PoCpi-remote-control um tmux pipe-pane + WS-Stream erweitern. Verifizieren dass pi sauber in tmux läuft (kein Crash, Alternate-Screen sauber, kein Latency-Problem).
  • Phase 1 — Sidecar production-ready — S-01 bis S-12 (alle), S-09a optional.
  • Phase 2 — iOS-App MVP — Renderer (Gruppe A), Input (Gruppe B ohne Hardware-KB), Status-Bar (iOS-C-01), Session-Switcher (iOS-D-01 + a/b), Reconnect-Lifecycle (Gruppe E), Auth (Gruppe F).
  • Phase 3 — iOS-App Augmentation — Slash-Palette (iOS-C-04), Voice (iOS-C-05), Thumbnails (iOS-D-01c), Scrollback-Search (iOS-D-02), Hardware-KB (iOS-B-06).

9. Offene Punkte für v4

  • Q-A — pi-CLI one-shot mode für S-09a: CLI-Flags müssen verifiziert werden. Existiert das in der heutigen pi-CLI?
  • Q-C — APNs-Setup Details (Auth-Key-Provisioning, Token-Lifecycle).