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.
- mixlar_sdk-0.1.0/CHANGELOG.md +52 -0
- mixlar_sdk-0.1.0/LICENSE +19 -0
- mixlar_sdk-0.1.0/MANIFEST.in +14 -0
- mixlar_sdk-0.1.0/PKG-INFO +196 -0
- mixlar_sdk-0.1.0/README.md +154 -0
- mixlar_sdk-0.1.0/docs/cli.md +227 -0
- mixlar_sdk-0.1.0/docs/emulator.md +129 -0
- mixlar_sdk-0.1.0/docs/events.md +92 -0
- mixlar_sdk-0.1.0/docs/getting-started.md +83 -0
- mixlar_sdk-0.1.0/docs/index.md +25 -0
- mixlar_sdk-0.1.0/docs/migration.md +164 -0
- mixlar_sdk-0.1.0/docs/plugin-api.md +171 -0
- mixlar_sdk-0.1.0/docs/roadmap.md +75 -0
- mixlar_sdk-0.1.0/docs/settings.md +104 -0
- mixlar_sdk-0.1.0/docs/signing-and-packaging.md +132 -0
- mixlar_sdk-0.1.0/docs/widgets.md +161 -0
- mixlar_sdk-0.1.0/examples/README.md +66 -0
- mixlar_sdk-0.1.0/examples/pomodoro/plugin.json +16 -0
- mixlar_sdk-0.1.0/examples/pomodoro/pomodoro.py +148 -0
- mixlar_sdk-0.1.0/examples/pomodoro/settings.schema.json +32 -0
- mixlar_sdk-0.1.0/examples/pomodoro/widgets/pomodoro/widget.json +18 -0
- mixlar_sdk-0.1.0/pyproject.toml +64 -0
- mixlar_sdk-0.1.0/setup.cfg +4 -0
- mixlar_sdk-0.1.0/src/mixlar/__init__.py +67 -0
- mixlar_sdk-0.1.0/src/mixlar/colors.py +47 -0
- mixlar_sdk-0.1.0/src/mixlar/emulator/__init__.py +22 -0
- mixlar_sdk-0.1.0/src/mixlar/emulator/device.py +230 -0
- mixlar_sdk-0.1.0/src/mixlar/emulator/render.py +294 -0
- mixlar_sdk-0.1.0/src/mixlar/events.py +148 -0
- mixlar_sdk-0.1.0/src/mixlar/icons.py +66 -0
- mixlar_sdk-0.1.0/src/mixlar/imaging.py +67 -0
- mixlar_sdk-0.1.0/src/mixlar/macros.py +139 -0
- mixlar_sdk-0.1.0/src/mixlar/manifest.py +178 -0
- mixlar_sdk-0.1.0/src/mixlar/packaging.py +169 -0
- mixlar_sdk-0.1.0/src/mixlar/plugin.py +256 -0
- mixlar_sdk-0.1.0/src/mixlar/protocol.py +145 -0
- mixlar_sdk-0.1.0/src/mixlar/py.typed +0 -0
- mixlar_sdk-0.1.0/src/mixlar/qt.py +126 -0
- mixlar_sdk-0.1.0/src/mixlar/registry.py +83 -0
- mixlar_sdk-0.1.0/src/mixlar/settings_schema.py +181 -0
- mixlar_sdk-0.1.0/src/mixlar/signing.py +234 -0
- mixlar_sdk-0.1.0/src/mixlar/sliders.py +64 -0
- mixlar_sdk-0.1.0/src/mixlar/validator.py +185 -0
- mixlar_sdk-0.1.0/src/mixlar/widgets.py +261 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/__init__.py +12 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/__main__.py +64 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/__init__.py +7 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/create.py +130 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/dev.py +104 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/emulate.py +201 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/keygen.py +44 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/link.py +64 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/pack.py +59 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/publish.py +132 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/sign.py +53 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/validate.py +30 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/commands/verify.py +37 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/__ENTRY__.py +73 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/icons/README.txt +8 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/plugin.json +19 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/settings.schema.json +19 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/full/widgets/__PLUGIN_ID__w/widget.json +14 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/macro/__ENTRY__.py +31 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/macro/plugin.json +13 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/macro/settings.schema.json +12 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/slider/__ENTRY__.py +27 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/slider/plugin.json +13 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/widget/__ENTRY__.py +35 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/widget/plugin.json +18 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/templates/widget/widgets/__PLUGIN_ID__w/widget.json +13 -0
- mixlar_sdk-0.1.0/src/mixlar_cli/util.py +179 -0
- mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/PKG-INFO +196 -0
- mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/SOURCES.txt +83 -0
- mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/dependency_links.txt +1 -0
- mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/entry_points.txt +2 -0
- mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/requires.txt +17 -0
- mixlar_sdk-0.1.0/src/mixlar_sdk.egg-info/top_level.txt +2 -0
- mixlar_sdk-0.1.0/tests/test_cli.py +43 -0
- mixlar_sdk-0.1.0/tests/test_emulator.py +133 -0
- mixlar_sdk-0.1.0/tests/test_example.py +84 -0
- mixlar_sdk-0.1.0/tests/test_manifest.py +88 -0
- mixlar_sdk-0.1.0/tests/test_packaging.py +60 -0
- mixlar_sdk-0.1.0/tests/test_settings_schema.py +79 -0
- mixlar_sdk-0.1.0/tests/test_signing.py +77 -0
- 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.
|
mixlar_sdk-0.1.0/LICENSE
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/mixlar-sdk/)
|
|
46
|
+
[](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
|
+
[](https://pypi.org/project/mixlar-sdk/)
|
|
4
|
+
[](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.
|