A programmable Stream Deck for your Mac, driven from your phone. Local network only - nothing leaves your Wi-Fi.
slate v0.1.0 - first release is out. Download (macOS DMG + Android APK) | Star the repo
slate turns your phone into a programmable Stream Deck for a Mac. The phone shows a grid of buttons; tapping one sends a command over a WebSocket on your LAN, and a small native macOS menu-bar helper runs it on the Mac - launch or focus an app, run a macOS Shortcut or AppleScript, send keystrokes, switch Spaces, control media, run multi-step macros, and more. Buttons render the real macOS app icons, pulled live from the Mac. Local-network only, with secure pairing.
Built with React Native / Expo (one app for iOS and Android) and a native Swift menu-bar helper.
Note
slate is local-network only. Traffic is end-to-end encrypted - a code-authenticated X25519 handshake with ChaCha20-Poly1305, so the channel is sealed and man-in-the-middle fails closed without any TLS certificates. See Security.
Prefer one clip? Watch the 30-second walkthrough.
The deck follows your foreground app - bind a deck to an app and it activates when that app comes to the front. Here, opening the editor on the Mac swaps the phone from the Server deck to the DEV deck automatically:
Tap a button to run a command on your Mac:
Pair by scanning a QR, then build any button - apps, Shortcuts, AppleScript, keystrokes, macros, gestures:
New to slate? The user guide walks through every feature, gesture, and chain step by step - with diagrams.
Most phone-as-deck apps route through a vendor cloud, lock you to one mobile platform, or show generic icons. slate is local-network only - no account, no relay, nothing leaves your Wi-Fi - runs on both iOS and Android from one codebase, and renders the real macOS app icons pulled live from your Mac. The link is end-to-end encrypted; pairing is a QR you scan (or a code you type) plus a per-device token you can revoke at any time.
- Real macOS app icons, live. Buttons show each Mac app's actual Finder icon, pulled live from the Mac and cached on the phone - not generic glyphs.
- Local network only, no cloud. Everything runs over your LAN via WebSocket. No account, no relay, nothing leaves your Wi-Fi.
- End-to-end encrypted. A code-authenticated X25519 handshake with ChaCha20-Poly1305 seals every frame over the LAN; scan the QR on the Mac (or type the code) to pair, plus a revocable per-device token, rate-limited with lockout. MITM fails closed, no TLS certificates.
- iOS and Android. One app, both platforms.
- Portrait and a landscape dock mode. Stand the phone sideways and it becomes a control surface with a side rail.
- Light and dark theme. Follow-system or manual.
- Many actions per button. Launch or focus an app, quit an app, run a macOS Shortcut (with input piped in), run AppleScript, send keystrokes, switch Spaces, cycle apps, control media and volume, run multi-step macros, or run guarded shell commands. Three icon sources per button - the Mac app icon, an emoji, or a built-in glyph - plus a sensible auto icon and label per action.
- Per-button gestures. Map a command to long-press, double-tap, or a swipe in any direction - e.g. double-tap to quit an app, swipe to change Spaces.
- Pages and decks. Multiple pages and multiple decks with a switcher; swipe to change page (two fingers to change deck), and drag-to-reorder buttons in edit mode.
- Live profile switching. The deck can follow the Mac's foreground app - bind an app to a deck and it activates automatically when that app comes to the front.
- Auto-discovery and resilience. Bonjour / mDNS plus a subnet scan find the Mac automatically (manual host:port always available); auto-reconnect with backoff, and it follows the Mac to a new IP after a network change.
- A real menu-bar helper. Server status, a scan-to-pair QR + code, paired-device list with revoke, a logs panel, a configurable port, and launch-at-login.
New to slate? The user guide walks through every feature, option, and gesture step by step.
PHONE (the app) LAN MAC (the helper)
+---------------------+ WebSocket + JSON +-------------------------+
| React Native / | <----------------------> | Swift menu-bar app |
| Expo deck UI | | (NWListener server) |
| builds a Command | --- command.execute ---> | runs the macOS action |
+---------------------+ +-------------------------+
The app never calls OS logic directly - it builds a semantic Command and hands it to a transport,
so the backend stays pluggable behind a single interface.
A pnpm monorepo.
| Path | What |
|---|---|
apps/mobile |
Expo SDK 56 app (TypeScript) - the deck UI |
packages/protocol |
Shared TS types + JSON Schema - the wire contract |
packages/slate-discovery |
Expo module - Android UDP broadcast discovery |
helpers/mac-helper |
Swift menu-bar helper (the primary macOS backend) |
helpers/node-helper |
Node ws MVP helper (proves the loop; launch_app only) |
docs/ |
Architecture, stack, code-logic, and flow docs |
- Node
>=22(see.node-version) and pnpm10.x. - macOS 14+ with Xcode and XcodeGen to build the Swift helper.
- For the phone app, an Expo dev build (Expo Go cannot load slate - it uses native modules):
- iOS: Xcode + the iOS Simulator.
- Android: Android Studio / a device, plus either EAS (cloud build) or a local native build.
Prebuilt downloads are on the Releases page.
Install with Homebrew (recommended) or from the DMG.
Homebrew
brew tap vakharwalad23/tap
brew trust vakharwalad23/tap # one time: the cask runs a post-install step
brew install --cask slate-helperbrew upgrade keeps it current, and the quarantine flag is cleared for you.
DMG
-
Download
slate-helper-<version>.dmg, open it, and drag SlateHelper onto Applications. -
The helper is self-signed (not notarized), so macOS quarantines it on first launch. Clear the flag once, then open it normally:
xattr -dr com.apple.quarantine /Applications/SlateHelper.app
Either way, SlateHelper runs in the menu bar (no Dock icon). To send keystrokes, switch Spaces, cycle apps, or use media keys, enable it under System Settings -> Privacy and Security -> Accessibility; launching apps and changing volume do not need it.
Download slate-<version>.apk from the same release, open it on your phone, and allow installing from
your browser or files app when prompted. iOS has no prebuilt download - build it on your own iPhone
with a free Apple ID: see iOS on a physical iPhone.
Prefer to build from source, or want to run on iOS? Follow the steps below; otherwise see Install for prebuilt downloads.
git clone https://github.com/vakharwalad23/slate.git
cd slate
pnpm installOption A - the Swift menu-bar app (recommended):
brew install xcodegen
cd helpers/mac-helper
xcodegen generate
open SlateHelper.xcodeprojIn Xcode, set Signing to your own team, then Build and Run (Cmd-R). The app has no Dock icon - look for the menu-bar item. Allow the Local Network prompt on first connect. To send keystrokes, switch Spaces, cycle apps, use media keys, or focus an app, enable SlateHelper under System Settings -> Privacy and Security -> Accessibility; launching apps and changing volume do not need it.
Option B - the Node MVP helper (quick, launch_app only):
pnpm -F @slate/node-helper start # ws://0.0.0.0:8765# iOS simulator (local)
pnpm -F @slate/mobile ios
# Android: build a dev client, then install it on a device
# cloud build (no Android SDK needed):
eas build --profile preview --platform android
# or a local native build (needs the Android SDK):
pnpm -F @slate/mobile androidThere is no prebuilt iOS download: slate has no paid Apple Developer Program behind it, so no notarized, App Store, or TestFlight build. You can still install it on your own iPhone with a free Apple ID using Xcode's free provisioning. The one catch is Apple's limit for free accounts - the app stops launching about 7 days after it is signed, and you re-run it from Xcode to renew it.
-
Give the app a bundle id that is unique to you. In
apps/mobile/app.json, changeios.bundleIdentifierfromcom.slate.appto something likecom.yourname.slate- a free Apple ID cannot sign a bundle id already registered to another team. -
Generate the native iOS project (this also runs
pod install), then open the workspace - not the.xcodeproj:pnpm -F @slate/mobile prebuild open apps/mobile/ios/slate.xcworkspace
-
In Xcode: add your Apple ID under Settings -> Accounts, then select the
slatetarget -> Signing & Capabilities, tick Automatically manage signing, and pick your name (Personal Team) as the Team. -
Plug in your iPhone, select it as the run destination, and press Run (Cmd-R). The first launch is blocked until you trust the certificate: on the phone, Settings -> General -> VPN & Device Management -> your Apple ID -> Trust. Allow the Local Network prompt so slate can find your Mac.
Renewing: when slate refuses to open (~7 days later), reconnect the phone and press Run again to re-sign it - no rebuild from scratch needed. A paid Apple Developer account removes the expiry and is the only path to a shareable build (App Store / TestFlight / ad-hoc IPA); slate ships none today.
Open the app. It auto-discovers the helper (or enter host:port manually), then tap Pair and scan the QR shown in the Mac menu (or type the code under it). Add buttons, pick an action and icon, and tap to run them on the Mac.
Auto-discovery usually finds the Mac on its own (Android uses a UDP broadcast that answers in one round trip); if it ever misses, type the IP shown in the Mac menu-bar helper into the host box.
slate is local-network only, and all application traffic is end-to-end encrypted over the existing
ws:// transport. Pairing runs a code-authenticated X25519 handshake (the pairing code shown on the
Mac is mixed in as the pre-shared key), then ChaCha20-Poly1305 seals every frame - commands, the
bearer token, even ping/pong. The phone pins the helper's identity key at pairing, so reconnecting
needs no code and an impersonating helper fails closed. Pairing is rate-limited with lockout, and
devices are revocable from the helper. Full threat model, handshake spec, and key schedule:
docs/security.md.
pnpm check # Biome: format + lint + import sort
pnpm -F @slate/mobile type-check # TypeScript
pnpm -F @slate/node-helper test # Node helper tests
# Swift helper tests:
# xcodebuild -project helpers/mac-helper/SlateHelper.xcodeproj -scheme SlateHelper \
# -destination 'platform=macOS' testConventional Commits; Husky runs Biome (pre-commit) and commitlint (commit-msg). All source, comments, and docs are ASCII-only.
Next up, in priority order:
- Faster iOS discovery. Android now finds the Mac via a UDP broadcast fast path; a native
NWBrowserleg for iOS is the remaining piece before the subnet scan becomes a rare fallback. - Distribution and polish. Notarizing the macOS DMG (it is self-signed today), plus first-run onboarding and accessibility passes.
Prebuilt binaries - a macOS helper DMG and an Android APK - are published on the Releases page; see Install for setup. The DMG is self-signed (not yet notarized), so clear quarantine once after copying it to Applications.
Contributions are very much welcome - open an issue or a pull request. Please follow Conventional
Commits and the ASCII-only rule (see Development above), and make sure pnpm check and the
type-check pass. main is protected: changes land via pull request, not direct pushes.
New here? Read the slate user guide - every feature and how to use it.
Developer docs in docs/: React Native / Expo |
code logic | flows | SwiftUI helper |
security.
Released under the MIT License. Copyright (c) 2026 Dhruv Vakharwala.



