MeshChatX/docs/en/plugins.md
2026-08-03 20:53:12 -05:00

9.2 KiB

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.

{
    "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.

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:

python3 scripts/sign-plugin.py sign-dir ./my-plugin --identity <rnid>
python3 scripts/sign-plugin.py verify-dir ./my-plugin
python3 scripts/sign-plugin.py sign-zip ./my-plugin.zip --identity <rnid>
python3 scripts/sign-plugin.py sign-wasm ./plugin.wasm --identity <rnid>
python3 scripts/sign-plugin.py sign-py ./legacy_plugin.py --identity <rnid>

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