Typed Python SDK for building MiraBox Stream Dock plugins
Project description
English · Русский
MiraBox Stream Dock SDK
A typed Python SDK for building MiraBox Stream Dock plugins
Build reusable actions for keys, touch panels, and dials
without hand-writing the Stream Dock WebSocket protocol.
Quick start · PyPI · Example plugin · Protocol · API · Development
About
mirabox-stream-dock-sdk provides the protocol, runtime, and browser-side tools
needed to build Python plugins for MiraBox Stream Dock. It validates launch
arguments and incoming messages, creates one typed action instance per visible
control, dispatches lifecycle events, and serializes commands back to the
Stream Dock application.
The SDK was originally developed as part of a Stream Dock plugin. It was later extracted into a standalone project so the protocol and runtime could be reused across plugins, tested independently, and evolved as a public package. The SDK will continue to be improved as it is used in real plugins and more Stream Dock behavior is verified.
[!IMPORTANT] The project is currently in the
0.xseries. It is ready for experimentation and real plugin development, but public APIs may evolve between minor releases before1.0.
[!NOTE] This is an unofficial community project and is not affiliated with or endorsed by MiraBox, HotSpot, or Elgato. The callback name
connectElgatoStreamDeckSocketis retained because Stream Dock uses it for Property Inspector compatibility.
Table of contents
- Features
- How it works
- Requirements
- Installation
- Quick start
- Property Inspector client
- Counter example plugin
- Protocol basis
- API overview
- Errors and unknown events
- Logging
- Project structure
- Development
- Releasing
Features
| Feature | What it provides | |
|---|---|---|
| 🧩 | Typed protocol | Dataclass models for registration, commands, and key, touch, dial, device, application, and settings events. |
| 🧭 | Precise validation | Malformed payloads report the event name and exact JSON field path that failed validation. |
| 🎛️ | Action runtime | One action instance per Stream Dock context, declarative UUID registration, and automatic lifecycle dispatch. |
| 🔌 | WebSocket transport | Registration, message parsing, command serialization, logging, and graceful shutdown. |
| 🗃️ | Typed settings | Pluggable codecs for action settings, global settings, and Property Inspector messages. |
| 🖥️ | Property Inspector | A versioned, dependency-free JavaScript client with connection state, events, settings helpers, and queued startup messages. |
| 🧰 | Plugin services | Start and stop plugin-owned background services in a predictable order. |
| 📦 | Distribution tooling | A CLI resource copier, PyInstaller example, package verification, CI, and Trusted Publishing workflow. |
| 🛡️ | Forward compatibility | Unknown but valid events can be preserved as UnknownStreamDockEvent instead of breaking the plugin. |
How it works
flowchart LR
App["MiraBox Stream Dock<br>Windows"] <-->|"WebSocket · JSON"| Connection["WebSocketStreamDockConnection"]
Connection --> Runtime["StreamDockPlugin<br>event dispatcher"]
Registry["ActionRegistry<br>UUID → Action class"] --> Runtime
Runtime --> Actions["Action instances<br>one per context"]
Runtime --> Services["Lifecycle services"]
PI["Property Inspector<br>HTML / JavaScript"] <-->|"settings and messages"| App
Client["MiraBoxPropertyInspector<br>browser client"] --> PI
Stream Dock starts the packaged plugin executable with the WebSocket port,
plugin UUID, registration event, and application metadata. run_plugin_cli()
parses those arguments, while StreamDockPlugin registers the plugin and maps
incoming events to the appropriate action instance.
Requirements
- Python
3.11+; - MiraBox Stream Dock
2.10.179.426or newer (declared minimum); websocket-client>=1.8,<2(installed automatically);- Windows to run Stream Dock and package a standalone plugin with PyInstaller.
The SDK's Stream Dock integration has been manually verified with Stream Dock
3.10.203.0701.
The SDK itself and its test suite can be developed on Windows, Linux, or WSL.
The final .exe must be built on Windows because PyInstaller is not a
cross-compiler.
Installation
Install the released package from PyPI:
python -m pip install mirabox-stream-dock-sdk
To work on the SDK from source:
git clone https://github.com/Nekit678/MiraboxStreamDockSDK.git
cd MiraboxStreamDockSDK
python -m venv .venv
Windows PowerShell
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
Linux / WSL
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
Quick start
Define dependencies shared by your action instances, register each action UUID,
and return a configured StreamDockPlugin from the application factory:
from __future__ import annotations
from dataclasses import dataclass
from mirabox_sdk import (
Action,
ActionRegistry,
JsonObject,
KeyDownEvent,
PluginLaunchArguments,
StreamDockPlugin,
StreamDockSender,
WebSocketStreamDockConnection,
WillAppearEvent,
run_plugin_cli,
)
ACTION_UUID = "com.example.counter.increment"
@dataclass(frozen=True, slots=True)
class Dependencies:
stream_dock: StreamDockSender
registry: ActionRegistry[Dependencies] = ActionRegistry()
@registry.register(ACTION_UUID)
class CounterAction(Action[JsonObject, Dependencies]):
def _render(self) -> None:
count = self.settings.get("count", 0)
self.set_title(str(count if type(count) is int else 0))
def on_will_appear(self, _event: WillAppearEvent) -> None:
self._render()
def on_key_down(self, _event: KeyDownEvent) -> None:
count = self.settings.get("count", 0)
self.set_settings({"count": (count if type(count) is int else 0) + 1})
self._render()
def build_application(arguments: PluginLaunchArguments) -> StreamDockPlugin[Dependencies]:
connection = WebSocketStreamDockConnection(arguments.port)
return StreamDockPlugin(
arguments,
stream_dock=connection,
action_registry=registry,
action_dependencies=Dependencies(connection),
)
if __name__ == "__main__":
raise SystemExit(run_plugin_cli(build_application))
The exact same action UUID must appear in the plugin's manifest.json. Stream
Dock creates and removes action contexts through willAppear and
willDisappear; the runtime manages the corresponding Python instances.
Action callbacks
Override only the callbacks an action needs:
| Input or lifecycle | Action callback |
|---|---|
| Action becomes visible or disappears | on_will_appear, on_will_disappear |
| Key press or release | on_key_down, on_key_up |
| Touch panel tap | on_touch_tap |
| Dial press, release, or rotation | on_dial_down, on_dial_up, on_dial_rotate |
| Settings or title parameters change | on_did_receive_settings, on_title_parameters_did_change |
| Property Inspector opens, closes, or sends data | on_property_inspector_did_appear, on_property_inspector_did_disappear, on_send_to_plugin |
| Device, application, and wake-up notifications | on_device_did_connect, on_device_did_disconnect, on_application_did_launch, on_application_did_terminate, on_system_did_wake_up |
Action helper methods cover the common outbound commands: set_title(),
set_image(), set_state(), set_settings(), get_settings(), show_ok(),
show_alert(), open_url(), log_message(), and
send_to_property_inspector().
Typed settings
Actions use JSON objects by default. To work with an application-specific type,
provide a JsonCodec on the action class:
from dataclasses import dataclass
from mirabox_sdk import Action, FunctionalJsonCodec, JsonObject
@dataclass(frozen=True, slots=True)
class CounterSettings:
count: int
def decode_settings(value: JsonObject) -> CounterSettings:
count = value.get("count", 0)
if type(count) is not int:
raise ValueError("count must be an integer")
return CounterSettings(count)
COUNTER_SETTINGS_CODEC = FunctionalJsonCodec(
decoder=decode_settings,
encoder=lambda value: {"count": value.count},
)
class CounterAction(Action[CounterSettings, Dependencies]):
settings_codec = COUNTER_SETTINGS_CODEC
The codec boundary verifies that encoded values are valid JSON. Decode errors are wrapped with the relevant event name and settings path.
Property Inspector client
Copy the JavaScript client shipped with the installed SDK into the plugin bundle:
mirabox-sdk copy-property-inspector \
com.example.counter.sdPlugin/property-inspector
The command refuses to overwrite a different copy by default. Pass --force
when intentionally updating the bundled client.
Load it before the action-specific script:
<script src="mirabox-sdk.js"></script>
<script src="counter.js"></script>
Stream Dock invokes the compatibility callback automatically. The action script
uses the shared client through window.MiraBoxPropertyInspector:
const client = window.MiraBoxPropertyInspector;
client.on("connected", ({ settings }) => {
console.log("Current settings", settings);
});
client.on("didReceiveSettings", ({ payload }) => {
console.log("Updated settings", payload.settings);
});
client.sendToPlugin({ event: "refresh" });
client.updateSettings({ mode: "toggle" });
The client exposes on(), off(), send(), sendToPlugin(), setSettings(),
updateSettings(), and getSettings(), plus connection and registration state.
Messages sent while the WebSocket is connecting are queued until it opens.
Counter example plugin
examples/counter_plugin
is a complete plugin rather
than an isolated code fragment. It includes:
- a package with a registered counter action;
- a Property Inspector that can reset the counter;
- a valid
.sdPluginbundle and manifest; - SVG assets and a PyInstaller specification;
- tests for the plugin behavior.
Build its executable on Windows:
python -m pip install pyinstaller
python -m PyInstaller --clean --noconfirm examples/counter_plugin/build.spec
Copy-Item dist\CounterPlugin.exe `
examples\counter_plugin\com.example.counter.sdPlugin\
Copy the resulting com.example.counter.sdPlugin directory to
%APPDATA%\HotSpot\StreamDock\plugins\ and restart Stream Dock. See the
example guide
for a source-run command and
the complete packaging flow.
Protocol basis
This package is an independent, typed Python implementation of the WebSocket / JSON plugin API published by MiraBox. The primary upstream sources are:
- the official StreamDock Plugin SDK repository, including its Python template;
- the official registration procedure, received events, and events sent reference;
- the official
manifest.jsonreference and Property Inspector guide; - the upstream template overview on DeepWiki for secondary, generated explanations of the repository;
- the Space Platform for publishing completed Stream Dock plugins.
The local protocol map connects each supported wire event and command to its Python model or helper and calls out behavior verified in Stream Dock but not currently listed in the upstream event reference. When the published documentation and observed runtime behavior differ, tests record the behavior implemented by this SDK.
API overview
| Area | Public API |
|---|---|
| Runtime | Action, ActionRegistry, StreamDockPlugin, LifecycleService |
| Connection | WebSocketStreamDockConnection, StreamDockConnection, StreamDockSender, StreamDockListener |
| Launch and registration | PluginLaunchArguments, registration dataclasses, parse_plugin_cli_arguments, run_plugin_cli |
| Input events | Typed models for key, touch, dial, settings, Property Inspector, device, application, and system events |
| Output commands | Registration, settings, title, image, state, feedback, URL, log, and Property Inspector command models |
| Application data | JsonCodec, FunctionalJsonCodec, JsonObjectCodec, typed encode/decode helpers |
| Resources | copy_property_inspector_client, property_inspector_client_bytes, mirabox-sdk CLI |
| Parsing | parse_stream_dock_event, parse_registration_info, typed protocol errors |
| Logging | configure_logging with isolated console, file, and disable controls |
The supported public surface is exported from mirabox_sdk. Objects from
individual modules should be treated as implementation details unless they are
also exported there.
Errors and unknown events
| Exception | Meaning |
|---|---|
InvalidPluginLaunchArgumentsError |
Stream Dock did not provide valid executable arguments. |
InvalidRegistrationInfoError |
The registration metadata JSON has an invalid field. |
MalformedEventError / InvalidFieldError |
A known event is malformed; the error includes its JSON path. |
UnsupportedEventError |
An unknown event was parsed with allow_unknown=False. |
JsonCodecDecodeError |
Plugin-owned settings or messages could not be decoded. |
JsonCodecEncodeError |
A codec produced a value that cannot be sent as JSON. |
By default, parse_stream_dock_event() preserves an unknown but structurally
valid envelope as UnknownStreamDockEvent. This lets the SDK tolerate protocol
extensions while known events remain strictly validated.
Logging
SDK logging is disabled by default: it does not propagate to the application's
root logger and does not create a log file. Enable diagnostics explicitly before
calling run_plugin_cli():
from mirabox_sdk import configure_logging
configure_logging(level="INFO")
When enabled without a file, the destination is stderr. To write UTF-8 logs to a file, pass a path; missing parent directories are created automatically. File logging rotates at 5 MiB with three backups by default:
from pathlib import Path
from mirabox_sdk import configure_logging
configure_logging(
level="DEBUG",
log_file=Path.home() / ".mirabox-counter" / "plugin.log",
max_bytes=5 * 1024 * 1024,
backup_count=3,
)
Adjust max_bytes and backup_count for the plugin's needs. Set
max_bytes=0 only when intentionally requesting an unbounded file.
include_payload=True adds the complete inbound and outbound protocol message
to DEBUG records. Payloads may contain tokens, settings, and other secrets, so
enable this option only temporarily in a trusted development environment. Omit
the option (its default is False) to return to redacted payloads while keeping
other diagnostics enabled.
configure_logging(
level="DEBUG",
log_file=Path.home() / ".mirabox-counter" / "plugin.log",
include_payload=True,
)
Repeated calls replace the handler previously installed by
configure_logging(), so the level or destination can be changed without
duplicating messages. Return the SDK to its default silent state with:
configure_logging(enabled=False)
INFO records contain protocol direction, event, and context. DEBUG adds
routing metadata. Message payloads remain redacted unless include_payload=True
is explicitly configured.
Handlers installed manually by the application remain its responsibility.
Project structure
MiraboxStreamDockSDK/
├── pyproject.toml # Package metadata and tool configuration
├── src/mirabox_sdk/
│ ├── action.py # Reusable action base class
│ ├── action_registry.py # Action UUID registry
│ ├── commands.py # Typed outbound commands
│ ├── events.py # Typed inbound event models
│ ├── parser.py # Strict wire-message parser
│ ├── plugin.py # Runtime and lifecycle dispatcher
│ ├── connection.py # WebSocket transport
│ ├── logging_config.py # Isolated SDK logging configuration
│ └── property_inspector/ # Browser-side SDK resource
├── examples/counter_plugin/ # Complete buildable plugin
├── tests/ # SDK and release-tool tests
├── scripts/ # Version and distribution verification
└── .github/workflows/ # CI and Trusted Publishing release jobs
Development
Install the development dependencies, then run the same checks as CI:
python -m unittest discover -s tests -v
PYTHONPATH=examples/counter_plugin/src \
python -m unittest discover -s examples/counter_plugin/tests -v
python -m compileall -q src tests scripts examples
ruff check src tests scripts examples
ruff format --check src tests scripts examples
python -m build
python scripts/verify_distribution.py dist
python -m twine check dist/*
The test suite uses fake connections and protocol messages; it does not require a running Stream Dock instance. CI runs the SDK on Linux and Windows across all supported Python versions.
Contributions are welcome. Please read CONTRIBUTING.md before submitting a change, and include the Stream Dock version and a regression test when changing observed protocol behavior.
Releasing
Releases are built from version tags, published to PyPI through Trusted Publishing, and attached to a generated GitHub Release. The required one-time configuration and release checklist are documented in RELEASING.md.
License
Distributed under the MIT License.
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 mirabox_stream_dock_sdk-0.3.1.tar.gz.
File metadata
- Download URL: mirabox_stream_dock_sdk-0.3.1.tar.gz
- Upload date:
- Size: 82.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
065b5d89237c7d2e72c0f02c2061eb8f547f3eac07443fe237ccd31c9af7219c
|
|
| MD5 |
ea82a16abe993a6f43768cba0dd01180
|
|
| BLAKE2b-256 |
e723a5d5cba5a8867b5ef2c3367f50cd1af38cee32fbbd52eef854caac7aeeea
|
Provenance
The following attestation bundles were made for mirabox_stream_dock_sdk-0.3.1.tar.gz:
Publisher:
release.yml on Nekit678/MiraboxStreamDockSDK
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mirabox_stream_dock_sdk-0.3.1.tar.gz -
Subject digest:
065b5d89237c7d2e72c0f02c2061eb8f547f3eac07443fe237ccd31c9af7219c - Sigstore transparency entry: 2201149512
- Sigstore integration time:
-
Permalink:
Nekit678/MiraboxStreamDockSDK@946c6dd60eae3ef28e731fc023c6188fb69b2e88 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/Nekit678
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@946c6dd60eae3ef28e731fc023c6188fb69b2e88 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mirabox_stream_dock_sdk-0.3.1-py3-none-any.whl.
File metadata
- Download URL: mirabox_stream_dock_sdk-0.3.1-py3-none-any.whl
- Upload date:
- Size: 50.7 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 |
61a6a4bb740a582b1265ee861bc7b887fa66ae1ea7b7cad14a53df11670c7c7c
|
|
| MD5 |
e8dd770d7a137be1afde95fe8ae6a635
|
|
| BLAKE2b-256 |
aced899d70776fde49c631d985b09f66f51c5ddbc3d8ab36dd8a4ef33828d61e
|
Provenance
The following attestation bundles were made for mirabox_stream_dock_sdk-0.3.1-py3-none-any.whl:
Publisher:
release.yml on Nekit678/MiraboxStreamDockSDK
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mirabox_stream_dock_sdk-0.3.1-py3-none-any.whl -
Subject digest:
61a6a4bb740a582b1265ee861bc7b887fa66ae1ea7b7cad14a53df11670c7c7c - Sigstore transparency entry: 2201149719
- Sigstore integration time:
-
Permalink:
Nekit678/MiraboxStreamDockSDK@946c6dd60eae3ef28e731fc023c6188fb69b2e88 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/Nekit678
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@946c6dd60eae3ef28e731fc023c6188fb69b2e88 -
Trigger Event:
push
-
Statement type: