PLUGINS.md (7000B)
1 # Bzl Plugins (MVP) 2 3 This is the **minimal plugin system** used to ship optional features without forking the core app. 4 5 Plugins are **trusted code**: moderators/owners install them, and they can run both client-side and server-side logic. 6 7 ## Included plugins at a glance 8 9 Use this quick reference when packaging releases or deciding what to enable by default. 10 11 | Plugin | What it adds | Where to install | Notes | 12 |---|---|---|---| 13 | `maps` | Spatial map rooms, local/global map chat, movement, collisions, TTRPG-style props/sprites | Any instance | Good for social hub worlds and events. | 14 | `library` | Shelf/Reader workflow, text + PDF style content management, community borrowing/return flow | Any instance | Best used as persistent knowledge + story archive. | 15 | `radio` | Community radio stations with uploaded tracks and shared listening | Any instance | Requires upload storage; tune in from panel UI. | 16 | `dice` | Shared dice roller (`XdY+Z`) broadcast to connected users | Any instance | Lightweight RP/TTRPG utility. | 17 | `godot` | Template plugin for hosting a bundled Godot HTML5 export from `godotapp/` | Any instance | Put your export files in `plugins_dev/godot/godotapp/` before build. | 18 | `directory-server` | Public directory endpoint + moderation queue for listings (`/api/plugins/directory-server/list`) | **Directory host instance only** | Acts as the central directory service. | 19 | `directory-publisher` | Sends this instance announcement payloads to a directory server | Any instance that wants listing | Configure directory URL + instance URL/name, then publish. | 20 21 ## Install / manage (Moderator/Owner UI) 22 23 1. Sign in as `owner` or `moderator`. 24 2. Open the **Instance** panel in the left sidebar. 25 3. Under **Plugins**: 26 - Upload a plugin `.zip` (must contain a `plugin.json` manifest). 27 - Toggle **Enabled** to activate it. 28 - Use **Uninstall** to remove it from disk. 29 30 Notes: 31 - Enabling/disabling a plugin may require a refresh for all connected clients (depends on what the plugin does). 32 33 ### Dev: build the included example plugin zips 34 This repo includes starter plugins and zip builders: 35 - Maps: `plugins_dev/maps/` 36 - Build: `node scripts/build-maps-plugin.js` 37 - Upload: `dist/plugins/maps.zip` 38 - Library: `plugins_dev/library/` 39 - Build: `node scripts/build-library-plugin.js` 40 - Upload: `dist/plugins/library.zip` 41 - Radio: `plugins_dev/radio/` 42 - Build: `node scripts/build-radio-plugin.js` 43 - Upload: `dist/plugins/radio.zip` 44 - Dice: `plugins_dev/dice/` 45 - Build: `node scripts/build-dice-plugin.js` 46 - Upload: `dist/plugins/dice.zip` 47 - Godot: `plugins_dev/godot/` 48 - Build: `node scripts/build-godot-plugin.js` 49 - Upload: `dist/plugins/godot.zip` 50 - Bundle app: place your Godot HTML5 export in `plugins_dev/godot/godotapp/` (entry file must be `index.html`) 51 - Directory Server (draft): `plugins_dev/directory-server/` 52 - Build: `node scripts/build-directory-server-plugin.js` 53 - Upload: `dist/plugins/directory-server.zip` 54 - Directory Publisher (draft): `plugins_dev/directory-publisher/` 55 - Build: `node scripts/build-directory-publisher-plugin.js` 56 - Upload: `dist/plugins/directory-publisher.zip` 57 58 ## Where plugins live on disk 59 60 - Installed plugins are stored at: `data/plugins/<pluginId>/` 61 - Each plugin folder must contain: `plugin.json` 62 63 ## `plugin.json` manifest 64 65 Required: 66 - `id`: `^[a-z0-9][a-z0-9_.-]{0,31}$` 67 - `name` 68 - `version` 69 70 Optional: 71 - `description` 72 - `entryClient`: path to a client JS file within the plugin folder 73 - `entryServer`: path to a server JS file within the plugin folder 74 - `permissions`: string labels shown in the UI (informational) 75 76 Example: 77 ```json 78 { 79 "id": "polls", 80 "name": "Polls", 81 "version": "0.1.0", 82 "description": "Adds simple polls to hives.", 83 "entryClient": "client.js", 84 "entryServer": "server.js", 85 "permissions": ["ui", "ws"] 86 } 87 ``` 88 89 ## Zip format 90 91 The `.zip` you upload must include `plugin.json` either: 92 - at the zip root, or 93 - inside a single top-level folder 94 95 After extraction, the server installs it into `data/plugins/<id>/`. 96 97 ## Client plugin API 98 99 If your plugin has `entryClient`, it is loaded from: 100 - `/plugins/<id>/<entryClient>` 101 102 ### UI panels (draft) 103 104 Some plugins are primarily UI. We’re planning a UI “rack layout” where plugins (and core features) register **dockable panels** instead of directly manipulating core DOM. 105 106 See: `docs/UI_RACK_LAYOUT.md` (draft). 107 108 Client plugins can register via: 109 ```js 110 window.BzlPluginHost.register("polls", (ctx) => { 111 ctx.toast("Polls", "Plugin loaded!"); 112 113 // Send a plugin WS message to the server: 114 ctx.send("ping", { hello: true }); 115 }); 116 ``` 117 118 `ctx` provides: 119 - `ctx.id` 120 - `ctx.toast(title, body)` 121 - `ctx.getUser()` / `ctx.getRole()` 122 - `ctx.ui.registerPanel(panelDef)` (experimental; only active when the core rack layout is enabled) 123 - `ctx.send(eventName, payload)` -> sends `{ type: "plugin:<id>:<eventName>", ...payload }` 124 - `ctx.devLog(level, message, data)` -> writes to the in-app dev log (Moderation -> Log -> Server dev log) 125 126 ### `ctx.ui.registerPanel(panelDef)` (experimental) 127 128 This registers a dockable UI panel for your plugin under the rack layout system. 129 130 Notes: 131 - This is currently **experimental** and only works when the user has enabled the rack layout UI. 132 - If rack layout is disabled, your plugin should still work using the existing DOM integration patterns. 133 134 Example: 135 ```js 136 window.BzlPluginHost.register("hello", (ctx) => { 137 ctx.ui?.registerPanel?.({ 138 id: "hello", 139 title: "Hello", 140 icon: "👋", 141 defaultRack: "right", // or "main" 142 role: "aux", // "primary" | "aux" | "transient" | "utility" 143 presetHints: { 144 discordLike: { place: "docked.bottom" } 145 }, 146 render(mount, api) { 147 mount.innerHTML = "<div class='small'>Hello from a plugin panel.</div>"; 148 } 149 }); 150 }); 151 ``` 152 153 ## Server plugin API 154 155 If your plugin has `entryServer`, it must export a function: 156 ```js 157 module.exports = function init(api) { 158 api.registerWs("ping", (ws, msg) => { 159 api.broadcast({ type: "plugin:polls:pong", at: api.now() }); 160 }); 161 }; 162 ``` 163 164 `api` provides: 165 - `api.id` 166 - `api.now()` 167 - `api.log(level, message, data)` 168 - `api.registerWs(eventName, handler)` 169 - `api.registerHttp(method, routePath, handler)` 170 - `api.broadcast(message)` (must have `type` prefixed with `plugin:<id>:`) 171 172 Server handlers are invoked when the client sends: 173 - `type: "plugin:<id>:<eventName>"` 174 175 ### Plugin HTTP routes (draft) 176 177 Server plugins can optionally register same-origin HTTP endpoints under: 178 - `/api/plugins/<pluginId>/<routePath>` 179 180 Example: 181 ```js 182 module.exports = function init(api) { 183 api.registerHttp("GET", "/list", (req, res, ctx) => { 184 ctx.sendJson(200, { ok: true, items: [] }); 185 }); 186 }; 187 ``` 188 189 The `handler` receives `(req, res, ctx)` where `ctx` provides: 190 - `ctx.readJsonBody({ maxBytes })` (async) 191 - `ctx.sendJson(status, body)` 192 193 ## Security warning 194 195 Plugins run as code on your server and in every client browser. 196 - Only install plugins you trust. 197 - Treat plugins like installing an app, not a theme.