~4 min readgrounded in examples/echo-device/README.md · apps/worker/src/devices.ts · README.md
Connect a device·
Devices reach the backend through app/api/devices/* in one of two ways.
Dial-in (kind: cli / daemon / browser) |
Dial-out (kind: endpoint) |
|
|---|---|---|
| Who holds the credential | the device: a tind_… token in its config |
the worker: a bearer it presents to the device |
| Traffic | heartbeats every 30 s, polls a relay mailbox, PATCHes replies; works behind NAT and on batteries | the worker calls GET /api/telemetry, POST /api/chat, GET /api/camera/snapshot on the device's public HTTPS URL |
| Typical hardware | laptops (npx tiny-vercel), phones, ESP32 e-ink, MicroPython boards, OpenWrt routers |
anything that already has a web API: a robot arm, a printer, a home server behind a tunnel |
| Enroll | CLI consent flow, or POST /api/devices/adopt from a pairing screen |
POST /api/devices {kind:"endpoint", url, secret} |
A laptop, with the CLI·
npx tiny-vercel init https://<your-app>.vercel.app # the one setting; probes /api/health
npx tiny-vercel login # browser approves; code returns on 127.0.0.1
npx tiny-vercel mesh # daemon: heartbeat + relay, so the web agent can reach this laptop
A browser tab asks you to approve; the code returns on 127.0.0.1 and is exchanged for a token, and the laptop is enrolled as a device in the same step. With the daemon running it appears online on /devices and heartbeats every 30 s. The full command set is on the CLI page.
An endpoint device, without hardware·
examples/echo-device is a 90-line Node script that answers the three bearer-authenticated calls tiny makes.
export ECHO_TOKEN=$(openssl rand -hex 24) # the bearer tiny will present
node examples/echo-device/server.mjs # http://127.0.0.1:8080
cloudflared tunnel --url http://127.0.0.1:8080 # any HTTPS tunnel works (ngrok, tailscale funnel…)
node examples/echo-device/enroll.mjs --app https://<your-app>.vercel.app --url https://<words>.trycloudflare.com --name echo
The enroll script walks the same consent flow as the CLI, then POST /api/devices {kind:"endpoint", url, secret}, then GET /api/devices/endpoint?action=telemetry — one line proves app → worker → tunnel → your process → back. Headless (CI, no browser): --token <cli-jwt>.
Open /devices: echo is listed with its address and the 8×8 PNG it serves as a snapshot. In chat, "ask my device named echo for its status" makes the agent's use_device tool call the device's /api/chat and answer with its real uptime and Node version.
What the worker enforces on endpoint devices·
From apps/worker/src/devices.ts: the URL must be https:// with a public hostname (no IPs, localhost or .local); the secret must be non-empty; a 3xx is refused, never followed (it could bounce the bearer to another origin); a 401/403 from the device is reported as "device rejected our credential". Budgets: 20 s for telemetry, 90 s for chat, 10 s for a snapshot.