Plex
Plex streams in the Omarchy bar: who is watching what, whether the server is transcoding for them, and whether that transcode is keeping up.
What it shows
One row per stream:
- What is playing — series and episode code, or film and year — and how far in.
- Who is watching it, and on which client.
- How it is reaching them: LAN, WAN, or relayed.
- The playback decision: Direct Play, Direct Stream, or Transcode with the codec change.
- Bandwidth, and for a transcode, whether it is keeping up.
The bar carries the number of active streams and says nothing at all when nobody is watching. It turns urgent only when the server cannot be reached; a transcode losing ground gets a warmer tint, because the stream is still playing and may recover.
The two things it is careful about
A direct stream is not a transcode. Plex reports the decision in two places. Part.decision says what happened to the file, TranscodeSession.videoDecision says what happened to the video. A file marked transcode whose video is copy is a direct stream — only the container or the audio changed, the video was passed through untouched, and the server is barely working. Counting those as transcodes makes a quiet server look busy. This plugin separates them.
A throttled transcode is a healthy one. speed is a multiple of realtime, so anything under 1.0 is losing ground — except that a transcoder which has run far enough ahead deliberately idles, and reports a low speed precisely because everything is fine. The panel reads throttled and speed together, and only says "falling behind" when the transcoder is actually working and still losing.
That second one is the whole reason this exists in the bar rather than in a dashboard: "someone is about to buffer" is worth knowing in the second it becomes true.
Sign in
Press l in the panel, or run:
bash ~/.config/omarchy/plugins/ianswope.plex/bin/omarchy-plex-login
A browser opens on Plex's own sign-in page, you click Allow, and that is the whole configuration. Nothing is typed, no token is hunted for, and the script never sees your password — it is the same device-link flow Plex's TV and mobile apps use. It then lists the servers on your account, picks the LAN address in preference to a remote one and a remote one in preference to a relay, and writes the config itself.
Over ssh with no display, set OMARCHY_PLEX_NO_BROWSER=1 and it prints the URL to open somewhere else instead.
What it writes — ~/.config/omarchy-plex/config.json, mode 600:
{
"url": "http://<plex-host>:32400",
"token": "<per-server access token>",
"client_id": "<stable device id>",
"stop_reason": "Stopped from the Omarchy bar"
}
The token stored is the server's access token, not the account token that authorised the sign-in. It reaches one server and nothing else — the account token is used once, in memory, to ask which servers exist, and is never written down. The config lives outside the plugin directory so it is never in the repo, and outside the bar's settings so it is never in shell.json.
stop_reason is the message a viewer sees when you stop their stream. It lives here, in a file, rather than in a settings row — the wording lands on someone else's screen mid-show and deserves to be written once, deliberately.
If you would rather not sign in at all, the two fields the plugin actually needs are url and token; any Plex token you already have works.
Stopping a stream
x on a selected row, or right-click it. The panel confirms first, naming what is playing and who is watching.
This ends playback server-side and puts your reason on their screen. It is deliberately the only action: Plex's remote-control protocol (play, pause, seek) only reaches clients that advertise themselves as controllable, and most of the ones people actually watch on do not. An action that works on half your devices is worse than one that always works.
Keys
| Key | Does |
|---|---|
x |
Stop the selected stream, after confirming |
r |
Refresh now |
l |
Sign in to Plex |
e |
Open the config |
| arrows | Move the cursor |
| Escape | Close |
Right-click the bar icon to refresh without opening the panel.
Install
Requires Omarchy 4 ("Quattro", Quickshell bar), plus curl and jq.
# 1. Drop the plugin in place — the directory name must be the manifest id
mkdir -p ~/.config/omarchy/plugins
cp -r omarchy-plex ~/.config/omarchy/plugins/ianswope.plex
# 2. Sign in — opens a browser, click Allow, done
bash ~/.config/omarchy/plugins/ianswope.plex/bin/omarchy-plex-login
# 3. Prove the data layer before touching the bar
bash ~/.config/omarchy/plugins/ianswope.plex/bin/omarchy-plex-status
# 4. Add {"id": "ianswope.plex"} to bar.layout.right in ~/.config/omarchy/shell.json,
# then restart the shell
omarchy-restart-shell
Step 3 is the one worth not skipping: it prints the same JSON the panel reads, so a bad token or an unreachable host is one line of output rather than a widget that silently shows nothing.
A bar widget goes in bar.layout.right, not the top-level plugins[] array — that array is for overlay plugins, and a bar widget listed there never appears and logs nothing. omarchy-restart-shell, not omarchy-refresh-shell: refresh re-reads config without reloading plugin QML.
Remove
# 1. Take the widget out of bar.layout.right in ~/.config/omarchy/shell.json
# 2. Delete the plugin and its config
rm -rf ~/.config/omarchy/plugins/ianswope.plex
rm -rf ~/.config/omarchy-plex
omarchy-restart-shell
Removing the config directory is what revokes this machine's access. The token it held reaches only your Plex server, but it stays valid until you also remove the device: plex.tv → Account → Authorized Devices → Omarchy Plex. Do that if the machine is leaving your hands.
Nothing else is written anywhere: no state directory, no cache, no files on the Plex server, and no change to any Plex setting.
What it never does
It does not start playback, change quality, alter your library, or write anything to the server. The only request that changes state is the session termination behind x, and it is confirmed first.
The token is passed to curl through stdin rather than on the command line, because arguments are visible in /proc to every user on the machine.
curl is given -q as its first option, so ~/.curlrc is never read. That file is writable by anything running as this user, and a line in it can add a second url = to the request — which the X-Plex-Token header written on stdin would then follow. A poll that runs every few seconds makes that a standing leak rather than a one-off.
Every binary on the credential path is an absolute /usr/bin path rather than a name resolved through PATH: a shadow curl planted ahead of the real one would receive the token, and a shadow dd reads the config it came from.
Tests
node test/model-test.js # what the panel decides, on plain data
bash test/path-test.sh # the helpers, under a hostile PATH
model-test.js covers the decision and throttle logic above, the formatting, the health policy, and the input handling for text and ids that arrive from the server. path-test.sh plants a shim for every binary the helpers use earlier in PATH and proves the status helper still runs and reaches none of them.
Licence
MIT.