-
-
Notifications
You must be signed in to change notification settings - Fork 57
feat: CLI remote connection via relay with E2EE #181
Description
Summary
The CLI currently only supports direct TCP/IPC connections to the daemon (ws://host:port/ws). It would be great to also support connecting through the relay with E2EE, just like the mobile, web, and desktop apps do.
This would allow managing agents from a remote terminal (e.g., from a personal machine to a work machine) without SSH tunnels or VPNs — using the same secure relay infrastructure that already powers the app clients.
Current behavior
connectToDaemon() in packages/cli/src/utils/client.ts always resolves the host to a plain WebSocket URL and creates a DaemonClient without e2ee config:
const client = new DaemonClient({
url: target.url, // always ws://host:port/ws
clientId,
clientType: "cli",
webSocketFactory: ...,
reconnect: { enabled: false },
// no e2ee, no relay URL
});Proposed behavior
Allow the CLI to connect through the relay by accepting a pairing offer (link or file). For example:
# Connect using a pairing link
paseo ls --relay "https://app.paseo.sh/#offer=eyJ2Ijo..."
# Or using a saved offer file
paseo daemon pair --save ~/paseo-offer.json # on the daemon machine
paseo ls --relay ~/paseo-offer.json # on the remote machine
# Or via environment variable
PASEO_RELAY_OFFER="eyJ2Ijo..." paseo lsThe CLI would:
- Parse the pairing offer to extract
serverId,daemonPublicKeyB64, andrelay.endpoint - Build a relay WebSocket URL via
buildRelayWebSocketUrl() - Pass
e2ee: { enabled: true, daemonPublicKeyB64 }toDaemonClient
Why this is low effort
All the infrastructure is already in place:
DaemonClientfully supports relay E2EE (checksisRelayClientWebSocketUrl(), wraps transport withcreateRelayE2eeTransportFactory)buildRelayWebSocketUrl()constructs the correct WSS URL withserverIdandrole=client- The app clients (
packages/app/src/runtime/host-runtime.ts:443-453) demonstrate the exact pattern
The change is essentially wiring up ~30 lines in the CLI's connectToDaemon() to use the same code path the app already uses.