From b070d2849e4b3ac45e2ba270f7f02a491899eb08 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Tue, 4 Aug 2026 16:10:47 -0600 Subject: Added plugin zip file creation instructions --- docs/plugin-development.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/plugin-development.md b/docs/plugin-development.md index 8722376..c9ec492 100644 --- a/docs/plugin-development.md +++ b/docs/plugin-development.md @@ -2,6 +2,8 @@ Plugins live under `plugins/` as self-contained directories. The app discovers them automatically; the user enables or disables them in **Settings → Plugins**. A disabled plugin leaves zero trace in the UI. +Plugins can also be installed at runtime as a `.zip` file via **Settings → Plugins → Install Plugin…** — no need to hand-copy files into `plugins/`. See [§1.1](#11-installing-from-a-zip). + --- ## Table of Contents @@ -43,6 +45,22 @@ from my_device import MyDevice # works inside plugin.py from ui.my_window import MyWindow # works too ``` +### 1.1 Installing from a .zip + +`PluginManager` scans two locations: the bundled `plugins/` dir in the repo, and a per-user dir (`PluginManager(user_dir, extra_scan_dirs=[project_plugins])`). **Settings → Plugins → Install Plugin…** lets a user pick a `.zip` and calls `PluginManager.install_from_zip(zip_path)`, which: + +- requires the zip contain exactly **one top-level directory**, matching the layout above (`my_plugin/manifest.json`, `my_plugin/plugin.py`, ...) +- extracts it into the user dir (overwriting any existing folder of the same name) +- re-runs `discover()` and returns the new `PluginManifest` + +Plugins installed this way show a **Remove** button in Settings (`PluginManager.uninstall(plugin_id)` — deletes the folder; the plugin must be disabled first). Bundled plugins under the repo's `plugins/` dir are not removable — `is_user_installed()` is `False` for them. + +When packaging a plugin for distribution, zip the plugin directory itself (not its contents) so the single-top-level-dir rule holds: + +```bash +zip -r my_plugin.zip my_plugin/ +``` + --- ## 2. manifest.json @@ -54,7 +72,9 @@ from ui.my_window import MyWindow # works too "version": "1.0.0", "description": "One-line description shown in Settings.", "author": "Your Name", - "entry_point": "plugin.MyPlugin" + "entry_point": "plugin.MyPlugin", + "requires": ["opencv-python>=4.8.0"], + "source_url": "https://github.com/you/my_plugin" } ``` @@ -66,6 +86,8 @@ from ui.my_window import MyWindow # works too | `description` | No | One sentence. Shown in Settings. | | `author` | No | Shown in Settings. | | `entry_point` | No | `"module.ClassName"` relative to the plugin dir. Defaults to `"plugin.Plugin"`. | +| `requires` | No | List of pip-style requirement strings (e.g. `"numpy>=1.24"`). Checked via `importlib.metadata` against the installed distribution name (not the import name — `opencv-python` imports as `cv2`). A plugin with unmet requirements fails to load; missing deps are printed to the console. | +| `source_url` | No | Informational link to where the plugin comes from. Not currently rendered in the UI. | --- -- cgit v1.2.3