diff --git a/plugins/README.md b/plugins/README.md index d8761c282..13cdc2abd 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -29,38 +29,41 @@ Navidrome supports WebAssembly (Wasm) plugins for extending functionality. Plugi ### 1. Create a minimal plugin +Create `main.go`: + ```go package main -import ( - "encoding/json" - "github.com/extism/go-pdk" -) - -//go:wasmexport nd_manifest -func ndManifest() int32 { - manifest := map[string]string{ - "name": "My Plugin", - "author": "Your Name", - "version": "1.0.0", - } - out, _ := json.Marshal(manifest) - pdk.Output(out) - return 0 -} +import "github.com/extism/go-pdk" func main() {} + +// Implement your capability functions here ``` -### 2. Build with TinyGo +Create `manifest.json`: + +```json +{ + "name": "My Plugin", + "author": "Your Name", + "version": "1.0.0" +} +``` + +### 2. Build with TinyGo and package as .ndp ```bash -tinygo build -o my-plugin.wasm -target wasip1 -buildmode=c-shared . +# Compile to WebAssembly +tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared . + +# Package as .ndp (zip archive) +zip -j my-plugin.ndp manifest.json plugin.wasm ``` ### 3. Install -Copy `my-plugin.wasm` to your Navidrome plugins folder and enable plugins in your config: +Copy `my-plugin.ndp` to your Navidrome plugins folder and enable plugins in your config: ```toml [Plugins] @@ -74,23 +77,31 @@ Folder = "/path/to/plugins" ### What is a Plugin? -A Navidrome plugin is a WebAssembly (`.wasm`) file that: +A Navidrome plugin is an `.ndp` package file (zip archive) containing: -1. **Exports `nd_manifest`** – Returns JSON describing the plugin -2. **Exports capability functions** – Implements one or more capabilities +1. **`manifest.json`** – Plugin metadata (name, author, version, permissions) +2. **`plugin.wasm`** – Compiled WebAssembly module with capability functions + +### Plugin Package Structure + +``` +my-plugin.ndp (zip archive) +├── manifest.json # Required: Plugin metadata +└── plugin.wasm # Required: Compiled WebAssembly module +``` ### Plugin Naming -Plugins are identified by their **filename** (without `.wasm` extension), not the manifest `name` field: +Plugins are identified by their **filename** (without `.ndp` extension), not the manifest `name` field: -- `my-plugin.wasm` → plugin ID is `my-plugin` +- `my-plugin.ndp` → plugin ID is `my-plugin` - The manifest `name` is the display name shown in the UI This allows users to have multiple instances of the same plugin with different configs by renaming the files. ### The Manifest -Every plugin must export `nd_manifest` returning JSON: +Every plugin must include a `manifest.json` file. Example: ```json { @@ -110,8 +121,6 @@ Every plugin must export `nd_manifest` returning JSON: **Required fields:** `name`, `author`, `version` -**Capabilities are auto-detected** from which functions your plugin exports. You don't declare them in the manifest. - --- ## Capabilities @@ -572,12 +581,12 @@ Enabled = true Folder = "/path/to/plugins" # Default: DataFolder/plugins AutoReload = true # Auto-reload on file changes (dev mode) LogLevel = "debug" # Plugin-specific log level -CacheSize = "100MB" # Compilation cache size limit +CacheSize = "200MB" # Compilation cache size limit ``` ### Plugin Configuration -Plugin configuration is managed through the Navidrome web UI. Navigate to the Plugins page, select a plugin, and edit its configuration as a JSON object with string key-value pairs. +Plugin configuration is managed through the Navidrome web UI. Navigate to the Plugins page, select a plugin, and edit its configuration as key-value pairs. Access configuration values in your plugin: @@ -607,8 +616,31 @@ Plugins can be written in any language that compiles to WebAssembly. We recommen ```bash # Install TinyGo: https://tinygo.org/getting-started/install/ -# Build -tinygo build -o my-plugin.wasm -target wasip1 -buildmode=c-shared . +# Build WebAssembly module +tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared . + +# Package as .ndp +zip -j my-plugin.ndp manifest.json plugin.wasm +``` + +### Rust + +```bash +# Build WebAssembly module +cargo build --release --target wasm32-unknown-unknown + +# Package as .ndp +zip -j my-plugin.ndp manifest.json target/wasm32-unknown-unknown/release/plugin.wasm +``` + +### Python (with extism-py) + +```bash +# Build WebAssembly module (requires extism-py installed) +extism-py plugin.wasm -o plugin.wasm *.py + +# Package as .ndp +zip -j my-plugin.ndp manifest.json plugin.wasm ``` ### Using XTP CLI (Scaffolding) @@ -625,8 +657,9 @@ xtp plugin init \ --path ./my-agent \ --name my-agent -# Build +# Build and package cd my-agent && xtp plugin build +zip -j my-agent.ndp manifest.json dist/plugin.wasm ``` See [schemas/README.md](schemas/README.md) for available schemas. @@ -671,16 +704,18 @@ Plugins run in a secure WebAssembly sandbox: ## Runtime Management -### Auto-Reload (Development) +### Auto-Reload -With `AutoReload = true`, Navidrome watches the plugins folder and automatically reloads plugins when files change. +With `AutoReload = true`, Navidrome watches the plugins folder and automatically detects when `.ndp` files are added, modified, or removed. When a plugin file changes, the plugin is disabled and its metadata is re-read from the archive. -### Programmatic Control +If the `AutoReload` setting is disabled, Navidrome needs to be restarted to pick up plugin changes. -Plugins can be enabled/disabled via the Navidrome UI or API. The plugin state is persisted in the database. +### Enabling/Disabling Plugins + +Plugins can be enabled/disabled via the Navidrome UI. The plugin state is persisted in the database. ### Important Notes - **In-flight requests** – When reloading, existing requests complete before the new version takes over -- **Config changes** – Plugin configuration is read at load time; changes require a reload +- **Config changes** – Changes to the plugin configuration in the UI are applied immediately - **Cache persistence** – The in-memory cache is cleared when a plugin is unloaded diff --git a/plugins/examples/README.md b/plugins/examples/README.md index 944482267..4177a72c9 100644 --- a/plugins/examples/README.md +++ b/plugins/examples/README.md @@ -23,18 +23,20 @@ This folder contains example plugins demonstrating various capabilities and lang - **Python plugins:** [extism-py](https://github.com/extism/python-pdk) - **Rust plugins:** [Rust](https://rustup.rs/) with `wasm32-unknown-unknown` target -### Build All (Go plugins) +### Build All Plugins ```bash make all ``` +This creates `.ndp` package files for each plugin. + ### Build Individual Plugin ```bash -make minimal.wasm -make wikimedia.wasm -make discord-rich-presence.wasm +make minimal.ndp +make wikimedia.ndp +make discord-rich-presence.ndp ``` ### Clean @@ -47,15 +49,15 @@ make clean ### With Extism CLI -Test any plugin without running Navidrome: +Test any plugin without running Navidrome. First extract the `.wasm` file from the `.ndp` package: ```bash # Install: https://extism.org/docs/install -# Test manifest -extism call minimal.wasm nd_manifest --wasi +# Extract the wasm file from the package +unzip -p minimal.ndp plugin.wasm > minimal.wasm -# Test with input +# Test a capability function extism call minimal.wasm nd_get_artist_biography --wasi \ --input '{"id":"1","name":"The Beatles"}' ``` @@ -63,6 +65,7 @@ extism call minimal.wasm nd_get_artist_biography --wasi \ For plugins that make HTTP requests, allow the hosts: ```bash +unzip -p wikimedia.ndp plugin.wasm > wikimedia.wasm extism call wikimedia.wasm nd_get_artist_biography --wasi \ --input '{"id":"1","name":"Yussef Dayes"}' \ --allow-host "query.wikidata.org" \ @@ -71,7 +74,7 @@ extism call wikimedia.wasm nd_get_artist_biography --wasi \ ### With Navidrome -1. Copy the `.wasm` file to your plugins folder +1. Copy the `.ndp` file to your plugins folder 2. Enable plugins in `navidrome.toml`: ```toml [Plugins] @@ -92,8 +95,9 @@ Copy the [minimal](minimal/) example and modify: ```bash cp -r minimal my-plugin cd my-plugin -# Edit main.go -tinygo build -o my-plugin.wasm -target wasip1 -buildmode=c-shared . +# Edit main.go and manifest.json +tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared . +zip -j my-plugin.ndp manifest.json plugin.wasm ``` ### Option 2: Bootstrap with XTP CLI @@ -108,6 +112,11 @@ xtp plugin init \ --template go \ --path ./my-plugin \ --name my-plugin + +# Then create manifest.json and package +cd my-plugin +xtp plugin build +zip -j my-plugin.ndp manifest.json dist/plugin.wasm ``` Available schemas in [../schemas/](../schemas/): diff --git a/plugins/examples/coverartarchive-py/README.md b/plugins/examples/coverartarchive-py/README.md index eb6ffe7ab..77957ac4f 100644 --- a/plugins/examples/coverartarchive-py/README.md +++ b/plugins/examples/coverartarchive-py/README.md @@ -23,18 +23,19 @@ A Python example plugin that fetches album cover images from the [Cover Art Arch From the `plugins/examples` directory: ```bash -make coverartarchive-py.wasm +make coverartarchive-py.ndp ``` Or directly: ```bash -extism-py plugin/__init__.py -o coverartarchive-py.wasm +extism-py plugin/__init__.py -o plugin.wasm +zip -j coverartarchive-py.ndp manifest.json plugin.wasm ``` ## Installation -1. Copy `coverartarchive-py.wasm` to your Navidrome plugins folder +1. Copy `coverartarchive-py.ndp` to your Navidrome plugins folder 2. Enable plugins in `navidrome.toml`: ```toml @@ -50,15 +51,10 @@ extism-py plugin/__init__.py -o coverartarchive-py.wasm ## Testing -Test the manifest: - -```bash -extism call coverartarchive-py.wasm nd_manifest --wasi -``` - -Test album image retrieval (using Portishead's "Dummy" MBID): +Extract the wasm file and test: ```bash +unzip -p coverartarchive-py.ndp plugin.wasm > coverartarchive-py.wasm extism call coverartarchive-py.wasm nd_get_album_images --wasi \ --input '{"name":"Dummy","artist":"Portishead","mbid":"76df3287-6cda-33eb-8e9a-044b5e15ffdd"}' \ --allow-host "coverartarchive.org" --allow-host "archive.org" diff --git a/plugins/examples/crypto-ticker/README.md b/plugins/examples/crypto-ticker/README.md index ba4a97505..821cce119 100644 --- a/plugins/examples/crypto-ticker/README.md +++ b/plugins/examples/crypto-ticker/README.md @@ -14,15 +14,11 @@ This is a WebSocket-based WASM plugin for Navidrome that displays real-time cryp Configure in the Navidrome UI (Settings → Plugins → crypto-ticker): -```json -{ - "tickers": "BTC,ETH,SOL,MATIC" -} -``` +| Key | Description | Default | +|-----------|----------------------------------------------------------------------|-----------| +| `tickers` | Comma-separated list of cryptocurrency symbols (e.g., `BTC,ETH,SOL`) | `BTC,ETH` | -- `tickers` is a comma-separated list of cryptocurrency symbols -- The plugin will append `-USD` to any symbol without a trading pair specified -- Default: `BTC,ETH` if not configured +The plugin will append `-USD` to any symbol without a trading pair specified. ## How it Works @@ -40,19 +36,23 @@ This plugin was scaffolded using XTP CLI: xtp plugin init --schema-file ../schemas/websocket_callback.yaml --template go --path ./crypto-ticker --name crypto-ticker ``` -To build the plugin to WASM: +To build the plugin and package as `.ndp`: ```bash # Using TinyGo (recommended - smaller binary) -tinygo build -o crypto-ticker.wasm -target wasip1 -buildmode=c-shared . +tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared . +zip -j crypto-ticker.ndp manifest.json plugin.wasm +``` -# Or using standard Go -GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o crypto-ticker.wasm . +Or from the `plugins/examples/` directory: + +```bash +make crypto-ticker.ndp ``` ## Installation -Copy the resulting `crypto-ticker.wasm` to your Navidrome plugins folder. +Copy the resulting `crypto-ticker.ndp` to your Navidrome plugins folder. ## Example Output diff --git a/plugins/examples/discord-rich-presence/README.md b/plugins/examples/discord-rich-presence/README.md index 397e4ecf5..ba7706be0 100644 --- a/plugins/examples/discord-rich-presence/README.md +++ b/plugins/examples/discord-rich-presence/README.md @@ -63,34 +63,30 @@ To work within this model the plugin stores no in-memory state. Connections are Configure in the Navidrome UI (Settings → Plugins → discord-rich-presence): -```json -{ - "clientid": "123456789012345678", - "users": "alice:token123,bob:token456" -} -``` - -- `ClientID` is your Discord application ID -- `Users` is a comma-separated list of `username:token` pairs used for authorization +| Key | Description | Example | +|------------|----------------------------------------------------------|--------------------------------| +| `clientid` | Your Discord application ID | `123456789012345678` | +| `users` | Comma-separated list of `username:token` pairs | `alice:token123,bob:token456` | ## Building From the `plugins/examples/` directory: ```sh -make discord-rich-presence.wasm +make discord-rich-presence.ndp ``` Or manually: ```sh cd discord-rich-presence -tinygo build -target wasip1 -buildmode=c-shared -o ../discord-rich-presence.wasm . +tinygo build -target wasip1 -buildmode=c-shared -o plugin.wasm . +zip -j discord-rich-presence.ndp manifest.json plugin.wasm ``` ## Installation -Place the resulting `discord-rich-presence.wasm` in your Navidrome plugins folder and enable plugins in your configuration: +Place the resulting `discord-rich-presence.ndp` in your Navidrome plugins folder and enable plugins in your configuration: ```toml [Plugins] @@ -100,21 +96,21 @@ Folder = "/path/to/plugins" ## Files -| File | Description | -|------|-------------| -| `main.go` | Plugin entry point, manifest, scrobbler implementation | -| `rpc.go` | Discord gateway communication and RPC logic | -| `pdk.gen.go` | Generated types from XTP schemas (combined) | +| File | Description | +|----------------|---------------------------------------------------------| +| `main.go` | Plugin entry point, manifest, scrobbler implementation | +| `rpc.go` | Discord gateway communication and RPC logic | +| `pdk.gen.go` | Generated types from XTP schemas (combined) | | `nd_host_*.go` | Host function wrappers (copied from `plugins/host/go/`) | ## Host Services Used -| Service | Purpose | -|---------|---------| -| Cache | Store Discord sequence numbers and processed image URLs | +| Service | Purpose | +|-----------|------------------------------------------------------------------| +| Cache | Store Discord sequence numbers and processed image URLs | | Scheduler | Schedule heartbeats (recurring) and activity clearing (one-time) | -| WebSocket | Maintain persistent connection to Discord gateway | -| Artwork | Get track artwork URLs for rich presence display | +| WebSocket | Maintain persistent connection to Discord gateway | +| Artwork | Get track artwork URLs for rich presence display | ## Implementation Details diff --git a/plugins/examples/library-inspector/README.md b/plugins/examples/library-inspector/README.md index 3b5af7859..dc159d3d9 100644 --- a/plugins/examples/library-inspector/README.md +++ b/plugins/examples/library-inspector/README.md @@ -23,19 +23,20 @@ rustup target add wasm32-wasip1 # Build the plugin cargo build --target wasm32-wasip1 --release -# The output will be at target/wasm32-wasip1/release/library_inspector.wasm +# Package as .ndp +zip -j library-inspector.ndp manifest.json target/wasm32-wasip1/release/library_inspector.wasm ``` Or use the provided Makefile from the examples directory: ```bash cd plugins/examples -make library-inspector.wasm +make library-inspector.ndp ``` ## Installation -1. Copy the `.wasm` file to your Navidrome plugins folder +1. Copy the `.ndp` file to your Navidrome plugins folder 2. Enable plugins in your Navidrome configuration: ```toml @@ -50,11 +51,9 @@ Folder = "/path/to/plugins" Configure the inspection interval in the Navidrome UI (Settings → Plugins → library-inspector): -```json -{ - "cron": "@every 5m" -} -``` +| Key | Description | Default | +|--------|------------------------------------------|--------------| +| `cron` | Cron expression for inspection interval | `@every 1m` | ### Cron Expression Examples diff --git a/plugins/examples/minimal/README.md b/plugins/examples/minimal/README.md index d98979fe9..53f6597d6 100644 --- a/plugins/examples/minimal/README.md +++ b/plugins/examples/minimal/README.md @@ -8,12 +8,19 @@ This is a minimal example demonstrating how to create a Navidrome plugin using G 2. Build the plugin: ```bash go mod tidy - tinygo build -o minimal.wasm -target wasip1 -buildmode=c-shared ./main.go + tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared ./main.go + zip -j minimal.ndp manifest.json plugin.wasm + ``` + +Or using the examples Makefile: + ```bash + cd plugins/examples + make minimal.ndp ``` ## Installing -Copy `minimal.wasm` to your Navidrome plugins folder (default: `/plugins/`). +Copy `minimal.ndp` to your Navidrome plugins folder (default: `/plugins/`). ## Configuration @@ -29,7 +36,7 @@ Agents = "lastfm,spotify,minimal" ## What This Example Demonstrates -- Exporting the required `nd_manifest` function +- Plugin package structure (`.ndp` = zip with `manifest.json` + `plugin.wasm`) - Implementing `nd_get_artist_biography` as a MetadataAgent capability - Basic JSON input/output handling with the Extism PDK diff --git a/plugins/examples/nowplaying-py/README.md b/plugins/examples/nowplaying-py/README.md index bcabe8b8d..ac4ba26f7 100644 --- a/plugins/examples/nowplaying-py/README.md +++ b/plugins/examples/nowplaying-py/README.md @@ -23,18 +23,19 @@ A Python example plugin that demonstrates the **Scheduler** and **SubsonicAPI** From the `plugins/examples` directory: ```bash -make nowplaying-py.wasm +make nowplaying-py.ndp ``` Or directly: ```bash -extism-py plugin/__init__.py -o nowplaying-py.wasm +extism-py plugin/__init__.py -o plugin.wasm +zip -j nowplaying-py.ndp manifest.json plugin.wasm ``` ## Installation -1. Copy `nowplaying-py.wasm` to your Navidrome plugins folder +1. Copy `nowplaying-py.ndp` to your Navidrome plugins folder 2. Enable plugins in `navidrome.toml`: ```toml @@ -43,20 +44,14 @@ extism-py plugin/__init__.py -o nowplaying-py.wasm Folder = "/path/to/plugins" ``` -3. Configure the plugin in the UI (Settings → Plugins → nowplaying-py): - ```json - { - "cron": "*/1 * * * *", - "user": "admin" - } - ``` +3. Configure the plugin in the UI (Settings → Plugins → nowplaying-py) -### Configuration Options +## Configuration -| Key | Description | Default | -|--------|-------------------------------------|------------------------------| -| `cron` | Cron expression for check frequency | `*/1 * * * *` (every minute) | -| `user` | Navidrome user for SubsonicAPI | `admin` | +| Key | Description | Default | +|--------|-------------------------------------|---------------| +| `cron` | Cron expression for check frequency | `*/1 * * * *` | +| `user` | Navidrome user for SubsonicAPI | `admin` | ## Testing diff --git a/plugins/examples/webhook-rs/README.md b/plugins/examples/webhook-rs/README.md index af9b2ac5e..86afad9bb 100644 --- a/plugins/examples/webhook-rs/README.md +++ b/plugins/examples/webhook-rs/README.md @@ -20,7 +20,7 @@ A Navidrome plugin written in Rust that sends HTTP webhook notifications when tr From the `plugins/examples` directory: ```bash -make webhook-rs.wasm +make webhook-rs.ndp ``` Or build directly with cargo: @@ -28,28 +28,20 @@ Or build directly with cargo: ```bash cd webhook-rs cargo build --release -cp target/wasm32-unknown-unknown/release/webhook_rs.wasm ../webhook-rs.wasm +zip -j webhook-rs.ndp manifest.json target/wasm32-unknown-unknown/release/webhook_rs.wasm ``` ## Installation -Copy `webhook-rs.wasm` to your Navidrome plugins folder (configured via `Plugins.Folder` in your config). +Copy `webhook-rs.ndp` to your Navidrome plugins folder (configured via `Plugins.Folder` in your config). ## Configuration Configure in the Navidrome UI (Settings → Plugins → webhook-rs): -```json -{ - "urls": "https://example.com/webhook,https://another.example.com/notify" -} -``` - -### Configuration Options - -| Key | Description | Example | -|--------|--------------------------------------|---------------------------------------------------------| -| `urls` | Comma-separated list of webhook URLs | `"https://example.com/hook1,https://example.com/hook2"` | +| Key | Description | Example | +|--------|--------------------------------------|-----------------------------------------------------------| +| `urls` | Comma-separated list of webhook URLs | `https://example.com/hook1,https://example.com/hook2` | ## Webhook Request Format diff --git a/plugins/examples/wikimedia/README.md b/plugins/examples/wikimedia/README.md index 5785c099a..e4833cff6 100644 --- a/plugins/examples/wikimedia/README.md +++ b/plugins/examples/wikimedia/README.md @@ -22,16 +22,11 @@ xtp plugin init \ ## Building -### Using XTP CLI (recommended) - -```bash -xtp plugin build -``` - ### Using TinyGo ```bash -tinygo build -target wasip1 -buildmode=c-shared -o dist/plugin.wasm . +tinygo build -target wasip1 -buildmode=c-shared -o plugin.wasm . +zip -j wikimedia.ndp manifest.json plugin.wasm ``` ### Using the Makefile @@ -39,15 +34,22 @@ tinygo build -target wasip1 -buildmode=c-shared -o dist/plugin.wasm . From the `plugins/examples` directory: ```bash -make wikimedia.wasm +make wikimedia.ndp +``` + +### Using XTP CLI + +```bash +xtp plugin build +zip -j wikimedia.ndp manifest.json dist/plugin.wasm ``` ## Installation -Copy the `.wasm` file to your Navidrome plugins folder: +Copy the `.ndp` file to your Navidrome plugins folder: ```bash -cp dist/plugin.wasm /path/to/navidrome/plugins/wikimedia.wasm +cp wikimedia.ndp /path/to/navidrome/plugins/ ``` Then enable plugins in your `navidrome.toml`: @@ -73,23 +75,13 @@ brew install extism/tap/extism # macOS # or see https://extism.org/docs/install for other platforms ``` -Run these commands from the `plugins/examples` directory. - -### Test the manifest +Extract the wasm file from the package and test: ```bash -extism call wikimedia.wasm nd_manifest --wasi -``` +# Extract wasm from package +unzip -p wikimedia.ndp plugin.wasm > wikimedia.wasm -Expected output: -```json -{"name":"Wikimedia","author":"Navidrome","version":"1.0.0","description":"Fetches artist metadata from Wikidata, DBpedia and Wikipedia","website":"https://navidrome.org","permissions":{"http":{"reason":"Fetch metadata from Wikimedia APIs","allowedHosts":["query.wikidata.org","dbpedia.org","en.wikipedia.org"]}}} -``` - -### Test artist URL lookup - -```bash -# With MBID (The Beatles) +# Test artist URL lookup with MBID (The Beatles) extism call wikimedia.wasm nd_get_artist_url --wasi \ --input '{"id":"1","name":"The Beatles","mbid":"b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d"}' \ --allow-host "query.wikidata.org"