Skip to content

feat: CLI remote connection via relay with E2EE #181

@smaky

Description

@smaky

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 ls

The CLI would:

  1. Parse the pairing offer to extract serverId, daemonPublicKeyB64, and relay.endpoint
  2. Build a relay WebSocket URL via buildRelayWebSocketUrl()
  3. Pass e2ee: { enabled: true, daemonPublicKeyB64 } to DaemonClient

Why this is low effort

All the infrastructure is already in place:

  • DaemonClient fully supports relay E2EE (checks isRelayClientWebSocketUrl(), wraps transport with createRelayE2eeTransportFactory)
  • buildRelayWebSocketUrl() constructs the correct WSS URL with serverId and role=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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions