Skip to main content

Typed Python SDK for building MiraBox Stream Dock plugins

Project description

English · Русский

MiraBox Stream Dock SDK logo

MiraBox Stream Dock SDK

A typed Python SDK for building MiraBox Stream Dock plugins

PyPI version Python 3.11+ MiraBox Stream Dock 2.10+ CI status MIT license

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.x series. It is ready for experimentation and real plugin development, but public APIs may evolve between minor releases before 1.0.

[!NOTE] This is an unofficial community project and is not affiliated with or endorsed by MiraBox, HotSpot, or Elgato. The callback name connectElgatoStreamDeckSocket is retained because Stream Dock uses it for Property Inspector compatibility.

Table of contents

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.426 or 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 .sdPlugin bundle 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 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.

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, but message payloads remain redacted at every level. 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.


Built for reusable Python plugins on MiraBox Stream Dock.

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

mirabox_stream_dock_sdk-0.2.0.tar.gz (66.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mirabox_stream_dock_sdk-0.2.0-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

File details

Details for the file mirabox_stream_dock_sdk-0.2.0.tar.gz.

File metadata

  • Download URL: mirabox_stream_dock_sdk-0.2.0.tar.gz
  • Upload date:
  • Size: 66.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mirabox_stream_dock_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8904702508dd0c81596f119f29719a19d8c5d25b6de966258d095c3f46fcbfbc
MD5 f94b2542f17244881de1414ae1b32539
BLAKE2b-256 5711d5e5d577f7e6f6ff3463f8f3f636899b0b3ee3fee19a1c38c421adfc1853

See more details on using hashes here.

Provenance

The following attestation bundles were made for mirabox_stream_dock_sdk-0.2.0.tar.gz:

Publisher: release.yml on Nekit678/MiraboxStreamDockSDK

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mirabox_stream_dock_sdk-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for mirabox_stream_dock_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 53058687fe4d8445c61ae1faa2f00751f182777993ff862964081d7b36a07170
MD5 5b141c884e982db8a6070167d6f83405
BLAKE2b-256 6e8311efd35e9eb98a9aa1272dfabf2322574557bb7494f47a6c27cd7fae5387

See more details on using hashes here.

Provenance

The following attestation bundles were made for mirabox_stream_dock_sdk-0.2.0-py3-none-any.whl:

Publisher: release.yml on Nekit678/MiraboxStreamDockSDK

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page