- name
- ops-streamdeck
- description
- Stream Deck control skill for agents. Provides ability to restart streamdeck daemons, execute button tasks, query status, and manage Stream Deck hardware through a persistent daemon interface. Works with the streamdeck Python package for CLI operations and agent-driven automation.
- allowed-tools
- Bash, Read
- triggers
- ["restart streamdeck","streamdeck button","execute streamdeck task","streamdeck status","streamdeck daemon","start streamdeck","stop streamdeck","streamdeck health check","fix streamdeck buttons","streamdeck broken"]
- metadata
- {"short-description":"Stream Deck daemon control and automation"}
- provides
- ["ops-streamdeck"]
- composes
- ["task-monitor","agentic-evals"]
- disciplines
- ["developer-tooling","observability-operations"]
# Stream Deck Skill
Agent-accessible interface for Stream Deck control. Provides persistent daemon
management, button task execution, and status querying capabilities.
## Architecture
The streamdeck project has two components:
1. **CLI Tool** (`streamdeck` command) - Manual operations by users
2. **Daemon** (this skill) - Persistent service for agent automation
```
┌─────────────────────────────────────────────────────────────────────┐
│ AGENT / AUTOMATION │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Stream Deck Skill (this skill) │ │
│ │ • Restart daemon │ │
│ │ • Execute button tasks │◄──►│
│ │ • Query status │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Stream Deck Daemon (persistent service) │ │
│ │ • Manages Stream Deck hardware │ │
│ │ • Executes button press events │ │
│ │ • Provides status API │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Stream Deck CLI (manual operations) │ │
│ │ • User-invoked commands │ │
│ │ • Video chat, lights, monitoring, etc. │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
## Quick Start
### 1. Start the Daemon
```bash
# Start the streamdeck daemon in the background
./run.sh daemon start
# Or start in foreground for debugging
./run.sh daemon start --foreground
```
### 2. Use Agent Commands
```bash
# Restart the daemon
./run.sh restart
# Execute a button task
./run.sh button <button_id> [args...]
# Query daemon status
./run.sh status
# List available buttons
./run.sh list-buttons
# Get daemon logs
./run.sh logs
```
## Commands
### Daemon Management
| Command | Description |
| --------------------------- | ------------------------------------------ |
| `daemon start` | Start the streamdeck daemon (background) |
| `daemon start --foreground` | Start daemon in foreground (for debugging) |
| `daemon stop` | Stop the streamdeck daemon |
| `daemon restart` | Restart the streamdeck daemon |
| `daemon status` | Check if daemon is running |
| `daemon logs` | View daemon logs |
### Button Operations
| Command | Description |
| -------------------- | ------------------------------- |
| `button <id>` | Execute button press event |
| `button <id> --hold` | Execute button long-press event |
| `list-buttons` | List all available button IDs |
| `button-info <id>` | Get information about a button |
### Status Queries
| Command | Description |
| ------------------ | ------------------------- |
| `status` | Get overall daemon status |
| `status --json` | Get status in JSON format |
| `status --buttons` | Get button states |
### Safety Audits
| Command | Description |
| ---------------------- | --------------------------------------------------------------------------- |
| `audit-display-safety` | Non-mutating audit for meeting/display button routes and KDE scale hazards |
| `dynamic-stage-check` | Non-mutating compile/stage check for voice/chat dynamic page requests |
| `audio-switch` | Delegate meeting audio target switching to ops-workstation |
### Dynamic Pages
| Command | Description |
| --------------------- | --------------------------------------------------------------------------- |
| `dynamic-stage-check` | Compiles a semantic `streamdeck.dynamic_page_request.v1` request through the live streamdeck CLI and verifies staged artifacts without hardware effects |
### Meeting Audio
| Command | Description |
| -------------------------------------------- | ------------------------------------------------ |
| `audio-switch status` | Read Jabra USB, Jabra Bluetooth, earbuds BT, and wired availability |
| `audio-switch fallback` | Switch to the first available non-Jabra-USB meeting target |
| `audio-switch next` | Cycle to the next available meeting audio target |
| `audio-switch set jabra-usb|jabra-bt|earbuds-bt|wired` | Set a specific target through ops-workstation |
### Configuration
| Command | Description |
| ---------------------------- | -------------------------- |
| `config` | Show current configuration |
| `config --set <key> <value>` | Set configuration value |
| `config --get <key>` | Get configuration value |
### Prompts Management
| Command | Description |
| ---------------------------------------- | -------------------------------------------------------------- |
| `prompts list` | List available prompts |
| `prompts copy <name>` | Copy prompt content to clipboard |
| `prompts set-button <name> <btn>` | Configure a button for a prompt |
| `prompts set-button ... --switch-page N` | Configure button to switch page after press (1=Home, 2=Page 1) |
### Health Check & Auto-Fix
| Command | Description |
| -------------- | ---------------------------------------------- |
| `health-check` | Verify all services running and icons correct |
| `fix` | Auto-repair broken button configurations |
```bash
# Check if buttons are configured correctly
./run.sh health-check
# Auto-fix broken buttons (stops service, edits config, restarts)
./run.sh fix
```
## CRITICAL: Config File Behavior
The Stream Deck UI (`streamdeck.service`) saves its in-memory state to `~/.streamdeck_ui.json` every ~30 seconds.
**NEVER edit the config while the service is running!** Your changes will be overwritten.
### Correct Procedure for Config Changes
```bash
# 1. STOP the service first
systemctl --user stop streamdeck.service
# 2. Edit the config file
vim ~/.streamdeck_ui.json
# 3. START the service (loads your changes into memory)
systemctl --user start streamdeck.service
```
### Why Socket Updates Are Temporary
The `icon_updater.py` sends updates via Unix socket to change button icons in real-time.
These updates modify the **in-memory state** only. When the service saves its state
(every ~30 seconds), it writes the in-memory values back to the config file.
This means:
- Socket updates appear immediately on the Stream Deck
- But if the config file had different values, those get overwritten
- The next time the service restarts, it loads from the config file
**Use `./run.sh fix` to safely update button configurations.**
## Workstation Display Safety
Display topology and KDE Global Scale are global workstation state, not normal
button state. A Stream Deck meeting button must not route through commands that
can change monitor topology, mirror outputs, or write KDE scale settings unless
the human has explicitly authorized a recovery operation.
For meeting/display button work:
- Treat `xrandr --output`, `kscreen-doctor output.*`, `nvidia-settings --assign`,
`nvidia-settings --load-config-only`, `kwriteconfig`, `kdeglobals`,
`ScreenScaleFactors`, `forceFontDPI`, and KDE Global Scale writes as hazardous.
- Do not treat a blank command as a working disabled state. If a disabled button
must become usable, wire it to a non-mutating script first and prove the command
survived the Stream Deck daemon save interval.
- Do not run a physical button or command path as proof until a static
`audit-display-safety` readback has passed.
- If display recovery is needed, use the separate workstation rollback plan and
do not fold recovery commands into Stream Deck meeting automation.
### Teleprompter (DP-3) Heal — authorized display automation
The middle monitor is a camera monitor that is manually power-cycled and
cannot stay powered (operator, 2026-09-17). KScreen leaves DP-3 disabled
across every disconnect/reconnect, so two heal paths are explicitly
authorized. Both restore ONLY the known-good state (DP-3 enabled,
1920x1080@60 at 3840,0) and never touch topology, mirroring, scale, or other
outputs — do not remove or extend them past that boundary:
1. **Video (meeting) button self-heal** — `meeting-mode.sh` in
`~/workspace/streamdeck` launches `scripts/teleprompter-restore.sh`
asynchronously (`setsid`, fire-and-forget). The restore polls up to ~30s
while DP-3's link is down and re-enables it the moment it returns. It
MUST stay async: when it ran synchronously, every press with the camera
monitor off stalled ~30s before any lights/audio/layout feedback, and
double-presses cancelled out — the button appeared dead (observed
2026-09-18). State guards must stay word-matched (`grep -w`):
"disconnected" contains the substring "connected".
2. **Hotplug watcher** — systemd --user unit `teleprompter-watch.service`
(source `~/workspace/streamdeck/scripts/`, symlinked from
`~/.config/systemd/user/`). Polls DRM connector status every 2s and runs
the same restore script whenever a connector comes back, so manually
powering the camera monitor on heals within ~2s with no button press.
Known edge: an off→on flip inside one 2s poll gap is missed; the next event
or Video press heals. Disable the watcher with
`systemctl --user disable --now teleprompter-watch.service`.
Receipt: `/tmp/teleprompter-restore.json`
(`streamdeck.teleprompter_restore.v1`); log: `/tmp/teleprompter-restore.log`.
## Dynamic Page Safety
Voice commands and SPARTA Explorer chat must enter the Stream Deck through the
semantic request boundary, not by emitting raw button commands. The safe request
shape is `streamdeck.dynamic_page_request.v1`; it names source, request id,
intent text, context refs, confidence, lifetime, and optional catalog recipe id.
The compiler selects a catalog recipe and emits a staged
`streamdeck.dynamic_page_manifest.v1` artifact. Button commands in that artifact
must be dispatcher bindings such as:
```text
streamdeck-cli action invoke --binding <binding-id>
```
The request boundary rejects raw executable fields such as `buttons`, `command`,
`keys`, or `write`, and rejects workstation primitives such as `xrandr`,
`kscreen`, `nvidia-settings`, KDE scale terms, process kill commands, and shell
composition tokens.
Use this non-mutating proof command after dynamic-page or voice/SPARTA page
changes:
```bash
./run.sh dynamic-stage-check
```
It calls `~/workspace/streamdeck/.venv/bin/streamdeck-cli page
stage-request`, writes artifacts under `/tmp/ops-streamdeck-dynamic-stage-check`
unless `STREAMDECK_DYNAMIC_STAGE_OUTPUT` is set, and requires
`external_effects=false`, `recipe_id=sparta_review_controls`, two dispatcher
bindings, and an inspectable staged manifest. This does not prove physical
button rendering, action-dispatch semantics, or deployment to hardware.
## Configuration
The daemon reads configuration from:
1. **Environment Variables:**
- `STREAMDECK_DAEMON_PORT` - Port for daemon API (default: 48970)
- `STREAMDECK_DAEMON_HOST` - Host for daemon API (default: 127.0.0.1)
- `STREAMDECK_LOG_LEVEL` - Log level (DEBUG, INFO, WARNING, ERROR)
2. **Config File:** `~/.streamdeck/daemon.json`
### Example Config
```json
{
"daemon": {
"port": 48970,
"host": "127.0.0.1",
"log_level": "INFO"
},
"buttons": {
"0": {
"name": "Video Chat Start",
"command": "streamdeck videochat start"
},
"1": {
"name": "Video Chat Stop",
"command": "streamdeck videochat stop"
},
"2": {
"name": "Lights Toggle",
"command": "streamdeck lights toggle"
},
"3": {
"name": "Time Tracker Toggle",
Auf GitHub ansehen