Skip to content

Companion Remote

Nemu-x edited this page Jul 7, 2026 · 2 revisions

TV Remote & Companion

Control a ClashFest instance running on another device — typically an Android TV — from your phone, over your local network. Turn the VPN on/off, share a subscription to the TV, see its status, and rename it. No accounts, no cloud, no relay — everything stays on your Wi-Fi.


What you can do

  • Discover ClashFest/SlothClash devices on your LAN by a friendly name (Living Room, Bedroom, …).
  • Pair securely by scanning a QR on the TV (or entering a PIN for camera-less controllers like a PC).
  • Toggle the VPN on/off on the TV.
  • Share one of your phone's subscriptions to the TV in one tap.
  • See status (connected / active profile / traffic) and rename the device.

Set it up

On the TV (the agent)

  1. Open ClashFest on the TV → the Remote control entry (on Home, or in Settings).
  2. Enable Allow remote control. This is off by default — like ADB debugging, you turn it on deliberately.
  3. The TV shows a QR code (and its ip:port for a sanity check). Leave it on screen.
    • For pairing a PC / camera-less controller, choose Show pairing PIN instead — a 6-digit code, valid ~60s.

On the phone (the controller)

  1. Open ClashFest → Companion (Settings, or the sheet → Control a TV).
  2. Scan the TV's QR (or enter the PIN).
  3. Paired! The device appears in your list. Right after pairing you're offered to share your active subscription to it.

Both devices must be on the same local network. Pairing needs you to physically see the TV screen (QR/PIN) — that's the security proof.

Troubleshooting

  • "Unreachable" right after pairing — make sure both devices are on the same Wi-Fi (not guest isolation / different VLANs). An emulator can't be an agent — its 10.0.2.15 address is internal NAT and unreachable from a real device.
  • QR looks stale — the TV persists its port, but if you reinstalled, re-open the remote-control screen for a fresh QR.
  • Nothing discovered — some routers block mDNS/multicast; try again or pair directly by scanning the QR.

Security model

Mechanism Why
Default OFF — explicit opt-in on the TV No silent network exposure
QR/PIN pairing carries ip:port + token + TLS cert fingerprint Requires physically seeing the TV = presence proof
TLS (self-signed, pinned) by the QR fingerprint Traffic (including subscriptions) encrypted; MITM impossible
Per-device bearer token, revocable by un-pairing One compromised controller ≠ all; easy revocation
Command whitelist on the gateway Even a leaked token can't do arbitrary damage
The proxy core's REST API stays bound to 127.0.0.1 The core API never touches the LAN — the gateway is the single auditable entry point

For developers — protocol (v1)

The companion is a thin, secure LAN gateway in front of the app's existing controls (VpnService toggle, subscription import) and — later — the Mihomo external-controller REST API.

  • Discovery: mDNS/NSD service _clashctl._tcp. TXT keys: app (clashfest|slothclash), id (stable device id), name, ver, fp (TLS fingerprint).
  • Pairing QR: clashctl-pair://<ip>:<port>?id=<deviceId>&name=<name>&app=<app>&fp=<sha256>&token=<token>
  • Transport: HTTPS (self-signed, pinned). Authorization: Bearer <token> on every request.
  • Endpoints (v1):
    • GET /v1/status{ running, activeProfile, uploadTotal, downloadTotal, mode }
    • POST /v1/power { "action": "on" | "off" | "toggle" }
    • POST /v1/subscription { "url", "name" } (agent fetches + imports) or { "payload": "<yaml>" }
    • POST /v1/rename { "name" } → persists + re-announces mDNS
    • POST /v1/pair { "pin" } → issues a bearer token (PIN-gated, no bearer required)
  • Phase 2: ANY /v1/core/* whitelisted proxy to the localhost external-controller (proxy-group select, /traffic, /connections), plus WS /v1/events for live push.

The shared wire contract lives in the clash-companion repo so ClashFest, SlothClash, and any future controller speak byte-for-byte the same protocol.

Clone this wiki locally