Rule Them All + Deck — Your free, open-source Stream Deck. Turn your smartphone into a wireless macro pad in 30 seconds.
No hardware to buy. No subscriptions. Just run npx rtadeck serve, scan the QR code with your phone, and you have a fully customizable Stream Deck — for free.
I wanted a Stream Deck. Not the $150 Elgato kind — I just wanted to press buttons on a screen and make things happen on my computer.
Then I realized: everyone already owns a Stream Deck. It's the smartphone in your pocket.
So I built rtaDeck. One npm install, one command, and your phone turns into a fully customizable macro pad over Wi-Fi. Zero cost. Zero hardware. Scan a QR code and you're in.
But it didn't stop there. I had a cheap 7" touch screen collecting dust — now it's a permanent command center on my desk. I have Claude Code running on three different machines, each with a dozen projects. One tap spawns the right AI agent, in the right folder, in the right terminal. Another tap opens my docs. Another fires off the keyboard shortcut I can never remember.
Why pay $150+ for an Elgato when your phone can do the same thing — and more? rtaDeck doesn't just press buttons. It launches programs, opens URLs, sends keyboard shortcuts, runs shell commands, and spawns AI coding agent terminals. It has multiple pages, profiles per machine, and a retro 8-bit aesthetic that makes it actually fun to use.
The commercial solutions are rigid, expensive, and locked-in. rtaDeck is a single JSON file you can edit by hand, modify with an AI agent, or configure from the touch UI. It runs everywhere Node.js runs — Windows, macOS, Linux, Raspberry Pi.
Your phone. Your old tablet. A $30 touch screen. Any of them becomes a free Stream Deck in 30 seconds.
- 5x3 button grid (configurable up to 10x6) with emoji or text labels
- 5 action types: open programs, URLs, keyboard shortcuts, CLI commands, AI agent terminals
- Retro 8-bit UI powered by NES.css + PICO-8 color palette
- Multi-page navigation with tab bar and swipe gestures
- Profiles for per-machine configurations (work PC, home PC, laptop)
- Live config editing — long-press any button to edit, or modify the JSON file directly
- LAN access — use your phone as a wireless Stream Deck via QR code
- PWA — install on your phone's home screen for a native app experience
- AI-agent friendly — JSON config,
--jsonCLI output, file watcher for external edits - Secure — API key authentication for LAN access, localhost always trusted
- Cross-platform — Windows, macOS, Linux
- Zero build step — vanilla HTML/CSS/JS frontend, no bundler needed
- 6 dependencies — minimal footprint
npm install -g rtadeck
# Create a config file in the current directory
rtadeck init
# Start the server
rtadeck servenpx rtadeck init
npx rtadeck servegit clone https://github.com/user/rtadeck.git
cd rtadeck
npm install
npm startrtaDeck uses a single JSON file: rtadeck.config.json. The file supports JSON Schema for autocomplete in editors (VS Code, JetBrains, etc.).
{
"$schema": "./rtadeck.config.schema.json",
"settings": {
"port": 3000,
"host": "0.0.0.0",
"defaultPage": "main",
"columns": 5,
"rows": 3
},
"profiles": [
{
"id": "default",
"name": "Default",
"icon": "🎮",
"pages": [
{
"id": "main",
"name": "Main",
"icon": "🏠",
"buttons": [
{
"id": "open-browser",
"slot": 0,
"display": { "type": "emoji", "content": "🌐", "size": "M" },
"action": { "type": "url", "target": "https://google.com" }
}
]
}
]
}
]
}| Field | Type | Default | Description |
|---|---|---|---|
port |
integer | 3000 |
Server port (auto-detects next free port if busy) |
host |
string | 127.0.0.1 |
Bind address. Use 0.0.0.0 for LAN access |
defaultPage |
string | — | Page shown on startup |
columns |
integer | 5 |
Grid columns (1-10) |
rows |
integer | 3 |
Grid rows (1-6) |
activeProfile |
string | — | Active profile ID |
| Field | Type | Description |
|---|---|---|
type |
emoji | text |
Content type |
content |
string | Emoji character or text label |
label |
string | Optional subtitle below the icon |
size |
S | M | L |
Display size |
color |
hex string | Text/emoji color (e.g. #FF004D) |
bgColor |
hex string | Background color |
{ "type": "open", "target": "C:\\Program Files\\Notepad++\\notepad++.exe" }{ "type": "url", "target": "https://github.com" }{ "type": "keys", "target": "ctrl+shift+p" }Supported modifiers: ctrl, alt, shift, win/cmd/super. Separate combos with +.
{ "type": "cli", "target": "npm run build", "cwd": "C:\\Projects\\my-app" }The command runs in the background. Output is captured for up to 10 seconds.
Opens a visible terminal window in a specific folder with a command. Designed for launching AI coding agents (Claude Code, Cursor, etc.) but works for any terminal task.
{
"type": "agent",
"target": "claude",
"cwd": "C:\\Projects\\my-app",
"args": ["wt"]
}| Field | Description |
|---|---|
target |
Command to run in the terminal |
cwd |
Working directory |
args[0] |
Terminal to use: cmd, powershell/pwsh, wt (Windows Terminal) |
On macOS, opens Terminal.app. On Linux, uses x-terminal-emulator.
Profiles let you maintain different button layouts per machine or context.
{
"settings": { "activeProfile": "work" },
"profiles": [
{ "id": "work", "name": "Work PC", "icon": "💼", "pages": [...] },
{ "id": "home", "name": "Home PC", "icon": "🏠", "pages": [...] }
]
}Switch profiles from the settings panel (gear icon) or via the API:
curl -X POST http://localhost:3000/api/profiles/home/activateThe web UI is a responsive PWA with a retro 8-bit design.
| Gesture | Action |
|---|---|
| Tap | Execute button action |
| Long-press (500ms) on button | Open editor overlay |
| Long-press on empty slot | Create new button |
The bottom bar shows page tabs, a fullscreen toggle, and a settings gear. Pages are switchable by tapping the tabs.
Long-press any button to open the editor overlay:
- Change emoji/text, label, colors, size (S/M/L)
- Configure action type and target
- Agent type shows terminal selector and folder field
- Delete button
The gear icon opens the settings panel:
- Profiles: list, activate, add, duplicate, rename, delete
- Pages: list, add, rename, delete
- Grid: adjust columns and rows
- Migrate: convert legacy (non-profile) configs to profile format
On Android/iOS, tap "Add to Home Screen" in the browser menu. The app opens in standalone mode (no browser chrome), landscape orientation.
rtaDeck uses API key authentication to protect LAN access.
- On startup, the server generates a random API key (24 bytes, base64url-encoded)
- Localhost connections (
127.0.0.1,::1) are always trusted — no key needed - LAN connections must provide the key via:
X-API-KeyHTTP header (for REST API calls)?key=URL parameter (for browser/WebSocket)
- The QR code printed at startup includes the key in the URL
- The server injects the key into the HTML page, so the frontend handles auth automatically
By default, a new key is generated on each restart. To keep the same key:
export RTADECK_API_KEY=my-secret-key
rtadeck serve| Connection | Auth required | How |
|---|---|---|
localhost:3000 (same PC) |
No | Trusted automatically |
192.168.x.x:3000 (LAN) |
Yes | API key in header, URL param, or QR code |
| WebSocket (localhost) | No | Trusted automatically |
| WebSocket (LAN) | Yes | ?key= in connection URL |
- Keep
host: "127.0.0.1"(default) if you don't need LAN access — no key is needed at all - Set
host: "0.0.0.0"to enable LAN access — the key system activates automatically - Use
RTADECK_API_KEYenv var for a stable key across restarts - Never expose the port to the public internet without additional security (reverse proxy, firewall)
rtadeck serve [--port 3000] Start the server
rtadeck init Create default config file
rtadeck exec <button-id> Execute a button's action
rtadeck list [--pages|--buttons] List configuration
rtadeck list --json JSON output (for AI agents)
rtadeck switch <page-id> Switch page on the display
rtadeck press <button-id> Simulate a button press
# List all pages as JSON
rtadeck list --pages --json
# List all buttons as JSON
rtadeck list --buttons --json
# Full config as JSON
rtadeck list --jsonAll /api/ routes require the X-API-Key header for non-localhost requests.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/config |
Get full configuration |
PUT |
/api/config |
Replace entire configuration |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/pages |
List all pages (active profile) |
GET |
/api/pages/:id |
Get a specific page |
POST |
/api/pages |
Create a new page |
PUT |
/api/pages/:id |
Update a page |
DELETE |
/api/pages/:id |
Delete a page |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/buttons/:id |
Get a button |
PUT |
/api/buttons/:id |
Update a button |
DELETE |
/api/buttons/:id |
Delete a button |
POST |
/api/buttons/:id/press |
Execute a button's action |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/actions/exec |
Execute an inline action |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/profiles |
List all profiles |
POST |
/api/profiles |
Create a new profile |
POST |
/api/profiles/:id/activate |
Activate a profile |
PUT |
/api/profiles/:id |
Update a profile |
POST |
/api/profiles/:id/duplicate |
Duplicate a profile |
DELETE |
/api/profiles/:id |
Delete a profile |
POST |
/api/profiles/migrate |
Migrate legacy config to profiles |
# Press a button
curl -X POST http://localhost:3000/api/buttons/open-browser/press
# Execute an inline action
curl -X POST http://localhost:3000/api/actions/exec \
-H "Content-Type: application/json" \
-d '{"type":"url","target":"https://github.com"}'
# Create a new page
curl -X POST http://localhost:3000/api/pages \
-H "Content-Type: application/json" \
-d '{"id":"tools","name":"Tools","icon":"🔧","buttons":[]}'
# LAN access (include API key)
curl -H "X-API-Key: YOUR_KEY" http://192.168.1.10:3000/api/configConnect via ws://localhost:3000 (or ws://192.168.x.x:3000?key=YOUR_KEY for LAN).
| Event | Direction | Payload |
|---|---|---|
config:updated |
Server → Client | { config } — config changed (external edit or API) |
page:switch |
Bidirectional | { pageId } — page navigation |
button:press |
Client → Server | { buttonId } — button pressed |
action:result |
Server → Client | { buttonId, result } — action execution result |
rtaDeck/
├── bin/rtadeck.js # CLI entry point
├── server/
│ ├── index.js # Express + WebSocket + auth + QR code
│ ├── routes/ # REST API routes
│ ├── services/
│ │ ├── config.js # Config load/save/validate/watch
│ │ ├── executor.js # Action execution engine
│ │ ├── keys.js # Cross-platform key simulation
│ │ ├── auth.js # API key generation + middleware
│ │ └── ws.js # WebSocket manager
│ └── cli/ # CLI commands
├── public/
│ ├── index.html # HTML shell (NES.css + fonts)
│ ├── css/style.css # PICO-8 dark theme
│ ├── js/
│ │ ├── app.js # Init + state
│ │ ├── api.js # REST + WebSocket client
│ │ ├── grid.js # Button grid renderer
│ │ ├── button.js # Button component
│ │ ├── editor.js # Button editor overlay
│ │ ├── pages.js # Page navigation
│ │ ├── settings.js # Settings panel
│ │ └── gestures.js # Touch handling
│ └── manifest.json # PWA manifest
├── rtadeck.config.json # User configuration (gitignored)
└── rtadeck.config.schema.json # JSON Schema
| Package | Purpose |
|---|---|
express |
HTTP server + routing |
ws |
WebSocket server |
commander |
CLI parsing |
ajv |
JSON Schema validation |
open |
Cross-platform open URLs/programs |
qrcode |
QR code in terminal for phone access |
- Windows: PowerShell
SendKeys+keybd_eventP/Invoke (for Win key combos) - macOS:
osascript(AppleScript key events) - Linux:
xdotool(must be installed:sudo apt install xdotool)
On Windows, programs launched via rtaDeck open on the primary monitor (not the touch screen). This is handled automatically by moving the cursor to the primary monitor before launching.
| OS | Terminal options |
|---|---|
| Windows | cmd (default), powershell/pwsh, wt (Windows Terminal) |
| macOS | Terminal.app |
| Linux | x-terminal-emulator (configurable) |
| Variable | Description |
|---|---|
RTADECK_API_KEY |
Persistent API key (skips random generation) |
RTADECK_HOST |
Override bind address |
Yes. rtaDeck is a 100% free, open-source alternative to the Elgato Stream Deck. You don't need to buy any hardware — your smartphone already works as a Stream Deck over Wi-Fi. Just run npx rtadeck serve on your computer, scan the QR code with your phone, and you have a fully functional macro pad. If you want a dedicated screen, any cheap HDMI touch display ($30-50) works perfectly.
Absolutely. rtaDeck runs on any device with Node.js 18+. A Raspberry Pi with a touch screen makes a perfect dedicated Stream Deck. Install with npm install -g rtadeck, set host: "0.0.0.0" to access it from other devices, and you have a standalone control panel.
Yes — this is exactly what rtaDeck was built for. Your smartphone is a touch screen you already own. Set host: "0.0.0.0" in your config, start the server, and scan the QR code printed in the terminal. Your phone connects instantly over Wi-Fi with secure authentication. Install it as a PWA ("Add to Home Screen") and it looks and feels like a native app. No app store, no subscription, no cost.
Use the agent action type. Configure a button with "type": "agent", set "target" to the command (e.g., "claude"), "cwd" to your project folder, and "args" to your preferred terminal ("wt", "cmd", "powershell"). One tap opens a terminal in the right folder with your agent running.
Yes. rtaDeck is fully cross-platform. Keyboard shortcuts use PowerShell on Windows, AppleScript on macOS, and xdotool on Linux. Program launching, URL opening, and terminal spawning all adapt to the OS automatically.
Yes. The config file (rtadeck.config.json) is a standard JSON file with a published schema. rtaDeck watches it for changes — any external edit (by an AI agent, a script, or a text editor) is instantly reflected in the UI. The CLI also provides --json output for machine-readable data.
rtaDeck is purpose-built for developers: it has first-class support for launching AI coding agents, running CLI commands, sending keyboard shortcuts, and managing multiple profiles per machine. It's also the only one with a retro 8-bit NES aesthetic, zero build step (vanilla JS), and just 6 npm dependencies.
MIT