### Example plugin folder structure Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/DEVELOPER_GUIDE.md Shows the recommended folder layout for a process plugin, including the manifest, main code, and a Windows launcher. ```text my-plugin/ ├── plugin.toml the manifest ├── main.js your code └── plugin.cmd a launcher (Windows), optional but convenient ``` -------------------------------- ### Example plugin.toml manifest Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/MANIFEST.md Complete example of a plugin.toml manifest file. It declares the plugin's identity, runtime configuration, and capabilities. The manifest is required at the root of every plugin artifact. ```toml id = "community.drops-farmer" name = "Drops and Points Tracker" version = "1.2.0" author = "communityhandle" tier = "C" description = "Background drops and channel-points tracking." homepage = "https://example.org/drops-farmer" host_min = "8.0.0" [runtime] kind = "process" entry = "drops-farmer.exe" args = [] transport = "stdio" [capabilities] events = ["on_watch_tick", "on_followed_live", "on_channel_change"] host_methods = ["get_followed_live", "notify", "log", "register_panel", "get_panel_values"] credentials = ["twitch.android"] network = "external" ui = ["panel"] ``` -------------------------------- ### Index document JSON example Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/SIGNING.md Example of an index document in JSON format, including format version, operator details, and plugin entries with artifact information. ```json { "format": 1, "name": "Example Community Index", "operator": "examplecollective", "operator_pubkey": "RW...base64...", "generated_at": "2026-06-10T00:00:00Z", "previous_operator_pubkeys": [], "plugins": [ { "id": "community.example", "name": "Example Plugin", "version": "1.2.0", "tier": "B", "description": "Short plain-language description.", "homepage": "https://example.org", "host_min": "8.0.0", "released_at": "2026-06-01T00:00:00Z", "author": { "name": "communityhandle", "pubkey": "RW...base64...", "previous_pubkeys": [] }, "artifact": { "url": "https://example.org/dl/community.example-1.2.0.zip", "sha256": "", "size": 1234567, "signature_url": "https://example.org/dl/community.example-1.2.0.zip.minisig" } } ] } ``` -------------------------------- ### Create signing key with minisign Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/DEVELOPER_GUIDE.md Run `minisign -G` to generate a new signing key pair. Keep the secret key offline; the public key identifies you to users and is pinned on first install. ```bash minisign -G ``` -------------------------------- ### register_panel panel schema JSON Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/PROTOCOL.md Example panel schema for register_panel. Field types include toggle, number, select, text, string_list, channel_list, slider, and folder. The host renders the panel; the plugin never gets arbitrary UI access. ```json { "title": "Example Plugin", "sections": [ { "label": "Behavior", "description": "Optional section description", "fields": [ { "key": "enabled", "type": "toggle", "label": "Enable", "description": "...", "default": false }, { "key": "slots", "type": "number", "label": "Slots", "min": 1, "max": 2, "default": 2 }, { "key": "mode", "type": "select", "label": "Mode", "options": [ { "value": "a", "label": "Mode A" } ], "default": "a" }, { "key": "note", "type": "text", "label": "Note", "placeholder": "...", "default": "" }, { "key": "channels", "type": "string_list", "label": "Channels" } ] } ] } ``` -------------------------------- ### initialize request (host to plugin) Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/PROTOCOL.md Sent by the host after spawning the plugin. The plugin must respond within 10 seconds. The 'granted' field is the subset of requested capabilities the user granted; the plugin must work with any subset. ```json { "protocol_version": 1, "host_version": "7.8.6", "plugin_id": "community.example", "granted": { "events": ["on_watch_tick", "on_followed_live"], "host_methods": ["get_followed_live", "notify", "log"], "credentials": ["twitch.android"], "ui": ["panel"] }, "data_dir": "", "log_dir": "" } ``` -------------------------------- ### get_credential result JSON Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/PROTOCOL.md Example result for get_credential. Fields other than kind and token are nullable. The first call for a kind may block up to 120 seconds for consent. ```json { "kind": "twitch.android", "token": "...", "client_id": "...", "user_id": "...", "device_id": "...", "expires_at": "2026-06-11T00:00:00Z" } ``` -------------------------------- ### initialize result (plugin to host) Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/PROTOCOL.md Response to the initialize request. The 'hooks' list must be a subset of 'granted.events'; names outside it are ignored. The host only emits events listed in 'hooks'. ```json { "plugin_version": "1.2.0", "hooks": ["on_watch_tick", "on_followed_live"] } ``` -------------------------------- ### Windows launcher plugin.cmd Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/DEVELOPER_GUIDE.md A one-line batch launcher that runs the main.js script with Node. This is the simplest way to run an interpreted script on Windows. ```bat @echo off node "%~dp0main.js" ``` -------------------------------- ### React shim re-exporting host copy Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/UI_PLUGINS.md Each shim re-exports the host copy of a shared library. This example shows the React shim, which is required for hooks to work when both sides run the same React instance. ```javascript module.exports = globalThis.__STREAMNOOK_HOST_LIBS__.react; ``` -------------------------------- ### Declare plugin hooks in [contributes] Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/MANIFEST.md Optional TOML block that lists the named hooks the plugin fills so the native UI can delegate to it. Each entry is a namespaced id (feature.name); non-namespaced ids are rejected at install. ```toml [contributes] actions = ["drops.mine", "drops.stop"] # actions the plugin handles when the UI invokes them status = ["drops.status"] # status slots the plugin pushes into for the UI to show provides = ["drops.mining"] # feature flags the host uses to light up matching controls ``` -------------------------------- ### Sign and hash the release Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/DEVELOPER_GUIDE.md Sign the zip with minisign and compute its SHA256 hash. The signature file is named `.minisig`. ```bash minisign -Sm my-plugin-1.0.0.zip produces my-plugin-1.0.0.zip.minisig certutil -hashfile my-plugin-1.0.0.zip SHA256 ``` -------------------------------- ### plugin.toml manifest for a process plugin Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/DEVELOPER_GUIDE.md Defines the plugin manifest with id, runtime, and capabilities. The id must be unique and never change. The capabilities section is the permission contract; request only what you use. network = "none" is a promise to the user. ```toml id = "yourhandle.my-plugin" name = "My Plugin" version = "0.1.0" author = "yourhandle" tier = "A" description = "Logs watch ticks and adds a settings panel." host_min = "7.0.0" [runtime] kind = "process" entry = "plugin.cmd" args = [] transport = "stdio" [capabilities] events = ["on_watch_tick", "on_followed_live", "on_panel_change"] host_methods = ["notify", "log", "register_panel", "get_panel_values"] credentials = [] network = "none" ui = ["panel"] ``` -------------------------------- ### playback.resolve action args Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/HOOKS.md Arguments for the playback.resolve action, invoked when a live stream starts and the viewer is not entitled. The plugin keeps stream_id for later set_upstream calls. auth_master is core's own resolution if it succeeded. ```json { "stream_id": "solo", "channel": "somechannel", "quality": "best", "auth_master": "" } ``` -------------------------------- ### Hybrid plugin runtime configuration with ui_entry Source: https://github.com/winters27/streamnook/blob/main/docs/plugins/UI_PLUGINS.md Add ui_entry to the [runtime] block of a process plugin to also ship a UI module. The host runs both the sidecar and the UI module when enabled, and shuts both down when disabled. ```toml [runtime] kind = "process" entry = "worker.exe" # the sidecar ui_entry = "dist/main.js" # the in-app UI module transport = "stdio" ```