# Plugins Plugins extend MeshChatX with extra tools, nav items, and background behaviour. They are capability-gated: a plugin only gets what you grant at install time. Manage them from **Settings → Plugins**. Disable every packaged plugin at startup with `--disable-plugins` or `MESHCHAT_DISABLE_PLUGINS=true`. ## What plugins can do - Add a row on the **Tools** page - Add an item in the main **Navigation** sidebar - React to mesh events (announces, RNS link traffic) - Call narrowly declared backend managers (path table, debug log, bug reports, RNS links) - Keep a private key-value store (`storage: isolated`) - Optionally fetch clearnet HTTP (`network: fetch`), still subject to **Privacy mode** Plugins cannot rewrite core MeshChatX. They do not get open-ended filesystem or process control unless you opt into Sideband Python plugins (see below). ## Runtimes | Runtime | Where it runs | Trust level | | --------------- | ------------------------- | ----------------------------------------------- | | Frontend JS | Browser Web Worker | Medium. Sandboxed worker, capability grants | | Backend WASM | `wasmtime` on the server | Medium. Fuel-metered, capability-gated host | | Backend Python | In-process with MeshChatX | High. Permission-checked in-process host | | Sideband `*.py` | In-process, flat files | Highest. Opt-in danger switch, full host access | A packaged plugin can ship frontend only, backend only, or both. ## Install flow ``` Pick ZIP or .wasm file in Settings → Plugins | --> Preview (permissions, URLs, signature, findings) | --> You grant or deny each capability | --> Optional: trust a valid signer | --> Install + integrity hash stored | --> Enable | --> Frontend Worker loads (if present) --> Backend WASM / Python activates (if present) ``` Invalid signatures hard-block install. Unsigned packages are allowed. Present-but-broken signatures are not. After install, MeshChatX hashes the on-disk tree. If files change outside the app, the plugin is auto-disabled as tampered. ## Bundled example: Bug Reports `com.meshchatx.mcx-bugs` ships with MeshChatX. It adds a **Bug Reports** tool for sending redacted debug logs to an `mcx-bugs-v1` collector, or running a collector yourself. Layout: ``` mcx-bugs/ plugin.json frontend/main.js backend/main.py locales/en.json ``` Use it as the reference package when building your own. ## Manifest (`plugin.json`) Every packaged plugin needs a root `plugin.json`. ```json { "id": "com.example.my-plugin", "version": "1.0.0", "apiVersion": 1, "name": "My Plugin", "description": "Adds a custom tool.", "frontend": { "entry": "frontend/main.js", "type": "js" }, "backend": { "entry": "backend/main.py", "type": "python" }, "i18n": { "directory": "locales", "defaultLocale": "en" }, "contributes": { "navItems": [ { "id": "my-plugin", "route": { "name": "plugin-my-plugin" }, "icon": "puzzle", "labelKey": "nav" } ], "toolsPageEntries": [ { "name": "my-plugin", "route": { "name": "plugin-my-plugin" }, "icon": "puzzle", "titleKey": "title", "descriptionKey": "description" } ] }, "permissions": { "hooks": ["announce.received"], "managers": ["destinationPath.read"], "storage": "isolated", "network": "none" } } ``` Notes: - `id` is reverse-DNS style and must stay stable across versions - `apiVersion` is currently `1` - Plugin strings live in the plugin bundle (`locales/{locale}.json`), not core `en.json` - `contributes` wires UI slots through the frontend registries ## Permissions Nothing is available unless it is declared in the manifest and granted in the install dialog. ### Hooks | Hook | When it fires | | ------------------- | ----------------------------------------------------------- | | `announce.received` | A Reticulum announce arrives | | `rns.link.event` | Generic RNS Link traffic (`packet_received`, `link_closed`) | Hook events reach the UI as WebSocket `plugin.event` frames, then into the plugin Worker. ### Managers | Manager | Purpose | | ---------------------- | ----------------------------- | | `destinationPath.read` | Read the Reticulum path table | | `debugLog.read` | Read redacted debug logs | | `bugReport.*` | Bug report / collector APIs | | `rnsLink.open` | Open or reuse an RNS link | | `rnsLink.identify` | Identify on a cached link | | `rnsLink.request` | Request/response on a link | | `rnsLink.send` | Send a raw link packet | | `rnsLink.close` | Tear down a cached link | Call managers from a plugin with `POST /api/v1/plugins/{id}/invoke` and `method: "callManager"`. Details for the link transport are in [RNS Link API](rns-link-api.md). ### Storage and network | Permission | Effect | | ------------------- | --------------------------------------------------------------------- | | `storage: isolated` | Private key-value store in the MeshChatX database | | `storage: none` | No plugin storage | | `network: fetch` | Outbound HTTP allowed (still blocked by Privacy mode when that is on) | | `network: none` | No clearnet fetch | Install preview also scans plugin files for external `http://` / `https://` URLs and shows them before you grant network access. ## How a frontend plugin runs ``` Settings enable plugin | --> PluginHost loads /api/v1/plugins | --> Fetch frontend entry as text | --> Spawn pluginWorker.js (module Worker) | --> Register nav / tools contributions | --> Subscribe to plugin.event on /ws (if hooks granted) | --> Worker may invoke backend via /api/v1/plugins/{id}/invoke ``` The Worker talks to the host with typed messages (`init`, `event`, `request`). The host never gives the Worker a raw privileged API. ## How a backend plugin runs ``` Enable plugin | +--> type: wasm --> load into wasmtime, fuel + host caps | +--> type: python --> import entry, call activate(host) | --> Hooks fan out from PluginManager | --> invoke(method, args) for RPC from the UI Worker ``` Python host surface (permission-checked): - `host.log(message)` - `host.call_manager(capability, args)` - `host.storage_get(key)` / `host.storage_set(key, value)` - `host.network_fetch_allowed()` ## Packaging and signing Distribute as: 1. **ZIP** with `plugin.json` and assets 2. **WASM bundle** (single `.wasm` with embedded manifest / files / optional signature) Signature file for ZIP/dir packages: `meshchatx.plugin.rsg` WASM custom sections: ``` meshchatx.plugin --> embedded plugin.json meshchatx.files --> embedded text assets meshchatx.signature --> RSG over payload without this section ``` Canonical ZIP signing uses sorted paths and fixed 1980-01-01 mtimes. The signature file itself is excluded from the signed payload. Sign and verify with: ```bash python3 scripts/sign-plugin.py sign-dir ./my-plugin --identity python3 scripts/sign-plugin.py verify-dir ./my-plugin python3 scripts/sign-plugin.py sign-zip ./my-plugin.zip --identity python3 scripts/sign-plugin.py sign-wasm ./plugin.wasm --identity python3 scripts/sign-plugin.py sign-py ./legacy_plugin.py --identity ``` Trust status in the UI: ``` No .rsg present --> Unsigned (install allowed) Valid .rsg, signer unknown --> Signed (you can add to Trusted Publishers) Valid .rsg, signer in Trusted Publishers --> Trusted Broken / mismatched .rsg --> Invalid (install blocked) ``` ## Sideband-compatible plugins Legacy Sideband-style flat `*.py` files are separate from packaged ZIP/WASM plugins. ``` Settings → Plugins → Sideband | --> Confirm danger prompt | --> Set directory of *.py files | --> Optional filename.py.rsg next to each script | --> Reload ``` These run in-process with full host access. They are not ZIP-permission gated. Keep the master switch off unless you trust every file in that directory. ## Operator tips - Prefer signed packages from publishers you added yourself - Deny `network: fetch` unless the plugin needs clearnet HTTP - Prefer WASM backends over Python when you can - Use `--disable-plugins` when diagnosing weird UI or backend behaviour - Treat Sideband plugins like running arbitrary local scripts ## See also - [Tools and utilities](tools.md) for the Tools page and contribution overview - [RNS Link API](rns-link-api.md) for `rnsLink.*` and `rns.link.event` - [Architecture and design](architecture.md) for the plugin runtime overview - [Identities, privacy, and security](identity-and-security.md) for signing and Privacy mode