Two thin clients — the native SwiftUI menu-bar app (wrec-app)
and the wrec CLI — talk to a local Rust daemon over the same Unix-socket
JSON protocol. The daemon owns all recording state and drives a native Swift capture
engine that does the actual pixel work: ScreenCaptureKit hands frames straight
to AVAssetWriter, which writes the hardware-encoded .mov. Rust
never touches frames.
Process boundaries
-
wrec-appstarts its channel's daemon. A CLI first launches the matching app when needed, then sends the request over that channel's socket. -
daemonresolves the target and settings, owns the queue and job lifecycle, and persists events and metrics. -
capture-enginereceives one resolved recording job and owns ScreenCaptureKit, AVAssetWriter, system audio, microphone input, pause, resume, and finalization. -
The
.movfile is written by AVAssetWriter while status and metrics travel back through the daemon to either client. Clean stops finish at the exact last sample; ten-second fragments bound media loss when finalization cannot run.
| Clients stay thin | The app and CLI own user interaction only. They do not import backend, macOS recorder, or capture-engine code. |
| One protocol for every client | Both clients use the same newline-delimited JSON protocol. Dev, Nightly, and Release have different sockets, and the handshake rejects cross-channel connections. |
| Daemon owns recording state | The Rust daemon is the source of truth for target listing, permissions, one active job, queued jobs, pause/resume/stop, history, metrics, and persisted events. |
| Frames stay native | The daemon launches a separate Swift capture-engine process for each active job. ScreenCaptureKit feeds AVAssetWriter directly; Rust never copies, transforms, or encodes video frames. |
| Stops preserve media | Normal controls, macOS Stop Sharing, capture failures, and catchable termination signals converge on writer finalization. Ten-second movie fragments keep an abruptly interrupted file playable through its last committed fragment. |
| Separate packages share one runtime shape | The app package carries wrec-app, daemon, and capture-engine. The standalone CLI package carries wrec, daemon, and capture-engine. |
| The app owns permission | The app requests Screen Recording directly. The daemon stays in its launch chain, and the CLI launches the matching app when needed, so internal helpers do not become user-facing permission entries. |
| Agent rule | Use the CLI with --json, trust daemon job state, refresh target ids per task, and follow error.next for recoverable failures. |