mixlar-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. mixlar_sdk-0.1.0/CHANGELOG.md +52 -0
  2. mixlar_sdk-0.1.0/LICENSE +19 -0
  3. mixlar_sdk-0.1.0/MANIFEST.in +14 -0
  4. mixlar_sdk-0.1.0/PKG-INFO +196 -0
  5. mixlar_sdk-0.1.0/README.md +154 -0
  6. mixlar_sdk-0.1.0/docs/cli.md +227 -0
  7. mixlar_sdk-0.1.0/docs/emulator.md +129 -0
  8. mixlar_sdk-0.1.0/docs/events.md +92 -0
  9. mixlar_sdk-0.1.0/docs/getting-started.md +83 -0
  10. mixlar_sdk-0.1.0/docs/index.md +25 -0
  11. mixlar_sdk-0.1.0/docs/migration.md +164 -0
  12. mixlar_sdk-0.1.0/docs/plugin-api.md +171 -0
  13. mixlar_sdk-0.1.0/docs/roadmap.md +75 -0
  14. mixlar_sdk-0.1.0/docs/settings.md +104 -0
  15. mixlar_sdk-0.1.0/docs/signing-and-packaging.md +132 -0
  16. mixlar_sdk-0.1.0/docs/widgets.md +161 -0
  17. mixlar_sdk-0.1.0/examples/README.md +66 -0
  18. mixlar_sdk-0.1.0/examples/pomodoro/plugin.json +16 -0
  19. mixlar_sdk-0.1.0/examples/pomodoro/pomodoro.py +148 -0
  20. mixlar_sdk-0.1.0/examples/pomodoro/settings.schema.json +32 -0
  21. mixlar_sdk-0.1.0/examples/pomodoro/widgets/pomodoro/widget.json +18 -0
  22. mixlar_sdk-0.1.0/pyproject.toml +64 -0
  23. mixlar_sdk-0.1.0/setup.cfg +4 -0
  24. mixlar_sdk-0.1.0/src/mixlar/__init__.py +67 -0
  25. mixlar_sdk-0.1.0/src/mixlar/colors.py +47 -0
  26. mixlar_sdk-0.1.0/src/mixlar/emulator/__init__.py +22 -0
  27. mixlar_sdk-0.1.0/src/mixlar/emulator/device.py +230 -0
  28. mixlar_sdk-0.1.0/src/mixlar/emulator/render.py +294 -0
  29. mixlar_sdk-0.1.0/src/mixlar/events.py +148 -0
  30. mixlar_sdk-0.1.0/src/mixlar/icons.py +66 -0
  31. mixlar_sdk-0.1.0/src/mixlar/imaging.py +67 -0
  32. mixlar_sdk-0.1.0/src/mixlar/macros.py +139 -0
  33. mixlar_sdk-0.1.0/src/mixlar/manifest.py +178 -0
  34. mixlar_sdk-0.1.0/src/mixlar/packaging.py +169 -0
  35. mixlar_sdk-0.1.0/src/mixlar/plugin.py +256 -0
  36. mixlar_sdk-0.1.0/src/mixlar/protocol.py +145 -0
  37. mixlar_sdk-0.1.0/src/mixlar/py.typed +0 -0
  38. mixlar_sdk-0.1.0/src/mixlar/qt.py +126 -0
  39. mixlar_sdk-0.1.0/src/mixlar/registry.py +83 -0
  40. mixlar_sdk-0.1.0/src/mixlar/settings_schema.py +181 -0
  41. mixlar_sdk-0.1.0/src/mixlar/signing.py +234 -0
  42. mixlar_sdk-0.1.0/src/mixlar/sliders.py +64 -0
  43. mixlar_sdk-0.1.0/src/mixlar/validator.py +185 -0
  44. mixlar_sdk-0.1.0/src/mixlar/widgets.py +261 -0
  45. mixlar_sdk-0.1.0/src/mixlar_cli/__init__.py +12 -0
  46. mixlar_sdk-0.1.0/src/mixlar_cli/__main__.py +64 -0
  47. mixlar_sdk-0.1.0/src/mixlar_cli/commands/__init__.py +7 -0
  48. mixlar_sdk-0.1.0/src/mixlar_cli/commands/create.py +130 -0
  49. mixlar_sdk-0.1.0/src/mixlar_cli/commands/dev.py +104 -0
  50. mixlar_sdk-0.1.0/src/mixlar_cli/commands/emulate.py +201 -0
  51. mixlar_sdk-0.1.0/src/mixlar_cli/commands/keygen.py +44 -0
  52. mixlar_sdk-0.1.0/src/mixlar_cli/commands/link.py +64 -0
  53. mixlar_sdk-0.1.0/src/mixlar_cli/commands/pack.py +59 -0
  54. mixlar_sdk-0.1.0/src/mixlar_cli/commands/publish.py +132 -0
  55. mixlar_sdk-0.1.0/src/mixlar_cli/commands/sign.py +53 -0
  56. mixlar_sdk-0.1.0/src/mixlar_cli/commands/validate.py +30 -0
  57. mixlar_sdk-0.1.0/src/mixlar_cli/commands/verify.py +37 -0
  58. mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/__ENTRY__.py +73 -0
  59. mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/icons/README.txt +8 -0
  60. mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/plugin.json +19 -0
  61. mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/settings.schema.json +19 -0
  62. mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/widgets/__PLUGIN_ID__w/widget.json +14 -0
  63. mixlar_sdk-0.1.0/src/mixlar_cli/templates/macro/__ENTRY__.py +31 -0
  64. mixlar_sdk-0.1.0/src/mixlar_cli/templates/macro/plugin.json +13 -0
  65. mixlar_sdk-0.1.0/src/mixlar_cli/templates/macro/settings.schema.json +12 -0
  66. mixlar_sdk-0.1.0/src/mixlar_cli/templates/slider/__ENTRY__.py +27 -0
  67. mixlar_sdk-0.1.0/src/mixlar_cli/templates/slider/plugin.json +13 -0
  68. mixlar_sdk-0.1.0/src/mixlar_cli/templates/widget/__ENTRY__.py +35 -0
  69. mixlar_sdk-0.1.0/src/mixlar_cli/templates/widget/plugin.json +18 -0
  70. mixlar_sdk-0.1.0/src/mixlar_cli/templates/widget/widgets/__PLUGIN_ID__w/widget.json +13 -0
  71. mixlar_sdk-0.1.0/src/mixlar_cli/util.py +179 -0
  72. mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/PKG-INFO +196 -0
  73. mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/SOURCES.txt +83 -0
  74. mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/dependency_links.txt +1 -0
  75. mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/entry_points.txt +2 -0
  76. mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/requires.txt +17 -0
  77. mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/top_level.txt +2 -0
  78. mixlar_sdk-0.1.0/tests/test_cli.py +43 -0
  79. mixlar_sdk-0.1.0/tests/test_emulator.py +133 -0
  80. mixlar_sdk-0.1.0/tests/test_example.py +84 -0
  81. mixlar_sdk-0.1.0/tests/test_manifest.py +88 -0
  82. mixlar_sdk-0.1.0/tests/test_packaging.py +60 -0
  83. mixlar_sdk-0.1.0/tests/test_settings_schema.py +79 -0
  84. mixlar_sdk-0.1.0/tests/test_signing.py +77 -0
  85. mixlar_sdk-0.1.0/tests/test_widgets.py +106 -0
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to the Mixlar SDK are recorded here. Versions follow
4
+ semver; the SDK version (`mixlar.__version__`) and the CLI version
5
+ (`mixlar_cli.__version__`, re-exported from the same place) always match.
6
+
7
+ ## 0.1.0 — initial release
8
+
9
+ The first complete cut of the SDK: everything needed to author, test, sign,
10
+ and package a Mixlar Control plugin without the desktop app installed.
11
+
12
+ **Core package (`mixlar`)**
13
+ - `MixlarPlugin` — standalone base class, byte-for-byte behavioral match with
14
+ the app's `MixlarPlugin`, transparently replaced by the app's live class
15
+ when running in-process.
16
+ - `PluginRegistry` — singleton macro/slider/widget-sender routing, same
17
+ transparent-replacement behavior.
18
+ - `colors` — the exact palette the app injects into plugin modules.
19
+ - `protocol` — the WDATA/WEVENT/WMACRO/WPAGE wire protocol as constants,
20
+ builders, and parsers.
21
+ - `manifest` — `plugin.json` reading and validation (`read_manifest`,
22
+ `validate_package`, `build_manifest`).
23
+ - `signing` — ed25519 package signing/verification, digest-compatible with
24
+ the app's `_plugin_package_digest`.
25
+ - `widgets` — the `Widget` fluent builder for `widget.json` (mixw:1).
26
+ - `validator` — `lint_widget` / `lint_package`, checked against the firmware's
27
+ accepted element types when the firmware source is reachable.
28
+ - `settings_schema` — the `SettingsSchema` builder/validator for
29
+ `settings.schema.json`.
30
+ - `events` — the system-events contract (`SystemEventsMixin`, `broadcast`,
31
+ `parse_deep_link`) — documented, not yet wired into the app.
32
+ - `macros` — `MacroActions` + `@action`, ergonomic macro-action declaration.
33
+ - `sliders` — `SliderMode`, ergonomic slider-mode declaration.
34
+ - `icons` — plugin-icon install-path helpers.
35
+ - `imaging` — image → RGB565 conversion for `push_widget_image`.
36
+ - `packaging` — `.mixplugin` pack/unpack/install, with path-traversal guards.
37
+ - `emulator` — `MockDevice` (headless device simulation) and `render`
38
+ (widget.json → PNG preview, needs Pillow).
39
+ - `qt` — off-app-safe stand-ins for the Qt helpers the app injects into
40
+ plugin modules (`_fa`, `_EDITOR_COMBO_STYLE`, `_style_combo_view`).
41
+
42
+ **CLI (`mixlar`)**
43
+ - `create`, `validate`, `link`, `dev`, `emulate`, `pack`, `sign`, `verify`,
44
+ `keygen`, `publish`.
45
+
46
+ **Docs, examples, tests**
47
+ - Full `docs/` reference, cross-linked from `docs/index.md`.
48
+ - `examples/pomodoro/` — a complete Pomodoro timer plugin: macro actions,
49
+ a bundled countdown-arc widget, a settings schema, live data pushes.
50
+ - `tests/` — a pytest suite covering signing, manifest validation, widget
51
+ building/linting, settings schemas, packaging, the emulator, and the
52
+ example plugin.
@@ -0,0 +1,19 @@
1
+ Mixlar SDK — Proprietary License
2
+ Copyright (c) 2026 Mixlar Labs. All rights reserved.
3
+
4
+ This software (the "SDK") is provided by Mixlar Labs to enable developers to
5
+ build plugins and device widgets for Mixlar Control and Mixlar M1X hardware.
6
+
7
+ Permission is granted to install and use the SDK, and to author, build, sign,
8
+ and distribute your own Mixlar plugins with it. You may not redistribute a
9
+ modified copy of the SDK itself, or use the SDK to build products that
10
+ compete with Mixlar hardware or software, without prior written permission
11
+ from Mixlar Labs.
12
+
13
+ THE SDK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
15
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL MIXLAR LABS BE LIABLE
16
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION
17
+ WITH THE SDK OR THE USE OR OTHER DEALINGS IN THE SDK.
18
+
19
+ For licensing questions, contact support@mixlar.net.
@@ -0,0 +1,14 @@
1
+ # Ensure the source distribution (sdist) carries everything the wheel needs and
2
+ # the docs/examples devs expect. The wheel already bundles package-data via
3
+ # pyproject; this covers the sdist tarball.
4
+ include README.md
5
+ include CHANGELOG.md
6
+ include LICENSE
7
+ include pyproject.toml
8
+ recursive-include src/mixlar py.typed
9
+ recursive-include src/mixlar_cli/templates *
10
+ recursive-include docs *.md
11
+ recursive-include examples *
12
+ prune **/__pycache__
13
+ global-exclude *.pyc
14
+ global-exclude *.mixplugin
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: mixlar-sdk
3
+ Version: 0.1.0
4
+ Summary: Official SDK and CLI for building Mixlar M1X plugins and device widgets.
5
+ Author: Mixlar Labs
6
+ License: Proprietary — Mixlar Labs
7
+ Project-URL: Homepage, https://mixlar.net
8
+ Project-URL: Documentation, https://github.com/MixlarLabs/mixlar-sdk/tree/main/docs
9
+ Project-URL: Repository, https://github.com/MixlarLabs/mixlar-sdk
10
+ Project-URL: Issues, https://github.com/MixlarLabs/mixlar-sdk/issues
11
+ Keywords: mixlar,m1x,plugin,sdk,stream-deck,control-surface
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: Other/Proprietary License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
24
+ Classifier: Topic :: Multimedia :: Sound/Audio
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: cryptography>=41.0
29
+ Requires-Dist: packaging>=21.0
30
+ Provides-Extra: render
31
+ Requires-Dist: Pillow>=9.0; extra == "render"
32
+ Provides-Extra: publish
33
+ Requires-Dist: requests>=2.25; extra == "publish"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7.0; extra == "dev"
36
+ Requires-Dist: Pillow>=9.0; extra == "dev"
37
+ Requires-Dist: requests>=2.25; extra == "dev"
38
+ Provides-Extra: all
39
+ Requires-Dist: Pillow>=9.0; extra == "all"
40
+ Requires-Dist: requests>=2.25; extra == "all"
41
+ Dynamic: license-file
42
+
43
+ # Mixlar SDK
44
+
45
+ [![PyPI](https://img.shields.io/pypi/v/mixlar-sdk.svg)](https://pypi.org/project/mixlar-sdk/)
46
+ [![Python](https://img.shields.io/pypi/pyversions/mixlar-sdk.svg)](https://pypi.org/project/mixlar-sdk/)
47
+
48
+ The official SDK and CLI for building **Mixlar Control** plugins and M1X device
49
+ widgets. Write a plugin once, against typed, importable classes — the same
50
+ classes the desktop app itself uses — and test it end-to-end without a device
51
+ plugged in: lint the manifest, run it against a headless mock device, render
52
+ its widget to a PNG, sign it, and package it, all from the command line.
53
+
54
+ Off-app, `from mixlar import MixlarPlugin` gives you a faithful standalone
55
+ implementation with autocomplete and type checking. Inside the running Mixlar
56
+ Control app, that same import transparently becomes the app's own live class —
57
+ so a plugin authored against this SDK loads completely unmodified. See
58
+ [How this maps onto the app](#how-this-maps-onto-the-app) below.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pip install mixlar-sdk
64
+ ```
65
+
66
+ Or, to hack on the SDK itself, from a checkout of this folder:
67
+
68
+ ```bash
69
+ pip install -e .
70
+ ```
71
+
72
+ Optional extras, added as needed:
73
+
74
+ | extra | adds | needed for |
75
+ |---|---|---|
76
+ | `[render]` | Pillow | `mixlar emulate --render` (widget PNG previews) |
77
+ | `[publish]` | requests | `mixlar publish` (nicer HTTP errors; stdlib fallback works without it) |
78
+ | `[dev]` | pytest, Pillow, requests | running this SDK's own test suite |
79
+ | `[all]` | everything above | "just give me all of it" |
80
+
81
+ ```bash
82
+ pip install -e ".[all]"
83
+ ```
84
+
85
+ This installs the `mixlar` console script. You can also run it in place with
86
+ `python -m mixlar_cli` (handy before `pip install -e .`, or in CI).
87
+
88
+ ## Quickstart
89
+
90
+ ```bash
91
+ mixlar create "My Plugin" --template full # scaffold into ./my_plugin/
92
+ cd my_plugin
93
+ mixlar validate # lint plugin.json + widgets/
94
+ mixlar emulate --render preview.png # render the widget, no hardware
95
+ mixlar emulate --interactive # press/toggle/slide it from a REPL
96
+ mixlar keygen --key-id my-key # once, if you plan to sign
97
+ mixlar pack --sign my-key # build a signed .mixplugin
98
+ mixlar link # copy it into the app's plugins dir
99
+ ```
100
+
101
+ `create` names the output folder after the plugin **id** (a slug derived from
102
+ the display name, or `--id`), not the display name itself — `"My Plugin"`
103
+ becomes `./my_plugin/`.
104
+
105
+ ## Commands
106
+
107
+ | command | what it does |
108
+ |---|---|
109
+ | `create` | scaffold a new plugin package from a template (`macro`, `slider`, `widget`, `full`) |
110
+ | `validate` | lint `plugin.json` and every bundled `widgets/<id>/widget.json` |
111
+ | `link` | copy (or symlink) a package into the app's plugins directory |
112
+ | `dev` | validate + link once, then watch the folder and re-sync on save |
113
+ | `emulate` | run a package against a headless mock device — render a PNG or drive it interactively |
114
+ | `pack` | zip a validated package into a `.mixplugin`, optionally signing it first |
115
+ | `sign` | sign a package in place with a publisher key |
116
+ | `verify` | check a package's signature against the pinned publisher keyset |
117
+ | `keygen` | generate an ed25519 publisher keypair |
118
+ | `publish` | upload a `.mixplugin` to the marketplace (stub until the endpoint ships) |
119
+
120
+ Full reference with flags and example output: [`docs/cli.md`](docs/cli.md).
121
+
122
+ ## The SDK in a nutshell
123
+
124
+ ```python
125
+ from mixlar import MixlarPlugin
126
+ from mixlar.macros import MacroActions, action
127
+ from mixlar.colors import ACCENT
128
+
129
+ class MyPlugin(MacroActions, MixlarPlugin):
130
+ plugin_id = "my_plugin"
131
+ plugin_name = "My Plugin"
132
+ plugin_icon_color = ACCENT
133
+
134
+ #: Pairs this plugin with widgets/my_widget/widget.json.
135
+ widget_id = "my_widget"
136
+
137
+ @action("ping", "Ping", icon="fa5s.satellite-dish")
138
+ def _ping(self, step):
139
+ self.push_widget_data("status", "pong!") # → WDATA,my_widget.status,pong!
140
+
141
+ def on_widget_shown(self, widget_id):
142
+ self.push_widget_data("status", "idle")
143
+
144
+ def on_widget_event(self, widget_id, element_id, action):
145
+ if element_id == "ping_btn" and action == "press":
146
+ self._ping({})
147
+ ```
148
+
149
+ That's a complete plugin: a macro action, a bundled device widget pairing,
150
+ and a button handler. No boilerplate quartet of empty methods to override —
151
+ `MacroActions` derives `get_macro_actions()` / `get_macro_action_groups()` /
152
+ `get_macro_action_icons()` / `execute_macro_step()` from the `@action`
153
+ decorators for you (see [`docs/plugin-api.md`](docs/plugin-api.md)).
154
+
155
+ ## How this maps onto the app
156
+
157
+ Everything in this SDK is a faithful, importable port of code that already
158
+ lives inline in `PC Software/mixlar_mini.py` — `MixlarPlugin`, `PluginRegistry`,
159
+ the color palette, the ed25519 package signing, the widget/manifest linters.
160
+ Off-app you get standalone reimplementations (so imports resolve in your
161
+ editor and this SDK has no dependency on the app). **Inside the running app**,
162
+ `mixlar.plugin.MixlarPlugin`, `mixlar.registry.PluginRegistry`, and
163
+ `mixlar.colors.*` detect that `mixlar_mini` is already imported and
164
+ transparently re-export the app's own live objects instead — same class,
165
+ same registry, same palette — so a plugin written against this SDK needs zero
166
+ changes to run for real, and `push_widget_data` reaches the actual serial
167
+ link. This "single source of truth" seam is documented in each module's
168
+ docstring and in [`docs/migration.md`](docs/migration.md).
169
+
170
+ Package trust works the same way both places: `mixlar.signing.package_digest`
171
+ computes the exact SHA-256 the app's `_plugin_package_digest` computes, so a
172
+ package signed with this SDK verifies in the real app. See
173
+ [`docs/signing-and-packaging.md`](docs/signing-and-packaging.md).
174
+
175
+ ## Documentation
176
+
177
+ Start at [`docs/index.md`](docs/index.md). Highlights:
178
+
179
+ - [`docs/getting-started.md`](docs/getting-started.md) — install, scaffold, the author loop
180
+ - [`docs/plugin-api.md`](docs/plugin-api.md) — the `MixlarPlugin` hook reference
181
+ - [`docs/widgets.md`](docs/widgets.md) — building device widgets with `Widget`
182
+ - [`docs/settings.md`](docs/settings.md) — declarative `settings.schema.json`
183
+ - [`docs/signing-and-packaging.md`](docs/signing-and-packaging.md) — trust, keys, `.mixplugin`
184
+ - [`docs/emulator.md`](docs/emulator.md) — testing without hardware
185
+ - [`docs/events.md`](docs/events.md) — the system-events contract
186
+ - [`docs/cli.md`](docs/cli.md) — every command in detail
187
+ - [`docs/migration.md`](docs/migration.md) — converting a legacy plugin, and adopting the SDK app-side
188
+ - [`docs/roadmap.md`](docs/roadmap.md) — shipped vs. what still needs app-side wiring
189
+
190
+ A complete worked example — a Pomodoro timer plugin with macros, a settings
191
+ schema, and a bundled countdown widget — lives in
192
+ [`examples/pomodoro/`](examples/pomodoro/).
193
+
194
+ For the underlying specs this SDK implements, see the repo root's
195
+ `PLUGINS.md` (package format, install/trust flow) and `WIDGETS.md` (widget
196
+ spec, serial protocol).
@@ -0,0 +1,154 @@
1
+ # Mixlar SDK
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/mixlar-sdk.svg)](https://pypi.org/project/mixlar-sdk/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/mixlar-sdk.svg)](https://pypi.org/project/mixlar-sdk/)
5
+
6
+ The official SDK and CLI for building **Mixlar Control** plugins and M1X device
7
+ widgets. Write a plugin once, against typed, importable classes — the same
8
+ classes the desktop app itself uses — and test it end-to-end without a device
9
+ plugged in: lint the manifest, run it against a headless mock device, render
10
+ its widget to a PNG, sign it, and package it, all from the command line.
11
+
12
+ Off-app, `from mixlar import MixlarPlugin` gives you a faithful standalone
13
+ implementation with autocomplete and type checking. Inside the running Mixlar
14
+ Control app, that same import transparently becomes the app's own live class —
15
+ so a plugin authored against this SDK loads completely unmodified. See
16
+ [How this maps onto the app](#how-this-maps-onto-the-app) below.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install mixlar-sdk
22
+ ```
23
+
24
+ Or, to hack on the SDK itself, from a checkout of this folder:
25
+
26
+ ```bash
27
+ pip install -e .
28
+ ```
29
+
30
+ Optional extras, added as needed:
31
+
32
+ | extra | adds | needed for |
33
+ |---|---|---|
34
+ | `[render]` | Pillow | `mixlar emulate --render` (widget PNG previews) |
35
+ | `[publish]` | requests | `mixlar publish` (nicer HTTP errors; stdlib fallback works without it) |
36
+ | `[dev]` | pytest, Pillow, requests | running this SDK's own test suite |
37
+ | `[all]` | everything above | "just give me all of it" |
38
+
39
+ ```bash
40
+ pip install -e ".[all]"
41
+ ```
42
+
43
+ This installs the `mixlar` console script. You can also run it in place with
44
+ `python -m mixlar_cli` (handy before `pip install -e .`, or in CI).
45
+
46
+ ## Quickstart
47
+
48
+ ```bash
49
+ mixlar create "My Plugin" --template full # scaffold into ./my_plugin/
50
+ cd my_plugin
51
+ mixlar validate # lint plugin.json + widgets/
52
+ mixlar emulate --render preview.png # render the widget, no hardware
53
+ mixlar emulate --interactive # press/toggle/slide it from a REPL
54
+ mixlar keygen --key-id my-key # once, if you plan to sign
55
+ mixlar pack --sign my-key # build a signed .mixplugin
56
+ mixlar link # copy it into the app's plugins dir
57
+ ```
58
+
59
+ `create` names the output folder after the plugin **id** (a slug derived from
60
+ the display name, or `--id`), not the display name itself — `"My Plugin"`
61
+ becomes `./my_plugin/`.
62
+
63
+ ## Commands
64
+
65
+ | command | what it does |
66
+ |---|---|
67
+ | `create` | scaffold a new plugin package from a template (`macro`, `slider`, `widget`, `full`) |
68
+ | `validate` | lint `plugin.json` and every bundled `widgets/<id>/widget.json` |
69
+ | `link` | copy (or symlink) a package into the app's plugins directory |
70
+ | `dev` | validate + link once, then watch the folder and re-sync on save |
71
+ | `emulate` | run a package against a headless mock device — render a PNG or drive it interactively |
72
+ | `pack` | zip a validated package into a `.mixplugin`, optionally signing it first |
73
+ | `sign` | sign a package in place with a publisher key |
74
+ | `verify` | check a package's signature against the pinned publisher keyset |
75
+ | `keygen` | generate an ed25519 publisher keypair |
76
+ | `publish` | upload a `.mixplugin` to the marketplace (stub until the endpoint ships) |
77
+
78
+ Full reference with flags and example output: [`docs/cli.md`](docs/cli.md).
79
+
80
+ ## The SDK in a nutshell
81
+
82
+ ```python
83
+ from mixlar import MixlarPlugin
84
+ from mixlar.macros import MacroActions, action
85
+ from mixlar.colors import ACCENT
86
+
87
+ class MyPlugin(MacroActions, MixlarPlugin):
88
+ plugin_id = "my_plugin"
89
+ plugin_name = "My Plugin"
90
+ plugin_icon_color = ACCENT
91
+
92
+ #: Pairs this plugin with widgets/my_widget/widget.json.
93
+ widget_id = "my_widget"
94
+
95
+ @action("ping", "Ping", icon="fa5s.satellite-dish")
96
+ def _ping(self, step):
97
+ self.push_widget_data("status", "pong!") # → WDATA,my_widget.status,pong!
98
+
99
+ def on_widget_shown(self, widget_id):
100
+ self.push_widget_data("status", "idle")
101
+
102
+ def on_widget_event(self, widget_id, element_id, action):
103
+ if element_id == "ping_btn" and action == "press":
104
+ self._ping({})
105
+ ```
106
+
107
+ That's a complete plugin: a macro action, a bundled device widget pairing,
108
+ and a button handler. No boilerplate quartet of empty methods to override —
109
+ `MacroActions` derives `get_macro_actions()` / `get_macro_action_groups()` /
110
+ `get_macro_action_icons()` / `execute_macro_step()` from the `@action`
111
+ decorators for you (see [`docs/plugin-api.md`](docs/plugin-api.md)).
112
+
113
+ ## How this maps onto the app
114
+
115
+ Everything in this SDK is a faithful, importable port of code that already
116
+ lives inline in `PC Software/mixlar_mini.py` — `MixlarPlugin`, `PluginRegistry`,
117
+ the color palette, the ed25519 package signing, the widget/manifest linters.
118
+ Off-app you get standalone reimplementations (so imports resolve in your
119
+ editor and this SDK has no dependency on the app). **Inside the running app**,
120
+ `mixlar.plugin.MixlarPlugin`, `mixlar.registry.PluginRegistry`, and
121
+ `mixlar.colors.*` detect that `mixlar_mini` is already imported and
122
+ transparently re-export the app's own live objects instead — same class,
123
+ same registry, same palette — so a plugin written against this SDK needs zero
124
+ changes to run for real, and `push_widget_data` reaches the actual serial
125
+ link. This "single source of truth" seam is documented in each module's
126
+ docstring and in [`docs/migration.md`](docs/migration.md).
127
+
128
+ Package trust works the same way both places: `mixlar.signing.package_digest`
129
+ computes the exact SHA-256 the app's `_plugin_package_digest` computes, so a
130
+ package signed with this SDK verifies in the real app. See
131
+ [`docs/signing-and-packaging.md`](docs/signing-and-packaging.md).
132
+
133
+ ## Documentation
134
+
135
+ Start at [`docs/index.md`](docs/index.md). Highlights:
136
+
137
+ - [`docs/getting-started.md`](docs/getting-started.md) — install, scaffold, the author loop
138
+ - [`docs/plugin-api.md`](docs/plugin-api.md) — the `MixlarPlugin` hook reference
139
+ - [`docs/widgets.md`](docs/widgets.md) — building device widgets with `Widget`
140
+ - [`docs/settings.md`](docs/settings.md) — declarative `settings.schema.json`
141
+ - [`docs/signing-and-packaging.md`](docs/signing-and-packaging.md) — trust, keys, `.mixplugin`
142
+ - [`docs/emulator.md`](docs/emulator.md) — testing without hardware
143
+ - [`docs/events.md`](docs/events.md) — the system-events contract
144
+ - [`docs/cli.md`](docs/cli.md) — every command in detail
145
+ - [`docs/migration.md`](docs/migration.md) — converting a legacy plugin, and adopting the SDK app-side
146
+ - [`docs/roadmap.md`](docs/roadmap.md) — shipped vs. what still needs app-side wiring
147
+
148
+ A complete worked example — a Pomodoro timer plugin with macros, a settings
149
+ schema, and a bundled countdown widget — lives in
150
+ [`examples/pomodoro/`](examples/pomodoro/).
151
+
152
+ For the underlying specs this SDK implements, see the repo root's
153
+ `PLUGINS.md` (package format, install/trust flow) and `WIDGETS.md` (widget
154
+ spec, serial protocol).
@@ -0,0 +1,227 @@
1
+ # CLI reference
2
+
3
+ Installed as `mixlar` (`pip install -e .`), or run in place with
4
+ `python -m mixlar_cli` — identical behavior, useful before installing or in
5
+ CI. Every subcommand takes an optional `pkg` positional argument (a package
6
+ directory); omitted, it defaults to the current directory, and every command
7
+ resolves it by looking for `plugin.json` (`mixlar_cli.util.find_package`) —
8
+ a clear error if none is found, never a silent wrong-folder operation.
9
+
10
+ ```
11
+ mixlar --version
12
+ mixlar <command> -h # per-command help
13
+ ```
14
+
15
+ ## `create`
16
+
17
+ Scaffold a new plugin package from a bundled template.
18
+
19
+ ```
20
+ mixlar create [name] --dir DIR --template {macro,slider,widget,full}
21
+ --id PLUGIN_ID --author AUTHOR --force
22
+ ```
23
+
24
+ | flag | default | meaning |
25
+ |---|---|---|
26
+ | `name` (positional) | `"My Plugin"` | display name |
27
+ | `--dir` | `.` | where to create the package folder |
28
+ | `--template` | `full` | `macro` / `slider` / `widget` / `full` |
29
+ | `--id` | slug of `name` | plugin id (also the output folder name) |
30
+ | `--author` | `""` | written into `plugin.json` |
31
+ | `--force` | off | overwrite an existing folder of the same name |
32
+
33
+ ```console
34
+ $ mixlar create "Weather Widget" --template widget --author "Jane Dev"
35
+ [ok] created 'widget' plugin 'Weather Widget' (weather_widget) at C:\...\weather_widget
36
+ Next steps:
37
+ cd C:\...\weather_widget
38
+ mixlar validate
39
+ mixlar emulate --render preview.png
40
+ mixlar pack
41
+ ```
42
+
43
+ The output folder is named after the **id**, not the display name — `"My
44
+ Plugin"` → `./my_plugin/`.
45
+
46
+ ## `validate`
47
+
48
+ Lint `plugin.json` and every bundled `widgets/<id>/widget.json`.
49
+
50
+ ```
51
+ mixlar validate [pkg]
52
+ ```
53
+
54
+ ```console
55
+ $ mixlar validate
56
+ [warn] no 'author' — shown on the plugin card
57
+ [ok] package valid (Weather Widget v0.1.0)
58
+ ```
59
+
60
+ Exit code `0` if valid (warnings allowed), `1` on any error.
61
+
62
+ ## `link`
63
+
64
+ Copy (or symlink) a package into the app's plugins directory.
65
+
66
+ ```
67
+ mixlar link [pkg] --plugins-dir DIR --symlink
68
+ ```
69
+
70
+ | flag | default | meaning |
71
+ |---|---|---|
72
+ | `--plugins-dir` | `%APPDATA%\Mixlar\config\plugins` | destination |
73
+ | `--symlink` | off | try a real symlink (needs Windows privilege) instead of copying; falls back to copy on failure |
74
+
75
+ ```console
76
+ $ mixlar link
77
+ [ok] copied C:\...\weather_widget -> C:\Users\you\AppData\Roaming\Mixlar\config\plugins\weather_widget
78
+ ```
79
+
80
+ Copying is the default because Windows symlinks usually need
81
+ Administrator/Developer Mode — most authors won't have that enabled, so
82
+ `link` "just works" without it.
83
+
84
+ ## `dev`
85
+
86
+ Validate once, link once, then poll the package for changes and re-sync.
87
+
88
+ ```
89
+ mixlar dev [pkg] --plugins-dir DIR --interval SECONDS
90
+ ```
91
+
92
+ ```console
93
+ $ mixlar dev
94
+ mixlar dev — watching C:\...\weather_widget
95
+ [..] syncing into C:\Users\you\AppData\Roaming\Mixlar\config\plugins
96
+ [..] pairs with the app's own hot-reload; Ctrl-C to stop
97
+ [ok] [14:02:11] synced (0 warning(s))
98
+ [ok] [14:03:47] synced (0 warning(s))
99
+ ^C
100
+ [..] stopped
101
+ ```
102
+
103
+ Stdlib-only mtime polling, default 1s interval — no filesystem-events
104
+ dependency. It keeps the *linked copy* fresh; getting the running app to
105
+ notice is still on you (see [getting-started.md](getting-started.md)).
106
+
107
+ ## `emulate`
108
+
109
+ Run a package against a headless mock device.
110
+
111
+ ```
112
+ mixlar emulate [pkg] --widget ID --render OUT.png --data k=v --interactive
113
+ ```
114
+
115
+ | flag | meaning |
116
+ |---|---|
117
+ | `--widget` | widget id to load (default: the package's first declared widget) |
118
+ | `--render OUT.png` | render the current widget state to a PNG (needs `[render]`) |
119
+ | `--data k=v` | seed a `WDATA` value before rendering/inspecting (repeatable) |
120
+ | `--interactive` | drop into a REPL — see [emulator.md](emulator.md) |
121
+
122
+ ```console
123
+ $ mixlar emulate --data pomodoro.pct=40 --data pomodoro.remaining=12:34 --render preview.png
124
+ [..] loaded widget 'pomodoro' from C:\...\widgets\pomodoro\widget.json
125
+ Data snapshot:
126
+ pomodoro.pct = 40
127
+ pomodoro.remaining = 12:34
128
+ Interactive elements:
129
+ reset
130
+ start_pause
131
+ [ok] rendered preview to preview.png
132
+ ```
133
+
134
+ ## `pack`
135
+
136
+ Build a `.mixplugin` archive, optionally signing first.
137
+
138
+ ```
139
+ mixlar pack [pkg] --out PATH --sign KEY_ID --key-file PATH
140
+ ```
141
+
142
+ ```console
143
+ $ mixlar pack --sign my-studio
144
+ [ok] packed C:\...\weather_widget-0.1.0.mixplugin (18,204 bytes)
145
+ [..] signed with key 'my-studio'
146
+ ```
147
+
148
+ ## `sign`
149
+
150
+ Sign a package folder in place (writes `publisher`/`sig` into `plugin.json`).
151
+
152
+ ```
153
+ mixlar sign [pkg] --key-id KEY_ID --key-file PATH
154
+ ```
155
+
156
+ `--key-id` defaults to `mixlar-official-1`; `--key-file` defaults to the
157
+ conventional `%APPDATA%\Mixlar\keys\<key-id>.ed25519.key`.
158
+
159
+ ```console
160
+ $ mixlar sign --key-id my-studio
161
+ [ok] signed as 'my-studio'
162
+ [..] digest: a1b2c3d4e5f6a7b8...
163
+ [..] signature: 9f8e7d6c5b4a3210...
164
+ ```
165
+
166
+ ## `verify`
167
+
168
+ Check a package's signature against the pinned publisher keyset.
169
+
170
+ ```
171
+ mixlar verify [pkg]
172
+ ```
173
+
174
+ ```console
175
+ $ mixlar verify
176
+ [error] INVALID — unknown publisher or tampered package
177
+ ```
178
+
179
+ See [signing-and-packaging.md](signing-and-packaging.md) for what each
180
+ outcome means and how to get a key pinned. Exit `1` only on `INVALID`;
181
+ `UNSIGNED` and `VALID` both exit `0`.
182
+
183
+ ## `keygen`
184
+
185
+ Generate an ed25519 signing keypair.
186
+
187
+ ```
188
+ mixlar keygen --key-id KEY_ID --out PATH
189
+ ```
190
+
191
+ ```console
192
+ $ mixlar keygen --key-id my-studio
193
+ [ok] generated keypair 'my-studio'
194
+ [..] private key saved to: C:\Users\you\AppData\Roaming\Mixlar\keys\my-studio.ed25519.key
195
+ Public key (share this, keep the private key secret):
196
+ AjxJeR5Ns4h1GdUYEzP3biW6vWoStOG1/ydMccDFWZU=
197
+ To be trusted by the app, a maintainer must add:
198
+ "my-studio": "AjxJeR5Ns4h1GdUYEzP3biW6vWoStOG1/ydMccDFWZU="
199
+ to plugin-verify.php / the app's _PUBLISHER_KEYS and to
200
+ mixlar.signing.PUBLISHER_KEYS. Until then, 'mixlar verify' will report
201
+ packages signed with this key as INVALID (unknown publisher) — that's
202
+ expected for an unpinned key.
203
+ ```
204
+
205
+ ## `publish`
206
+
207
+ Pack (if needed) and upload a `.mixplugin` to the marketplace.
208
+
209
+ ```
210
+ mixlar publish [pkg] --url URL --token TOKEN
211
+ ```
212
+
213
+ Reads `--url`/`MIXLAR_MARKETPLACE_URL` and `--token`/`MIXLAR_DEV_TOKEN`. This
214
+ is deliberately a **stub** — the marketplace upload endpoint is being
215
+ reworked, so without a URL configured it explains that and exits `0` rather
216
+ than failing against a guessed endpoint:
217
+
218
+ ```console
219
+ $ mixlar publish
220
+ [warn] the marketplace upload endpoint is being reworked — no URL
221
+ configured. Pass --url or set $MIXLAR_MARKETPLACE_URL once it's available.
222
+ This command is currently a stub.
223
+ ```
224
+
225
+ With a URL set, it packs fresh (so what's uploaded matches the folder on
226
+ disk right now) and POSTs it multipart, preferring `requests` when installed
227
+ and falling back to stdlib `urllib` otherwise.