Hardware probe/diagnostic app for the Elgato Stream Deck+ (seed of the muxplex sidecar)
Project description
muxplex-deck: Stream Deck+ hardware probe & muxplex sidecar
This repo has two apps, sharing one uv project:
deck-probe-- a PoC/spike app that proves we can drive every feature of any Elgato Stream Deck model and capture every input it can produce, including clean hotplug (unplug/replug without restarting the process). It is capability-driven: on connect it prints a capability report (model, serial, firmware, key count/layout/size, dials, touch strip, touch keys) and exercises only the controls the connected deck actually has -- keys always, dials only ifdial_count() > 0, the touch strip only ifis_touch(). Verified on the Stream Deck+ (8 keys, 4 dials, touch strip); the 15-key Original/MK2 (3x5, 72x72 keys, no dials/touch) runs the same probe with dial/touch exercises skipped. Device I/O only, no server/network integration. See "Stream Deck hardware probe" below.muxplex-deck-- the actual product: a sidecar that shows your muxplex tmux sessions on the deck's 8 keys and switches sessions on key press. See "muxplex sidecar" below.
muxplex-deck talks to the deck only through a small DeckDevice protocol
(src/muxplex_deck/device.py), with two interchangeable backends: the real
hardware (device_real.py, a thin wrapper over the streamdeck library)
and an in-process emulator (emulator.py) with a localhost web UI. This
means the whole sidecar -- state machine, muxplex client, rendering,
interaction flow -- can be developed and tested with zero hardware, on any
machine, with no hidapi installed. See "Stream Deck+ emulator" below.
Quickstart (recommended)
uv tool install git+https://github.com/bkrabach/muxplex-deck
muxplex-deck init https://your-server:8088
muxplex-deck service install
muxplex-deck init is a turnkey setup wizard: it validates the server URL,
auto-fetches the server's CA certificate when one is needed (no scp/SSH
required -- and no risk of grabbing the leaf cert by mistake, a real
gotcha this project has hit before), walks you through pasting the
federation key, writes config.json, and re-verifies the server, CA, and
Stream Deck before handing off. It's idempotent -- re-run it anytime;
existing values become the defaults, nothing is clobbered.
uv sync is for developing this repo, not installing the tool. It only
builds muxplex-deck's own .venv and does not put muxplex-deck on
your PATH -- this exact confusion has already cost real setup time. Use
uv tool install (above) to actually install and run the sidecar; reach
for uv sync only if you're hacking on muxplex-deck's source.
The manual/advanced setup (hand-editing config.json, scp-ing files
yourself) is still documented further down for anyone who wants it --
see "Service install walkthrough" and "muxplex sidecar" -> "Config" below.
CLI
muxplex-deck has the same command shape as its sibling muxplex server
tool: a default action, a config group, a service group (systemd on
Linux, launchd on macOS), doctor, update, and version. Bare
muxplex-deck and muxplex-deck run do exactly the same thing.
| Command | What it does |
|---|---|
muxplex-deck / muxplex-deck run |
Run the sidecar (the default action) |
muxplex-deck --emulator |
Run against the in-process emulator instead of real hardware |
muxplex-deck config / config list |
Show all config keys and their current values |
muxplex-deck config get <key> |
Show one config value |
muxplex-deck config set <key> <value> |
Set a config value (type auto-detected) |
muxplex-deck config reset [key] |
Reset one key, or all keys, to defaults |
muxplex-deck service install |
Install + enable + start the background service |
muxplex-deck service uninstall |
Stop + disable + remove the service |
muxplex-deck service start / stop / restart |
Control the service |
muxplex-deck service status |
Show service status |
muxplex-deck service logs |
Tail service logs |
muxplex-deck status / status --json |
Show connected hardware, server reachability, and active session/view/page -- read from the running sidecar's published status, so it never contends with a running service for the (exclusive) HID handle |
muxplex-deck doctor |
Check Python version, install source, config, federation key permissions, ca_file validity, Stream Deck detection, HID openability, server reachability, and service status |
muxplex-deck update (alias upgrade) |
Update to the latest main and restart the service |
muxplex-deck version / --version |
Show the installed version |
Every config key maps 1:1 to a config.json field: server_url,
key_file, ca_file, poll_interval, sort, focus_app. See "Config"
under "muxplex sidecar" below for what each one means.
The HID-permission caveat (why service install prints a udev block)
Unlike the muxplex server (a plain user process), the sidecar needs raw
USB HID access to the Stream Deck -- which a non-root Linux user does not
have by default. This is why you've been running sudo muxplex-deck. A
systemd user service, however, runs as your normal user, not root -- so
without a udev rule granting your user access to the device (vendor id
0fd9), the installed service will start but fail to open the deck.
muxplex-deck service install checks for an existing rule under
/etc/udev/rules.d/ or /usr/lib/udev/rules.d/ and, if none is found,
prints a copy-pasteable remediation block (the same rule shown in
"Permissions" under the hardware probe section above) instead of silently
installing a service that won't work. It never writes to /etc itself --
only detects and reports. This remediation is only ever printed when udev
is actually running (/run/udev/control present) -- on WSL without
systemd, or in a container, it never fires anyway, so muxplex-deck
detects that and prints a different, WSL/container-appropriate fix instead
(see docs/WSL.md). Run the printed sudo tee ... && sudo udevadm control --reload-rules && sudo udevadm trigger commands, then unplug and
replug the deck (or muxplex-deck wsl attach under WSL) and muxplex-deck service restart.
On Linux, service install also attempts loginctl enable-linger <you> (best-effort, non-fatal) so the service keeps running after you log
out -- appropriate for a headless, always-on sidecar. muxplex-deck doctor
reports both the udev rule and HID-openable status.
Service install walkthrough (Linux)
uv tool install git+https://github.com/bkrabach/muxplex-deck
muxplex-deck init https://<your-server>:8088 # sets server_url, ca_file, federation key
muxplex-deck doctor # confirms config, key file, CA, and deck are all in order first
muxplex-deck service install # writes the systemd user unit, enables linger, warns about udev if needed
muxplex-deck service status
On macOS the same commands install a launchd agent instead (no udev/linger step -- macOS needs no special HID permissions).
macOS setup
- Install uv if you don't have it:
curl -LsSf https://astral.sh/uv/install.sh | sh
- Install the native HIDAPI library (the
streamdecklibrary needs it at runtime):brew install hidapi
- Quit the official Elgato Stream Deck app first. It holds exclusive access to the device's USB HID interface -- if it's running, this probe will not be able to open the device.
- From this directory, sync dependencies:
uv sync
Stream Deck hardware probe (any model)
Running
uv run deck-probe
Windows + WSL
USB devices are invisible to WSL until Windows hands them over. Rather
than driving usbipd/udev rules by hand for this PoC probe, install and
use the actual product instead -- it handles this for you:
uv tool install muxplex-deck
muxplex-deck wsl attach # finds the deck, attaches it, tells you what's left
muxplex-deck doctor # diagnoses every remaining step, with real values
Neither needs the details -- but see docs/WSL.md if you want them (BUSID/
device-node churn, the usbipd vs usbipd.exe trap, durable vs per-attach
permission options).
Close the Elgato Stream Deck app on Windows first -- it holds exclusive HID access. The probe prints this same guidance when it finds no device.
Or equivalently:
uv run python -m deck_probe
Press Ctrl+C to exit cleanly at any time -- the deck is reset (blanked)
and the device handle is closed before the process exits.
Verification checklist
Walk through these in order. Everything is logged to the console with a timestamp; watch both the terminal and the physical device.
-
Cold start, no device connected
- Run
uv run deck-probewith nothing plugged in. - Expect: no-device guidance (udev/usbipd/Elgato-app hints) followed by
waiting for a Stream Deck (polling every 2s)...logged once, not repeated every 2 seconds (a "still waiting" heartbeat is fine roughly every 30s).
- Run
-
Plug in the Stream Deck+
- Within ~2 seconds, expect a
deck capabilities:report block with model, serial number, firmware version, key count/layout/size, dial count, key image format, and touch strip format. - Expect all 8 keys to light up immediately, each showing a distinct background color and its own index number (0-7).
- Expect the touch strip to show 4 labeled zones:
D0 BRIGHTNESS,D1 COUNTER,D2 COUNTER,D3 COUNTER, each with a starting value.
- Within ~2 seconds, expect a
-
Key presses
- Press and hold any key: it should invert to a black background with
white text while held, and log
key[N] PRESSED. - Release it: it should return to its original color, and log
key[N] released. - Try all 8 keys.
- Press and hold any key: it should invert to a black background with
white text while held, and log
-
Dial 0 (brightness)
- Rotate dial 0: the overall deck brightness should change live and
smoothly, the
D0 BRIGHTNESSzone should update to show the new percentage, and each tick should logdial[0] TURN ... (clockwiseorcounter-clockwise). - Press dial 0: brightness resets to the default (75%), the zone
updates, and it logs
dial[0] PRESSED/released.
- Rotate dial 0: the overall deck brightness should change live and
smoothly, the
-
Dials 1-3 (counters)
- Rotate each dial: its zone's counter value should update on the touch
strip (only that zone repaints -- proving partial touch-strip
updates), and it logs
dial[N] TURN .... - Press each dial: its counter resets to 0 (zone updates), logging
dial[N] PRESSED/released.
- Rotate each dial: its zone's counter value should update on the touch
strip (only that zone repaints -- proving partial touch-strip
updates), and it logs
-
Touch strip -- short tap
- Tap anywhere on the touch strip: expect a vertical yellow marker line
drawn at the tapped x position (full strip repaint), and a log line
touch SHORT tap at (x, y).
- Tap anywhere on the touch strip: expect a vertical yellow marker line
drawn at the tapped x position (full strip repaint), and a log line
-
Touch strip -- long press
- Press and hold a point on the touch strip: expect a log line
touch LONG press at (x, y). No visual change is expected for this one.
- Press and hold a point on the touch strip: expect a log line
-
Touch strip -- drag/swipe
- Swipe across the touch strip: expect a log line
touch DRAG from (x1, y1) to (x2, y2).
- Swipe across the touch strip: expect a log line
-
Unplug while active
- Unplug the Stream Deck+ USB cable while the probe is running.
- Expect a loud
Stream Deck+ disconnectedwarning within a couple of seconds, the process should NOT crash, and it should return to thewaiting for Stream Deck+...polling state.
-
Replug
- Plug the Stream Deck+ back in.
- Expect it to be detected automatically within ~2 seconds and fully repainted (step 2 again), with no restart of the process required.
- Repeat steps 9-10 a few times to confirm the cycle is reliable.
-
Ctrl+C shutdown
- With the device connected and active, press
Ctrl+C. - Expect the deck to reset/blank, a
deck-probe shutting downlog line, and the process to exit with code 0. - Also try
Ctrl+Cwhile in thewaiting for Stream Deck+...state (no device connected) -- same clean exit expected.
- With the device connected and active, press
Troubleshooting
Device not found / stuck waiting forever
- Is the official Elgato Stream Deck app running? Quit it -- it holds exclusive HID access and this probe cannot open the device while it's running.
- Is the USB cable a data cable (not charge-only) and firmly seated? Try a different cable or port.
- Confirm the OS sees the device at all: on macOS,
system_profiler SPUSBDataType | grep -A 5 Elgatoshould show a "Stream Deck +" entry.
Could not find the native HIDAPI library... error at startup
- macOS:
brew install hidapi, then retry. - Debian/Ubuntu:
sudo apt install libhidapi-libusb0, then retry. - This error means the library is missing entirely -- it is not a permissions issue and won't resolve itself by retrying without installing the package.
Permissions
- macOS: no special permissions are needed for USB HID access with this library.
- Linux: you need a udev rule granting your user access to the Stream
Deck+'s USB HID interface (VID
0fd9). Create/etc/udev/rules.d/50-elgato-streamdeck.ruleswith:
Then reload rules and replug the device:SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", MODE="0666" SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0fd9", MODE="0666"sudo udevadm control --reload-rules sudo udevadm trigger
muxplex sidecar (muxplex-deck)
Shows your muxplex tmux sessions on the deck's 8 keys -- each key rendered
as a mini terminal preview of that session -- and switches the active
session on key press. Dial 0 cycles views; dial 1 pages within the current
view. Polls GET /api/sessions + GET /api/state + GET /api/settings on
the muxplex server every poll_interval seconds; repaints only the keys
(and strip) whose content actually changed (no flicker, no wasted JPEG
encodes for an idle session).
View-following: the deck mirrors whatever view is active in the muxplex
PWA. Keys show the sessions belonging to the server's current view
(active_view from GET /api/state) -- all, hidden, or a user-defined
view -- filtered the same way the PWA does. An unknown/deleted view name
shows honestly as zero sessions rather than silently falling back to all.
The needs-attention predicate is the exact one the PWA uses: a session
needs attention iff unseen_count > 0 and either it has never been seen or
the most recent fire is newer than the last time it was seen
(Bell.needs_attention in client.py) -- so an old, acknowledged bell
doesn't keep glowing forever. See "Key previews" below for how this and the
active-session state are shown (colored borders, matched to muxplex's own
brand palette).
Dial 0 -- view cycling: turning steps through ["all"] + <your named views> + ["hidden"] (wraps at the ends); the strip immediately echoes the
candidate view name while turning, and after ~400ms of no further ticks it
commits via PATCH /api/state -- so a fast spin sends exactly one request,
not one per tick. hidden is a reserved pseudo-view exactly like all --
never a member of your named views, but reachable the same way -- and
switching to it shows only the sessions you've hidden in the PWA (a
property of the session, orthogonal to view membership; a session can be
both hidden and a member of a named view). active_view is global
server-side state (last writer wins across every device/tab watching the
server), so turning the dial changes what the PWA shows too, exactly like
switching views there would. Pressing opens the view picker (see
"Dial-press picker mode" below) instead of jumping straight to all.
Dial 1 -- paging: turning moves ±1 page within the current view (8
sessions/page, clamped at the first/last page -- no wrap). Purely local --
no server writes. The strip shows a pN/M indicator whenever a view has
more than one page. Paging resets to page 1 whenever the active view
changes (from either dial 0 or the PWA). Pressing opens the page
picker (see "Dial-press picker mode" below) instead of resetting to page 1.
Dial-press picker mode: pressing either dial hands the 8 keys over to
a chooser instead of performing an immediate action -- useful once there
are more views or pages than there are keys (e.g. 9 views, or 9 pages of
sessions). Pressing dial 0 opens the view picker: each key shows
one view name (["all"] + <your named views> + ["hidden"], same list dial
0 cycles through); tapping a key switches to that view (PATCH /api/state) and
returns to the normal session display. Pressing dial 1 opens the
page picker: each key shows a page number for the current view; tapping
one jumps straight there. While a picker is open: turning its own dial
scrolls the window if there are more than 8 options (the strip shows e.g.
VIEW PICKER -- tap to choose · 1-8/9); pressing the same dial again
closes the picker with no change; pressing the other dial switches
straight to that dial's picker. The option matching today's actual active
view/page is marked with the same cyan border used for the active session,
so it's obvious what you'd be leaving. A session key's normal connect
behavior is suspended while a picker is open -- key taps select a picker
option, not a session, until you pick one or back out.
Sort order -- "sort" config field ("attention" default, or
"server"): in "attention" mode the view's sessions are reordered
before paging so the most urgent land on page 1: sessions needing
attention first (newest bell fire first), then the active session, then
everything else by most-recent activity (last_activity_at, when the
server exposes it -- older muxplex servers get a one-time INFO log and a
graceful fallback to server order for that tier). "server" mode disables
all of this and shows exactly what the PWA's own sort_order setting
(alphabetical or manual/server order) produces -- the pre-existing
behavior.
Key previews: each occupied key renders a small monospace crop of the
session's live pane (bottom-left corner, ANSI colors stripped in this first
pass -- see rendering.py's docstring for the honest fidelity tradeoff),
with the session name banner and status border(s) always layered on top so
identity stays legible regardless of what's scrolling underneath. Status is
shown as a colored border rather than a background fill or dot, using the
exact brand colors from muxplex's own frontend (frontend/style.css):
cyan #00D9F5 for the active session, amber #F1A640 for
needs-attention. A session that's both active and needs attention gets both
rings at once (amber outer, cyan inner) rather than losing one status to
the other.
Config
Config is a JSON file at ~/.config/muxplex-deck/config.json by default
(override with --config <path> or the MUXPLEX_DECK_CONFIG env var):
{
"server_url": "https://<your-server>:8088",
"key_file": "~/.config/muxplex-deck/federation_key",
"poll_interval": 2.0,
"sort": "attention",
"focus_app": "muxplex"
}
server_url(required) -- the muxplex server's base URL, reached over Tailscale from the Mac.key_file(optional, defaults to~/.config/muxplex-deck/federation_key) -- path to a file containing the federation Bearer key, read fresh at startup and whitespace-stripped.ca_file(optional) -- path to a CA bundle for TLS verification. Python does not use the macOS Keychain trust store, so if the server's certificate was issued by muxplex's own local CA (muxplex setup-tls), point this at that CA file. Never omit verification instead -- see Troubleshooting below.poll_interval(optional, default2.0) -- seconds between session polls while active.sort(optional, default"attention") --"attention"reorders each view's sessions before paging (attention first, then active, then most recently active);"server"disables that reordering and shows exactly what the PWA's ownsort_ordersetting produces. See "Dial 0 / Dial 1 / Sort order" above for the full behavior.focus_app(optional, default off) -- macOS app name of the locally installed muxplex PWA; see "Bringing the PWA to the foreground" below.
Bringing the PWA to the foreground (macOS)
If the muxplex PWA is installed as a standalone app on the same Mac the
sidecar runs on, set focus_app to its application name and every
key-press session switch will also bring that window to the foreground --
press a key, see the terminal you just switched to. It runs open -a "<focus_app>" on a background thread: it never delays the switch itself,
and a focus failure (wrong name, app not installed) is logged as a warning
and otherwise ignored.
{
"server_url": "https://<your-server>:8088",
"focus_app": "muxplex"
}
Finding the app name: it's the name macOS shows for the installed PWA
-- in the menu bar next to the Apple logo while it's frontmost, or under
its Dock icon. For a PWA installed via Chrome's "Install app" this is the
PWA's own title (Chrome puts the .app bundle under ~/Applications/Chrome Apps.localized/); for Safari's "Add to Dock" it's the name you gave it.
If the sidecar logs focus: ... failed, the name doesn't match -- check
what open -a "<name>" does in a terminal.
Scope, deliberately narrow: focus fires only on an explicit key-press
that changes the active session -- never on dial turns, view/page changes,
poll-driven repaints, or pressing the already-active session's key -- so
the window is never yanked forward while you're just browsing the deck.
macOS-only today (on other platforms a configured focus_app logs one
INFO notice and is ignored); a Windows implementation is planned.
Any missing/invalid config or unreadable key file produces a clear, actionable message on stderr and a non-zero exit -- there is no default that silently skips auth or TLS verification.
Getting the federation key onto the Mac
Easiest: muxplex-deck init (see "Quickstart" above) prompts you to
paste the key directly -- no SSH access to the server required, and it's
never echoed back or logged.
Manual method, if you'd rather not use the wizard: the muxplex server
already has a federation key generated (~/.config/muxplex/federation_key
on the server, federation enabled). Copy it over:
mkdir -p ~/.config/muxplex-deck
scp <your-server>:.config/muxplex/federation_key ~/.config/muxplex-deck/federation_key
chmod 600 ~/.config/muxplex-deck/federation_key
Then create ~/.config/muxplex-deck/config.json with your server_url
(the default key_file path above already matches, so you can omit it).
Running
uv run muxplex-deck
Or with an explicit config path:
uv run muxplex-deck --config ~/some/other/config.json
Ctrl+C exits cleanly at any time -- the deck is reset (blanked) and the
device handle closed before the process exits.
Verification checklist
- Cold start -- run with the deck unplugged: waits quietly, no
server traffic (check with e.g.
tcpdumpor just trust the code: the poll loop only starts once a device is open). - Plug in, server reachable -- keys populate with your first 8
session names within
poll_interval; the touch strip shows<hostname> · N sessions · ACTIVE: <name>; the deck is at full brightness (100%) even if it powered on dim. - Bell indicator -- trigger a bell in one of your tmux sessions
(e.g.
printf '\a'); its key should grow an amber (#F1A640) border within one poll tick. - Press a key -- switches muxplex's active session (confirm in the
PWA or by reconnecting a terminal). The cyan (
#00D9F5) active border moves to the pressed key immediately, before the server round-trip completes -- it does not wait for the next poll tick, and a second key press is accepted right away rather than blocking on the first one's connect. - Server down -- stop muxplex (or block the URL) while the sidecar
is running: strip shows
<hostname> UNREACHABLE -- retrying, keys go blank, and it recovers automatically (repainting sessions) once the server comes back. - Bad key -- temporarily corrupt the key file: strip shows
AUTH FAILED -- check key file, aCRITICALlog line appears, and it retries slowly (every 30s) rather than spinning. - Unplug/replug -- same hotplug behavior as the probe: unplug stops all server traffic and returns to waiting; replug brings up a fresh ACTIVE session (at full brightness again).
- Dial-press picker mode -- press dial 0: keys switch to a list of
view names (strip reads
VIEW PICKER -- tap to choose); tap one to switch views and return to the session display. Press dial 1: keys switch to page numbers; tap one to jump there. Pressing the same dial again exits without changing anything; pressing the other dial while a picker is open switches straight to that dial's picker.
Troubleshooting
SSL verification failure (SSLCertVerificationError or similar)
- Your muxplex server is likely using a certificate from its own local CA
(
muxplex setup-tls) rather than a publicly trusted one. Setca_filein your config to that CA's certificate path and retry. - Never work around this by disabling verification -- point at the right CA file instead.
401 / 403 -- AUTH FAILED on the strip
- The federation key is missing, stale, or doesn't match the server's.
Re-copy it from the server (see "Getting the federation key onto the
Mac" above) and confirm
key_filein your config points at it.
Device not found / Could not find the native HIDAPI library...
- Same as the probe -- see the Stream Deck+ hardware probe's
Troubleshooting section above (quit the official Elgato app, install
hidapi, check the USB cable).
Stream Deck+ emulator
muxplex-deck can run against an in-process virtual Stream Deck+ instead
of real hardware -- no device, no hidapi, no native library at all. This
is the same code path as real hardware above the device layer: state
machine, muxplex client, polling, rendering, key-press-to-session-switch.
Only the physical HID transport is swapped out (see "How this works" below).
Running
uv run muxplex-deck --emulator
Then open http://127.0.0.1:8484 in a browser. You'll see the 8 keys and
the touch strip rendered as images, a Plug in / Unplug toggle, and simple
dial controls. The emulator starts unplugged -- click "Plug in" to bring
the sidecar's state machine up (same as physically connecting the real
device), point your config.json at a running muxplex server as usual, and
watch keys populate with session names. Click a key to switch sessions,
exactly like pressing a physical button.
Use a different port with --emulator-port <N> if 8484 is taken.
The same HTTP endpoints the UI polls (GET /state, GET /keys/<n>.jpg,
GET /strip.jpg, POST /plug, POST /unplug, POST /input/key,
POST /input/dial, POST /input/touch) can be driven directly with
curl/httpx -- useful for scripted or agent-driven testing without a
browser at all.
What this proves vs. what only real hardware proves
Proves (same code as production): the hotplug state machine
(DEVICE_ABSENT/ACTIVE/UNREACHABLE/AUTH_FAILED transitions), the muxplex HTTP
client against a real muxplex server, session-list polling and repaint-only-
on-change logic, key-press-to-connect_session wiring, and all of
rendering.py's image generation (via the same PILHelper calls, same
image formats/sizes).
Does NOT prove: anything about the physical USB/HID transport itself --
whether the real streamdeck library's read thread, hidapi bindings, or
actual device firmware behave as expected. That layer is covered by
deck-probe against real hardware (see above), not by the emulator. Always
confirm a change against real hardware before considering it fully proven.
How this works
muxplex_deck.main depends only on the DeckDevice protocol
(device.py), never on the streamdeck library directly. --emulator
selects emulator.EmulatorDeviceManager instead of
device_real.RealDeviceManager as the one and only backend-selection point
(main._build_manager) -- the two backends are mutually exclusive imports,
so running with --emulator never imports (and thus never risks
constructing) anything hidapi-dependent.
The emulator's HTTP server runs for the whole sidecar process -- it is
the virtual USB bus. "Unplug" doesn't stop the server; it flips a flag so
the manager's find_device() returns nothing (exactly like a real
DeviceManager.enumerate() returning no devices), which drives the sidecar
into DEVICE_ABSENT with zero muxplex traffic, same as pulling a real
cable. "Plug" flips it back for a fresh ACTIVE bring-up.
Plane mode / fully offline development
To develop with no network at all (e.g. no Tailscale reachability), run a local muxplex server on the same machine as the sidecar:
- Before you lose connectivity, install tmux and muxplex on the Mac.
muxplex isn't assumed to be on PyPI -- install it from source/git, e.g.:
brew install tmux git clone <muxplex-repo-url> ~/dev/muxplex cd ~/dev/muxplex && uv sync
- Start a local muxplex bound to localhost (no TLS needed for localhost):
uv run muxplex --host 127.0.0.1 --port 8099
- Point the sidecar's config at it. Localhost muxplex still expects the
Authorizationheader to be present and non-empty (check your muxplex version's auth requirements), sokey_fileis still required byconfig.py-- a dummy file is fine if your local instance doesn't enforce a real key:{"server_url": "http://127.0.0.1:8099", "key_file": "~/.config/muxplex-deck/dummy_key"}
mkdir -p ~/.config/muxplex-deck && echo "dummy" > ~/.config/muxplex-deck/dummy_key
- Run the sidecar in emulator mode as above (
uv run muxplex-deck --emulator) and click "Plug in". You now have a complete, fully offline dev loop: virtual deck -> sidecar -> local muxplex -> real tmux sessions -- no hardware, no network, no remote server required.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file muxplex_deck-0.5.0.tar.gz.
File metadata
- Download URL: muxplex_deck-0.5.0.tar.gz
- Upload date:
- Size: 98.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac2c80625010d31479c1daaf36687cb7aed8a1d34cb021f8144c49f33bdd04da
|
|
| MD5 |
b0027e95ce82eb64ae4424060b431f93
|
|
| BLAKE2b-256 |
fe64291b829c31d3b010adc11b1f56c9c13f0e95218703a8cb4ee64e8b40ec6a
|
Provenance
The following attestation bundles were made for muxplex_deck-0.5.0.tar.gz:
Publisher:
publish.yml on bkrabach/muxplex-deck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
muxplex_deck-0.5.0.tar.gz -
Subject digest:
ac2c80625010d31479c1daaf36687cb7aed8a1d34cb021f8144c49f33bdd04da - Sigstore transparency entry: 2258061015
- Sigstore integration time:
-
Permalink:
bkrabach/muxplex-deck@e97df31a30e7b70b0c9d1e3d196a98a0ab199d8b -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/bkrabach
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e97df31a30e7b70b0c9d1e3d196a98a0ab199d8b -
Trigger Event:
push
-
Statement type:
File details
Details for the file muxplex_deck-0.5.0-py3-none-any.whl.
File metadata
- Download URL: muxplex_deck-0.5.0-py3-none-any.whl
- Upload date:
- Size: 111.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a69f8042aef6195e967c76d771ef532a523016e0ed6f7e6241b408dd70988d1
|
|
| MD5 |
812445a0539702453e44dcad1cadff2c
|
|
| BLAKE2b-256 |
ad2b5b7af289b142e16bd5801ccd3ea8fb90600f2871082eb6c23c4887396e8f
|
Provenance
The following attestation bundles were made for muxplex_deck-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on bkrabach/muxplex-deck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
muxplex_deck-0.5.0-py3-none-any.whl -
Subject digest:
5a69f8042aef6195e967c76d771ef532a523016e0ed6f7e6241b408dd70988d1 - Sigstore transparency entry: 2258061313
- Sigstore integration time:
-
Permalink:
bkrabach/muxplex-deck@e97df31a30e7b70b0c9d1e3d196a98a0ab199d8b -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/bkrabach
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e97df31a30e7b70b0c9d1e3d196a98a0ab199d8b -
Trigger Event:
push
-
Statement type: