# Example modules.yaml — copy to ~/.config/streamdeck-go/modules.yaml # # Required Slack token scopes: users.profile:write, users:write, dnd:write # Export your token: export SLACK_TOKEN="xoxp-..." modules: slack: set_status: params: emoji: ":speech_balloon:" text: "In a meeting" expiry: "1h" exec: | curl -s -X POST https://slack.com/api/users.profile.set \ -H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{"profile":{"status_emoji":"{{.emoji}}","status_text":"{{.text}}","status_expiration":{{expiry .expiry}}}}' clear_status: exec: | curl -s -X POST https://slack.com/api/users.profile.set \ -H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{"profile":{"status_emoji":"","status_text":"","status_expiration":0}}' set_presence: params: presence: "away" exec: | curl -s -X POST https://slack.com/api/users.setPresence \ -H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{"presence":"{{.presence}}"}' snooze: params: minutes: "60" exec: | curl -s -X POST https://slack.com/api/dnd.setSnooze \ -H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{"num_minutes":{{.minutes}}}' go_offline: exec: | curl -s -X POST https://slack.com/api/users.setPresence \ -H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{"presence":"away"}' && \ curl -s -X POST https://slack.com/api/users.profile.set \ -H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{"profile":{"status_emoji":":dumpsterfire:","status_text":"Offline","status_expiration":0}}' end_snooze: exec: | curl -s -X POST https://slack.com/api/dnd.endSnooze \ -H "Authorization: Bearer {{env "SLACK_TOKEN"}}" # Pomodoro / focus timer — the `flow` CLI. # # Absolute path required (minimal service PATH). Apple Silicon Homebrew installs # live at /opt/homebrew/bin/flow — set that in ~/.config/streamdeck-go/.env: # FLOW_CMD=/opt/homebrew/bin/flow # # Control verbs (start/pause/resume/toggle/skip/stop/reset/gui) run on key press. # `status` prints one line like `state=running phase=work remaining=24:12` for # icon-swap poll blocks (match: "state=running"). # # The LIVE countdown label uses a key's `text_command` field directly (not a # module), e.g. text_command: "/usr/local/bin/flow status --short" which prints # a two-line label like WORK\n24:12 (an actual newline between the lines). flow: start: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} start' } pause: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} pause' } resume: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} resume' } toggle: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} toggle' } skip: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} skip' } stop: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} stop' } reset: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} reset' } gui: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} gui' } status: { exec: '{{envDefault "FLOW_CMD" "/usr/local/bin/flow"}} status' } # OBS Studio — media player, streaming, and scene/transition control via obs-cmd # # Requires: obs-cmd (https://github.com/grigio/obs-cmd) # macOS (Intel): brew install grigio/obs-cmd/obs-cmd → /usr/local/bin/obs-cmd # macOS (Apple Silicon): brew install grigio/obs-cmd/obs-cmd → /opt/homebrew/bin/obs-cmd # Linux: cargo install obs-cmd → ~/.cargo/bin/obs-cmd # Linux (binary): download from GitHub releases, e.g. /usr/local/bin/obs-cmd # # OBS WebSocket must be enabled: Tools → WebSocket Server Settings (on by default in OBS 28+) # # Add to ~/.config/streamdeck-go/.env: # OBS_WEBSOCKET_PASSWORD=your-password # OBS_HOST=localhost (optional, default: localhost) # OBS_PORT=4455 (optional, default: 4455) # OBS_CMD=/opt/homebrew/bin/obs-cmd (optional, default: /usr/local/bin/obs-cmd) # set this on Apple Silicon or Linux cargo installs obs: play: params: source: "Media Source" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} media-input play "{{.source}}" pause: params: source: "Media Source" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} media-input pause "{{.source}}" stop: params: source: "Media Source" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} media-input stop "{{.source}}" restart: params: source: "Media Source" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} media-input restart "{{.source}}" toggle_record: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} recording toggle toggle_record_pause: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} recording toggle-pause is_recording_paused: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} recording status toggle_stream: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} streaming toggle scene_switch: params: scene: "Scene 1" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene switch "{{.scene}}" toggle_mute: params: source: "Mic/Aux" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} input toggle-mute "{{.source}}" is_recording: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} recording status is_streaming: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} streaming status # Change the active transition type. Common values: Fade, Cut, Slide, Swipe, Stinger. # Must match a transition that exists in your OBS profile (see Scene Transitions panel). set_transition: params: transition: "Fade" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene transition-set "{{.transition}}" # Set the active transition duration in milliseconds. set_transition_duration: params: duration: "300" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene transition-duration {{.duration}} # Toggle Studio Mode (preview + program with a Transition button). studio_mode_toggle: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene studio-mode-toggle # In Studio Mode, set the scene shown on the preview side. preview_scene: params: scene: "Scene 1" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene preview-set "{{.scene}}" # The Studio Mode "Transition" button — push preview to program using the current transition. take: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene studio-mode-transition # Fire the current transition without changing scenes (e.g. to re-trigger a stinger). transition_trigger: exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene transition-trigger # Switch scene using a specific transition, overriding OBS's current transition for this cut. # Chains set_transition + transition-duration + scene switch in one key press. scene_switch_with_transition: params: scene: "Scene 1" transition: "Fade" duration: "300" exec: | {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene transition-set "{{.transition}}" && \ {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene transition-duration {{.duration}} && \ {{envDefault "OBS_CMD" "/usr/local/bin/obs-cmd"}} --websocket obsws://{{envDefault "OBS_HOST" "localhost"}}:{{envDefault "OBS_PORT" "4455"}}/{{env "OBS_WEBSOCKET_PASSWORD"}} scene switch "{{.scene}}" # --------------------------------------------------------------------------- # Camera — cameractrls (https://github.com/soyersoyer/cameractrls), Linux only. # # Drives any UVC webcam's V4L2 controls (zoom, pan/tilt, focus, exposure, white # balance, brightness/contrast/...). Install: pacman -S cameractrls / pip. # # Device: set CAMERA_DEVICE in ~/.config/streamdeck-go/.env to the stable path, e.g. # CAMERA_DEVICE=/dev/v4l/by-id/usb-046d_Logitech_BRIO_XXXX-video-index0 # (defaults to /dev/video0; any function also takes a per-key `device` param). # # Discover control names/ranges: cameractrls -d /dev/video0 -l # Note: cameractrls exits 0 even for unknown controls, so poll blocks must use # the `is` function (which greps `-l` output) rather than exit codes. camera: # Set one or more controls, comma-separated (eg. "zoom_absolute=150,contrast=140"). # Buttons work too: "preset=save_1", "preset=load_1", "color_preset=vivid". set: params: device: "" controls: "zoom_absolute=100" exec: /usr/bin/cameractrls -d {{if .device}}{{.device}}{{else}}{{envDefault "CAMERA_DEVICE" "/dev/video0"}}{{end}} -c "{{.controls}}" 2>/dev/null # Flip a control between two values (a <-> b). Works for 0/1 and menu controls. toggle: params: device: "" control: auto_exposure a: aperture_priority_mode b: manual_mode exec: | cur=$(/usr/bin/cameractrls -d {{if .device}}{{.device}}{{else}}{{envDefault "CAMERA_DEVICE" "/dev/video0"}}{{end}} -l 2>/dev/null | sed -n 's/^ {{.control}} = \([^[:space:]]*\).*/\1/p') if [ "$cur" = "{{.a}}" ]; then v="{{.b}}"; else v="{{.a}}"; fi /usr/bin/cameractrls -d {{if .device}}{{.device}}{{else}}{{envDefault "CAMERA_DEVICE" "/dev/video0"}}{{end}} -c "{{.control}}=$v" 2>/dev/null # Step a numeric control by delta (negative to decrease), clamped to its min/max. # `extra` is applied first on the same call — eg. "auto_exposure=manual_mode" # so stepping exposure also takes the camera out of auto. adjust: params: device: "" control: contrast delta: "16" extra: "" exec: | line=$(/usr/bin/cameractrls -d {{if .device}}{{.device}}{{else}}{{envDefault "CAMERA_DEVICE" "/dev/video0"}}{{end}} -l 2>/dev/null | grep '^ {{.control}} = ') cur=$(printf '%s\n' "$line" | sed -n 's/^ [a-z0-9_]* = \([-0-9]*\).*/\1/p') min=$(printf '%s\n' "$line" | sed -n 's/.*min: \([-0-9]*\).*/\1/p') max=$(printf '%s\n' "$line" | sed -n 's/.*max: \([-0-9]*\).*/\1/p') [ -n "$cur" ] || exit 1 v=$((cur + {{.delta}})) [ -n "$min" ] && [ "$v" -lt "$min" ] && v=$min [ -n "$max" ] && [ "$v" -gt "$max" ] && v=$max ctl="{{.control}}=$v" [ -n "{{.extra}}" ] && ctl="{{.extra}},$ctl" /usr/bin/cameractrls -d {{if .device}}{{.device}}{{else}}{{envDefault "CAMERA_DEVICE" "/dev/video0"}}{{end}} -c "$ctl" 2>/dev/null # Poll helper for toggle keys: exit 0 when control == value. is: params: device: "" control: auto_exposure value: aperture_priority_mode exec: /usr/bin/cameractrls -d {{if .device}}{{.device}}{{else}}{{envDefault "CAMERA_DEVICE" "/dev/video0"}}{{end}} -l 2>/dev/null | grep -q '^ {{.control}} = {{.value}}[[:space:]]'