Metadata-Version: 2.4
Name: mixlar-sdk
Version: 0.1.9
Summary: Official SDK and CLI for building Mixlar M1X plugins and device widgets.
Author: Mixlar Labs
License: Proprietary — Mixlar Labs
Project-URL: Homepage, https://mixlar.net
Project-URL: Documentation, https://github.com/MixlarLabs/mixlar-sdk/tree/main/docs
Project-URL: Repository, https://github.com/MixlarLabs/mixlar-sdk
Project-URL: Issues, https://github.com/MixlarLabs/mixlar-sdk/issues
Keywords: mixlar,m1x,plugin,sdk,stream-deck,control-surface
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Multimedia :: Sound/Audio
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=41.0
Requires-Dist: packaging>=21.0
Provides-Extra: render
Requires-Dist: Pillow>=9.0; extra == "render"
Provides-Extra: publish
Requires-Dist: requests>=2.25; extra == "publish"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: Pillow>=9.0; extra == "dev"
Requires-Dist: requests>=2.25; extra == "dev"
Provides-Extra: all
Requires-Dist: Pillow>=9.0; extra == "all"
Requires-Dist: requests>=2.25; extra == "all"
Dynamic: license-file

# Mixlar SDK

[![PyPI](https://img.shields.io/pypi/v/mixlar-sdk.svg)](https://pypi.org/project/mixlar-sdk/)
[![Python](https://img.shields.io/pypi/pyversions/mixlar-sdk.svg)](https://pypi.org/project/mixlar-sdk/)

The official SDK and CLI for building **Mixlar Control** plugins and M1X device
widgets. Write a plugin once, against typed, importable classes — the same
classes the desktop app itself uses — and test it end-to-end without a device
plugged in: lint the manifest, run it against a headless mock device, render
its widget to a PNG, sign it, and package it, all from the command line.

Off-app, `from mixlar import MixlarPlugin` gives you a faithful standalone
implementation with autocomplete and type checking. Inside the running Mixlar
Control app, that same import transparently becomes the app's own live class —
so a plugin authored against this SDK loads completely unmodified. See
[How this maps onto the app](#how-this-maps-onto-the-app) below.

## Install

```bash
pip install mixlar-sdk
```

Or, to hack on the SDK itself, from a checkout of this folder:

```bash
pip install -e .
```

Optional extras, added as needed:

| extra | adds | needed for |
|---|---|---|
| `[render]` | Pillow | `mixlar-sdk emulate --render` (widget PNG previews) |
| `[publish]` | requests | `mixlar-sdk publish` (nicer HTTP errors; stdlib fallback works without it) |
| `[dev]` | pytest, Pillow, requests | running this SDK's own test suite |
| `[all]` | everything above | "just give me all of it" |

```bash
pip install -e ".[all]"
```

This installs the `mixlar-sdk` console script. You can also run it in place with
`python -m mixlar_cli` (handy before `pip install -e .`, or in CI).

## Quickstart

```bash
mixlar-sdk create "My Plugin" --template full   # scaffold into ./my_plugin/
cd my_plugin
mixlar-sdk validate                             # lint plugin.json + widgets/
mixlar-sdk emulate --render preview.png         # render the widget, no hardware
mixlar-sdk emulate --interactive                # press/toggle/slide it from a REPL
mixlar-sdk keygen --key-id my-key               # once, if you plan to sign
mixlar-sdk pack --sign my-key                   # build a signed .mixplugin
mixlar-sdk link                                 # copy it into the app's plugins dir
```

`create` names the output folder after the plugin **id** (a slug derived from
the display name, or `--id`), not the display name itself — `"My Plugin"`
becomes `./my_plugin/`.

## Commands

| command | what it does |
|---|---|
| `create` | scaffold a new plugin package from a template (`macro`, `slider`, `widget`, `full`) |
| `validate` | lint `plugin.json` and every bundled `widgets/<id>/widget.json` |
| `link` | copy (or symlink) a package into the app's plugins directory |
| `dev` | validate + link once, then watch the folder and re-sync on save |
| `emulate` | run a package against a headless mock device — render a PNG or drive it interactively |
| `pack` | zip a validated package into a `.mixplugin`, optionally signing it first |
| `sign` | sign a package in place with a publisher key |
| `verify` | check a package's signature against the pinned publisher keyset |
| `keygen` | generate an ed25519 publisher keypair |
| `publish` | upload a `.mixplugin` to the marketplace (stub until the endpoint ships) |

Full reference with flags and example output: [`docs/cli.md`](docs/cli.md).

## The SDK in a nutshell

```python
from mixlar import MixlarPlugin
from mixlar.macros import MacroActions, action
from mixlar.colors import ACCENT

class MyPlugin(MacroActions, MixlarPlugin):
    plugin_id = "my_plugin"
    plugin_name = "My Plugin"
    plugin_icon_color = ACCENT

    #: Pairs this plugin with widgets/my_widget/widget.json.
    widget_id = "my_widget"

    @action("ping", "Ping", icon="fa5s.satellite-dish")
    def _ping(self, step):
        self.push_widget_data("status", "pong!")   # → WDATA,my_widget.status,pong!

    def on_widget_shown(self, widget_id):
        self.push_widget_data("status", "idle")

    def on_widget_event(self, widget_id, element_id, action):
        if element_id == "ping_btn" and action == "press":
            self._ping({})
```

That's a complete plugin: a macro action, a bundled device widget pairing,
and a button handler. No boilerplate quartet of empty methods to override —
`MacroActions` derives `get_macro_actions()` / `get_macro_action_groups()` /
`get_macro_action_icons()` / `execute_macro_step()` from the `@action`
decorators for you (see [`docs/plugin-api.md`](docs/plugin-api.md)).

## How this maps onto the app

Everything in this SDK is a faithful, importable port of code that already
lives inline in `PC Software/mixlar_mini.py` — `MixlarPlugin`, `PluginRegistry`,
the color palette, the ed25519 package signing, the widget/manifest linters.
Off-app you get standalone reimplementations (so imports resolve in your
editor and this SDK has no dependency on the app). **Inside the running app**,
`mixlar.plugin.MixlarPlugin`, `mixlar.registry.PluginRegistry`, and
`mixlar.colors.*` detect that `mixlar_mini` is already imported and
transparently re-export the app's own live objects instead — same class,
same registry, same palette — so a plugin written against this SDK needs zero
changes to run for real, and `push_widget_data` reaches the actual serial
link. This "single source of truth" seam is documented in each module's
docstring and in [`docs/migration.md`](docs/migration.md).

Package trust works the same way both places: `mixlar.signing.package_digest`
computes the exact SHA-256 the app's `_plugin_package_digest` computes, so a
package signed with this SDK verifies in the real app. See
[`docs/signing-and-packaging.md`](docs/signing-and-packaging.md).

## Documentation

Start at [`docs/index.md`](docs/index.md). Highlights:

- [`docs/getting-started.md`](docs/getting-started.md) — install, scaffold, the author loop
- [`docs/plugin-api.md`](docs/plugin-api.md) — the `MixlarPlugin` hook reference
- [`docs/widgets.md`](docs/widgets.md) — building device widgets with `Widget`
- [`docs/settings.md`](docs/settings.md) — declarative `settings.schema.json`
- [`docs/signing-and-packaging.md`](docs/signing-and-packaging.md) — trust, keys, `.mixplugin`
- [`docs/emulator.md`](docs/emulator.md) — testing without hardware
- [`docs/events.md`](docs/events.md) — the system-events contract
- [`docs/cli.md`](docs/cli.md) — every command in detail
- [`docs/migration.md`](docs/migration.md) — converting a legacy plugin, and adopting the SDK app-side
- [`docs/roadmap.md`](docs/roadmap.md) — shipped vs. what still needs app-side wiring

A complete worked example — a Pomodoro timer plugin with macros, a settings
schema, and a bundled countdown widget — lives in
[`examples/pomodoro/`](examples/pomodoro/).

For the underlying specs this SDK implements, see the repo root's
`PLUGINS.md` (package format, install/trust flow) and `WIDGETS.md` (widget
spec, serial protocol).
