The framework-agnostic core of the Google TV stack — discovery, pairing, a stateful session, and a shared device store that every client (CLI, MCP, app) builds on.
🌟 Features
- 🔍 mDNS Discovery — finds every Google TV on the local network via
dns-sd; returns pure data, no side effects - 🔐 Dependency-injected Pairing —
onSecretis a callback you supply (terminalreadline, MCP round-trip, Tauri dialog…); the library never touches I/O itself - 📡 Stateful Session —
createSession()returns anEventEmitterthat reduces the underlying protocol stream to a single observableSessionState; subscribe with.on("change", state => …) - ⚡ One-shot Helpers —
sendKey,launchApp,connect,withRemotefor scripts that don't need a long-lived connection - 📱 App Catalog — curated
APPSlist withfindApp,listApps, andappLink(builds the reliablemarket://launch?id=<package>URI) - 🗄️ Shared Config Store — reads and writes
~/.config/gtv/config.json; all consumers (gtv-cli,mcp-gtv,gtv-app) share one device registry - 🔑 Full Keycode Surface —
KEYS,KEY_LABELS, and re-exportedRemoteKeyCode/RemoteDirectionso consumers need only depend on@kud/gtv
🚀 Quick Start
npm install @kud/gtvDiscover and pair
import { discover, pair } from "@kud/gtv"
const [tv] = await discover()
await pair({
host: tv.host,
hostname: tv.hostname,
port: tv.port,
name: tv.name,
onSecret: async () => promptUserForPin(), // PIN displayed on the TV screen
})Drive a stateful session
import { createSession, KEYS } from "@kud/gtv"
const session = createSession() // uses the currently configured device
session.on("change", (state) => console.log(state))
session.sendKey(KEYS.home)
session.typeText("interstellar")
session.launchApp("market://launch?id=com.netflix.ninja")
session.stop()One-shot commands
import { sendKey, launchApp, findApp, appLink, KEYS } from "@kud/gtv"
await sendKey(KEYS.mute)
const netflix = findApp("netflix")
if (netflix) await launchApp(appLink(netflix))