diff --git a/plugins/PLUGINS.md b/plugins/PLUGINS.md index 4156ed8..1568058 100644 --- a/plugins/PLUGINS.md +++ b/plugins/PLUGINS.md @@ -1,1552 +1,53 @@ # Overlord Plugins -This document explains how to build Overlord plugins. **Plugin 2.0** uses WebAssembly modules for agent-side code and permissioned host bridges. The legacy native plugin system still works, but native plugins are now considered v1. +This is the short entry point for plugin authors. The detailed docs are split by topic so server-only build extensions, WASM agent plugins, legacy native plugins, signing, and examples can evolve independently. -Plugin UI and server-side logic can be written in JavaScript or TypeScript. Agent-side Plugin 2.0 code is a single WASM module that can be built from C, Rust, Zig, TinyGo, AssemblyScript, or any other toolchain that can target WASI-style WebAssembly. Legacy v1 agent plugins can still be written as native shared libraries. +## Quick Start -> TL;DR: A Plugin 2.0 bundle is a zip with `config.json`, `.html`, `.css`, and either `.js` or TypeScript under `src/`. If it needs agent-side code, include one `.wasm` module. Upload it in the Plugins page or drop it in `Overlord-Server/plugins`. The server shows declared `needs` before a WASM plugin can be sent to a client. +A Plugin 2.0 bundle is a zip with a root `config.json`, browser assets, and optional server or agent code: -## Plugin 2.0: WASM runtime - -WASM plugins run inside the agent through a sandboxed runtime. They cannot access files directly; instead they declare broad file needs in `config.json`, and the agent exposes only those approved bridge operations. - -```json -{ - "name": "My WASM Plugin", - "version": "1.0.0", - "apiVersion": 2, - "runtime": "wasm", - "wasm": "my-plugin.wasm", - "needs": { - "files": [ - { "bucket": "pluginData", "access": ["read", "write", "list", "mkdir"], "reason": "Store plugin state" }, - { "bucket": "downloads", "access": ["list"], "reason": "Let the user pick downloaded files" } - ] - } -} -``` - -Supported file buckets are `home`, `desktop`, `documents`, `downloads`, `temp`, `appData`, `pluginData`, and `fullDisk`. Supported operations are `read`, `write`, `list`, `delete`, and `mkdir`. Approval is tracked by a stable hash of the normalized needs; changing needs requires approval again. - -Required exports: - -| Export | Signature | -|--------|-----------| -| `overlord_alloc` | `ptr(uint32 size)` | -| `overlord_free` | `void(ptr, uint32 size)` | -| `overlord_on_load` | `int32(ptr hostInfo, uint32 hostInfoLen)` | -| `overlord_on_event` | `int32(ptr event, uint32 eventLen, ptr payload, uint32 payloadLen)` | -| `overlord_on_unload` | `void()` | - -Host imports are available from both `env` and `overlord` modules: - -| Import | Behavior | -|--------|----------| -| `overlord_emit(event, eventLen, payload, payloadLen)` | Sends an event back through the normal plugin event channel. | -| `overlord_host_info(out, outLen)` | Writes HostInfo JSON into `out`; includes `clientId`, `os`, `arch`, and `version`. | -| `overlord_fs_stat(bucket, bucketLen, path, pathLen, out, outLen)` | Writes JSON file metadata into `out`. | -| `overlord_fs_list(bucket, bucketLen, path, pathLen, out, outLen)` | Writes a JSON directory listing into `out`. | -| `overlord_fs_read(bucket, bucketLen, path, pathLen, out, outLen)` | Writes file bytes into `out`, max 32 MB. | -| `overlord_fs_write(bucket, bucketLen, path, pathLen, data, dataLen)` | Writes bytes to a bucket-relative path. | -| `overlord_fs_delete(bucket, bucketLen, path, pathLen)` | Deletes a bucket-relative file or directory. | -| `overlord_fs_mkdir(bucket, bucketLen, path, pathLen)` | Creates a bucket-relative directory. | - -Bridge functions return non-negative byte counts or `0` on success. Negative return codes mean denied, invalid path/memory, not found, output buffer too small, file too large, or I/O failure. - -See `plugins/sample-wasm` for a C/WASI example, `plugins/sample-wasm-hostinfo` for a TinyGo example, and `plugins/sample-wasm-platform-note` for a Rust example. - -## TypeScript UI and server logic - -Plugin bundles can ship TypeScript source for browser UI logic and the optional server runtime. The server compiles these files during extraction, so the uploaded zip can stay clean and source-oriented. - -Default layout: - -``` -sample-ts-fullstack.zip - ├─ config.json - ├─ sample-ts-fullstack.html - ├─ sample-ts-fullstack.css - └─ src/ - ├─ ui.ts - ├─ server.ts - └─ shared.ts -``` - -`src/ui.ts` is compiled to `assets/.js`. `src/server.ts` is compiled to `server.js`. Both entrypoints can import other local files, so plugin authors can split code across multiple TypeScript modules instead of maintaining one large JavaScript file. This is only for browser UI and server-side plugin runtime logic; agent-side WASM is still supplied as a prebuilt `.wasm` file. - -You can override entrypoints in `config.json`: - -```json -{ - "apiVersion": 2, - "uiEntry": "src/browser/main.ts", - "serverEntry": "src/backend/index.ts" -} -``` - -Only files under `src/` are copied for compilation. Extraction does not run `npm install`, download packages, or execute arbitrary build scripts; use relative imports and APIs already available in the browser or the Overlord plugin server runtime. - -See `plugins/sample-ts-fullstack` for a multi-file TypeScript UI/backend example. - -## 1) How plugins are structured - -### Required bundle format - -A plugin bundle is a zip file named after the plugin ID: - -``` +```text .zip + config.json + .html + .css + .js optional when src/ui.ts exists + .wasm optional agent-side WASM module + server.js optional server-side runtime + src/ optional TypeScript source ``` -For a Plugin 2.0 source-oriented bundle, the root normally contains: +Upload the zip from the Plugins page, or place it under `Overlord-Server/plugins`. -``` -.zip - ├─ config.json - ├─ .html - ├─ .css - ├─ .js (optional when src/ui.ts exists) - ├─ .wasm (optional agent-side WASM module) - └─ src/ (optional TypeScript/JavaScript source) - ├─ ui.ts (compiled to assets/.js) - ├─ server.ts (compiled to server.js) - └─ shared.ts -``` - -For a legacy native v1 bundle, include platform-specific binaries named `--.` plus web assets. - -Plugin 2.0 example for plugin ID `sample-wasm`: - -``` -sample-wasm.zip - ├─ config.json - ├─ sample-wasm.html - ├─ sample-wasm.css - ├─ sample-wasm.wasm - └─ src/ - ├─ ui.ts - └─ server.ts -``` - -When the server extracts the zip: - -``` -Overlord-Server/plugins/sample-wasm/ - ├─ sample-wasm.wasm - ├─ server.js (compiled from src/server.ts when present) - ├─ manifest.json (auto-generated, merged with config.json) - ├─ src/ (copied source files) - ├─ data/ - │ └─ plugin.db (auto-created when server.js is present) - └─ assets/ - ├─ sample-wasm.html - ├─ sample-wasm.css - └─ sample-wasm.js (compiled from src/ui.ts or copied from root JS) -``` - -### Runtime selection - -`config.json` controls the runtime style: - -```json -{ - "apiVersion": 2, - "runtime": "wasm", - "wasm": "sample-wasm.wasm" -} -``` - -Use `runtime: "wasm"` when the plugin has an agent-side WASM module. Use `runtime: "server"` for UI/server-only Plugin 2.0 bundles. Legacy native bundles omit `apiVersion`/`runtime` or use `runtime: "native"`. - -### WASM architecture behavior - -WASM plugins are universal: the same `.wasm` file is sent to Windows, Linux, and macOS clients on any supported architecture. To branch by client platform, read HostInfo either from `overlord_on_load(hostInfo, hostInfoLen)` or by calling `overlord_host_info(out, outLen)`. - -Example HostInfo: - -```json -{ - "clientId": "abc123", - "os": "windows", - "arch": "amd64", - "version": "1.0.0" -} -``` - -### Legacy native architecture validation - -Legacy native v1 binaries are still matched to clients by OS and architecture. Binary filenames **must** follow the naming convention `--.`: - -| OS | Arch | Example filename | -|----------|---------|------------------------------| -| windows | amd64 | `sample-windows-amd64.dll` | -| windows | arm64 | `sample-windows-arm64.dll` | -| linux | amd64 | `sample-linux-amd64.so` | -| linux | arm64 | `sample-linux-arm64.so` | -| darwin | amd64 | `sample-darwin-amd64.dylib` | -| darwin | arm64 | `sample-darwin-arm64.dylib` | - -The server will **never** send an x64 native binary to an ARM client (or vice versa). If no native binary matches the client's platform, loading is skipped with an error. - -### Manifest fields - -The auto-generated manifest (merged with `config.json` if present): - -```json -{ - "id": "sample", - "name": "Sample Plugin", - "apiVersion": 2, - "runtime": "wasm", - "version": "1.0.0", - "description": "An example plugin", - "wasm": "sample.wasm", - "needs": { - "files": [ - { "bucket": "pluginData", "access": ["read", "write"], "reason": "Store plugin state" } - ] - }, - "binaries": { - "wasm32-wasi": "sample.wasm" - }, - "entry": "sample.html", - "assets": { - "html": "sample.html", - "css": "sample.css", - "js": "sample.js" - }, - "navbar": { - "label": "Sample", - "icon": "fa-cube" - } -} -``` - -The `navbar`, `needs`, `wasm`, and TypeScript entrypoint fields are present only when declared or inferred from the bundle. - -### Global Nav Bar Plugins (`config.json`) - -Include an optional `config.json` in the root of your plugin zip to register the plugin in the global navigation bar. This is suited for plugins that operate across all clients (e.g. a global clipboard manager) rather than requiring a specific `?clientId=` to be selected first. - -```json -{ - "name": "My Plugin", - "version": "1.0.0", - "description": "A global plugin", - "navbar": { - "label": "My Plugin", - "icon": "fa-cube" - } -} -``` - -**`navbar.icon`** accepts any [Font Awesome 6 solid icon class](https://fontawesome.com/icons?s=solid) without the `fa-solid` prefix, e.g. `fa-clipboard`, `fa-network-wired`, `fa-key`. - -When the server extracts the bundle: -- `config.json` fields are merged into the auto-generated `manifest.json` -- If the plugin is also `enabled`, it will appear in the **"Plugin Apps"** group in the sidebar/topbar -- Clicking the nav entry opens the plugin at `/plugins/` — **without** a `?clientId=` parameter, suitable for global operation - -> **Important:** `config.json` must be in the **root** of the zip (not inside a subfolder). The build scripts automatically include it if it exists at the plugin source root. The manifest is re-generated on every extraction (triggered when the zip is newer than `manifest.json`), so keep `config.json` in the zip to avoid losing the navbar registration on re-upload. - -## 2) Legacy native plugin ABI - -This section applies only to legacy v1 native agent plugins. Plugin 2.0 WASM modules use the `overlord_*` ABI described above. - -Every native plugin must export **C-callable functions** with specific signatures. The ABI differs slightly between Windows and Unix. - -### Required exports - -| Export | Signature (Windows) | Signature (Linux / macOS) | Required | -|--------------------|-----------------------------------------------------------------------|-----------------------------------------------------------------------------------|----------| -| `PluginOnLoad` | `int PluginOnLoad(char* hostInfo, int hostInfoLen, uint64 callback)` | `int PluginOnLoad(char* hostInfo, int hostInfoLen, uintptr callback, uintptr ctx)` | Yes | -| `PluginOnEvent` | `int PluginOnEvent(char* event, int eventLen, char* payload, int payloadLen)` | same | Yes | -| `PluginOnUnload` | `void PluginOnUnload()` | same | Yes | -| `PluginSetCallback`| `void PluginSetCallback(uint64 callback)` | — | Windows only | -| `PluginGetRuntime` | `const char* PluginGetRuntime()` | same | Recommended | - -### Host callback - -The host passes a callback function pointer during `PluginOnLoad`. The plugin uses it to send events back to the agent. - -**Windows** — The callback is a `__stdcall` function: - -```c -void __stdcall callback(const char *event, uintptr_t eventLen, - const char *payload, uintptr_t payloadLen); -``` - -**Linux / macOS** — The callback is a standard C function with an opaque context pointer: - -```c -void callback(uintptr_t ctx, - const char *event, int eventLen, - const char *payload, int payloadLen); -``` - -The `ctx` value received in `PluginOnLoad` must be passed back as the first argument to every callback invocation. - -### PluginGetRuntime (runtime detection) - -If a plugin exports `PluginGetRuntime`, the host calls it after loading. It must return a pointer to a static, null-terminated string identifying the runtime: - -| Return value | Meaning | -|-------------|---------------------------------------| -| `"c"` | Pure C plugin — fully unloadable | -| `"cpp"` | C++ plugin — fully unloadable | -| `"rust"` | Rust plugin — fully unloadable | -| *(absent)* | Defaults to `"go"` — **not** freed | - -This matters because **Go plugins cannot be fully unloaded** due to [golang/go#11100](https://github.com/golang/go/issues/11100) — Go's runtime spawns threads that cannot be stopped, so calling `FreeLibrary`/`dlclose` on a Go shared library causes a crash. The host uses this information to skip freeing Go plugins while properly reclaiming memory for C/C++/Rust plugins. - -The runtime is logged on load and unload: -``` -[plugins] loaded plugin "sample-c" (runtime=c, freeable=true) -[plugins] unloaded plugin "sample-c" — memory freed -[plugins] unloaded plugin "sample-go" — memory leaked (go runtime) -``` - -### HostInfo JSON - -The `hostInfo` buffer passed to `PluginOnLoad` is JSON: - -```json -{ - "clientId": "abc123", - "os": "windows", - "arch": "amd64", - "version": "1.0.0" -} -``` - -### Loading mechanism - -- **Windows** — DLLs are loaded **entirely in memory** via a custom PE loader. No files are written to disk. This is why Rust's `std::sync::Mutex` (backed by `SRWLOCK`) may not initialize correctly — see the Rust section below. -- **Linux** — Shared libraries are loaded in memory via `memfd_create` + `dlopen` on `/proc/self/fd/`. No files touch disk. -- **macOS** — Shared libraries are written to a temp file, `dlopen`'d, then the temp file is deleted. - -## 3) Sample plugins - -Current Plugin 2.0 samples: - -| Sample | Purpose | -|--------|---------| -| `sample-wasm` | C/WASI file bridge sample using `pluginData`, `downloads`, and `overlord_host_info`. | -| `sample-wasm-hostinfo` | TinyGo sample that queries HostInfo from a universal WASM module. | -| `sample-wasm-platform-note` | Rust sample that branches by `os`/`arch`, writes a platform-specific note, and probes Desktop list/read access. | -| `sample-ts-fullstack` | TypeScript UI plus TypeScript server runtime with shared local modules. | - -Legacy native samples remain available: - -### Go (`sample-go/`) - -Go plugins use `-buildmode=c-shared` with platform-specific export wrappers. - -**Project structure:** -``` -sample-go/native/ - ├─ main.go (shared core logic) - ├─ exports_unix.go (exports for Linux/macOS) - ├─ exports_windows.go (exports for Windows) - └─ go.mod -``` - -**Linux / macOS exports:** -```go -//export PluginOnLoad -func PluginOnLoad(hostInfo *C.char, hostInfoLen C.int, cb C.uintptr_t, ctx C.uintptr_t) C.int - -//export PluginOnEvent -func PluginOnEvent(event *C.char, eventLen C.int, payload *C.char, payloadLen C.int) C.int - -//export PluginOnUnload -func PluginOnUnload() -``` - -**Windows exports:** -```go -//export PluginOnLoad -func PluginOnLoad(hostInfo *C.char, hostInfoLen C.int, callbackPtr C.ulonglong) C.int - -//export PluginOnEvent -func PluginOnEvent(event *C.char, eventLen C.int, payload *C.char, payloadLen C.int) C.int - -//export PluginOnUnload -func PluginOnUnload() - -//export PluginSetCallback -func PluginSetCallback(callbackPtr C.ulonglong) -``` - -**Build:** -```bash -CGO_ENABLED=1 go build -buildmode=c-shared -o sample-windows-amd64.dll ./native -``` - -> **Note:** Go plugins do **not** export `PluginGetRuntime` and default to the `"go"` runtime. They are intentionally never freed on unload to prevent crashes from orphaned Go runtime threads. - ---- - -### C (`sample-c/`) - -Pure C plugins are the simplest — no runtime, no GC, fully unloadable. - -**Project structure:** -``` -sample-c/ - ├─ native/plugin.c - ├─ sample-c.html - ├─ sample-c.css - └─ sample-c.js -``` - -**Key points:** -- Uses `__declspec(dllexport)` on Windows, `__attribute__((visibility("default")))` on Unix -- Has a `DllMain` entry point on Windows (receives `DLL_PROCESS_ATTACH` / `DLL_PROCESS_DETACH`) -- Exports `PluginGetRuntime` → returns `"c"` -- No heap allocations required — can use stack buffers for all responses - -**Minimal example (Windows callback):** -```c -#ifdef _WIN32 -typedef void (__stdcall *host_callback_t)( - const char *event, uintptr_t eventLen, - const char *payload, uintptr_t payloadLen); -#else -typedef void (*host_callback_t)( - uintptr_t ctx, - const char *event, int eventLen, - const char *payload, int payloadLen); -#endif - -static host_callback_t g_callback; - -EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen, ...) { - // Parse hostInfo JSON, store callback, return 0 on success -} - -EXPORT int PluginOnEvent(const char *event, int eventLen, - const char *payload, int payloadLen) { - // Handle events, call g_callback() to respond - return 0; -} - -EXPORT void PluginOnUnload(void) { - // Cleanup -} - -EXPORT const char *PluginGetRuntime(void) { return "c"; } -``` - -**Build:** -```bash -# MSVC -cl /LD /O2 plugin.c /Fe:sample-c-windows-amd64.dll - -# MinGW / gcc -gcc -shared -O2 -o sample-c-windows-amd64.dll plugin.c - -# Linux -gcc -shared -fPIC -O2 -o sample-c-linux-amd64.so plugin.c -``` - ---- - -### C++ (`sample-cpp/`) - -C++ plugins can use the full standard library (STL containers, `std::mutex`, `std::string`, etc.) while still being fully unloadable. - -**Project structure:** -``` -sample-cpp/ - ├─ native/plugin.cpp - ├─ sample-cpp.html - ├─ sample-cpp.css - └─ sample-cpp.js -``` - -**Key points:** -- All exports wrapped in `extern "C"` to prevent name mangling -- Can freely use `std::string`, `std::unordered_map`, `std::mutex`, etc. -- Exports `PluginGetRuntime` → returns `"cpp"` - -**Build:** -```bash -# MSVC -cl /LD /EHsc /O2 plugin.cpp /Fe:sample-cpp-windows-amd64.dll - -# MinGW / g++ -g++ -shared -O2 -o sample-cpp-windows-amd64.dll plugin.cpp - -# Linux -g++ -shared -fPIC -O2 -o sample-cpp-linux-amd64.so plugin.cpp -``` - ---- - -### Rust (`sample-rust/`) - -Rust plugins compile as `cdylib` crates. Exports use `#[no_mangle]` and `extern "C"`. - -**Project structure:** -``` -sample-rust/ - ├─ native/ - │ ├─ Cargo.toml - │ └─ src/lib.rs - ├─ sample-rust.html - ├─ sample-rust.css - └─ sample-rust.js -``` - -**Key points:** -- `crate-type = ["cdylib"]` in Cargo.toml -- Uses `extern "stdcall"` for the callback type on Windows, `extern "C"` on Unix -- Exports `PluginGetRuntime` → returns `"rust"` - -**⚠️ Important: avoid `std::sync::Mutex` on Windows.** Because the DLL is loaded via an in-memory PE loader (not `LoadLibrary`), Rust's standard `Mutex` (backed by Windows `SRWLOCK`) may not initialize correctly, causing an access violation. Use C-style `static mut` globals instead — this is safe because the Go host serializes all calls through its own mutex. - -**Cargo.toml:** -```toml -[lib] -crate-type = ["cdylib"] - -[profile.release] -opt-level = "z" -lto = true -strip = true -``` - -**Build:** -```bash -# Native -cargo build --release - -# Cross-compile -cargo build --release --target=x86_64-pc-windows-msvc -cargo build --release --target=aarch64-unknown-linux-gnu -``` - -## 4) Legacy native build scripts & cross-compilation - -This section applies to legacy native v1 plugins. Plugin 2.0 WASM modules are universal and do not need one binary per OS/architecture, though you still need a WASM toolchain such as WASI clang, Rust `wasm32-wasip1`, Zig, TinyGo, or AssemblyScript. - -Each legacy native sample keeps its `.bat` (Windows) and `.sh` (Unix) build scripts in the plugin's own directory. All scripts support the `BUILD_TARGETS` environment variable to compile for multiple platforms in one invocation. - -### Available build scripts - -| Language | Windows | Unix | -|----------|--------------------------|-------------------------| -| Go | `sample-go/build.bat` | `sample-go/build.sh` | -| C | `sample-c/build.bat` | `sample-c/build.sh` | -| C++ | `sample-cpp/build.bat` | `sample-cpp/build.sh` | -| Rust | `sample-rust/build.bat` | `sample-rust/build.sh` | - -### Usage - -All scripts default to building for the host platform only: - -```bash -# Build for current platform -cd sample-c -./build.sh - -# Build for multiple targets -BUILD_TARGETS="linux-amd64 linux-arm64 windows-amd64" ./build.sh - -# Custom plugin directory -./build.sh /path/to/my-plugin -``` - -```bat -REM Build for current platform -sample-c\build.bat - -REM Build for multiple targets -set BUILD_TARGETS=windows-amd64 windows-arm64 -sample-c\build.bat - -REM Custom plugin directory -sample-c\build.bat C:\path\to\my-plugin -``` - -### Cross-compilation - -**Go** — Cross-compilation is built-in via `GOOS` / `GOARCH` env vars. No extra toolchains needed (unless CGo calls platform-specific APIs). - -**Rust** — Cross-compilation uses `--target=` with Rust target triples. Install targets with `rustup target add `. The build scripts map `os-arch` pairs to triples automatically: - -| Target | Rust triple (`.bat` / MSVC) | Rust triple (`.sh` / GNU) | -|-----------------|-------------------------------------|---------------------------------------| -| windows-amd64 | `x86_64-pc-windows-msvc` | `x86_64-pc-windows-gnu` | -| windows-arm64 | `aarch64-pc-windows-msvc` | `aarch64-pc-windows-gnullvm` | -| linux-amd64 | `x86_64-unknown-linux-gnu` | `x86_64-unknown-linux-gnu` | -| linux-arm64 | `aarch64-unknown-linux-gnu` | `aarch64-unknown-linux-gnu` | -| darwin-amd64 | `x86_64-apple-darwin` | `x86_64-apple-darwin` | -| darwin-arm64 | `aarch64-apple-darwin` | `aarch64-apple-darwin` | - -**C / C++** — Cross-compilation requires the appropriate cross-compiler toolchain to be installed: - -| Target | C compiler | C++ compiler | -|-----------------|-------------------------------|--------------------------------| -| linux-amd64 | `x86_64-linux-gnu-gcc` | `x86_64-linux-gnu-g++` | -| linux-arm64 | `aarch64-linux-gnu-gcc` | `aarch64-linux-gnu-g++` | -| linux-arm | `arm-linux-gnueabihf-gcc` | `arm-linux-gnueabihf-g++` | -| windows-amd64 | `x86_64-w64-mingw32-gcc` | `x86_64-w64-mingw32-g++` | -| windows-arm64 | `aarch64-w64-mingw32-gcc` | `aarch64-w64-mingw32-g++` | - -On Windows `.bat` scripts, MSVC (`cl`) is tried first with the appropriate `/link /machine:` flag, falling back to MinGW if `cl` fails. - -On Unix `.sh` scripts, override the compiler with `CC=` (C) or `CXX=` (C++): - -```bash -CC=zig-cc BUILD_TARGETS="linux-arm64" ./sample-c/build.sh -CXX=zig-c++ BUILD_TARGETS="linux-arm64" ./sample-cpp/build.sh -``` - -### Installing cross-compiler toolchains - -**Debian/Ubuntu:** -```bash -# ARM64 Linux cross-compilers -sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - -# Windows cross-compilers (MinGW) -sudo apt install gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 - -# Rust targets -rustup target add aarch64-unknown-linux-gnu x86_64-pc-windows-gnu -``` - -**macOS (Homebrew):** -```bash -# Linux cross-compilers via musl-cross -brew install FiloSottile/musl-cross/musl-cross - -# Rust targets -rustup target add aarch64-apple-darwin x86_64-apple-darwin -``` - -### WASM build examples - -Plugin 2.0 samples keep their Windows builders in their own plugin directories: - -| Script | Purpose | -|--------|---------| -| `sample-wasm/build.bat` | Builds and packages `sample-wasm`. | -| `sample-wasm-hostinfo/build.bat` | Builds and packages `sample-wasm-hostinfo`. | -| `sample-wasm-platform-note/build.bat` | Builds and packages `sample-wasm-platform-note`. | -| `sample-ts-fullstack/build.bat` | Packages the TypeScript UI/server sample; the server compiles TypeScript during extraction. | - -Usage: - -```bat -REM Per-sample Plugin 2.0 build -sample-ts-fullstack\build.bat -sample-wasm\build.bat -``` - -The sample WASM plugins intentionally use different source languages: - -| Sample | Source language | Toolchain | -|--------|-----------------|-----------| -| `sample-wasm` | C | WASI-capable `clang` | -| `sample-wasm-hostinfo` | Go | TinyGo 0.41.0+ with `-target=wasi` | -| `sample-wasm-platform-note` | Rust | Rust target `wasm32-wasip1` | - -A minimal manual C/WASI build for `sample-wasm` looks like: - -```bash -clang --target=wasm32-wasi -O2 \ - -Wl,--no-entry \ - -Wl,--export=overlord_alloc \ - -Wl,--export=overlord_free \ - -Wl,--export=overlord_on_load \ - -Wl,--export=overlord_on_event \ - -Wl,--export=overlord_on_unload \ - -o sample-wasm.wasm native/sample_wasm.c -``` - -The generated `.wasm` goes at the root of the plugin zip and is referenced by `config.json`. - -For the TinyGo sample, install TinyGo 0.41.0 or newer and run `sample-wasm-hostinfo\build.bat`. For the Rust sample, install the target with `rustup target add wasm32-wasip1`, then run `sample-wasm-platform-note\build.bat`. - -## 5) Install & open a plugin - -### Install / upload - -- Use the UI at `/plugins` to upload the zip -- Or drop `.zip` into `Overlord-Server/plugins` and restart - -### Open the UI - -Plugin UI is embedded directly in the main page at: - -``` -/plugins/?clientId= -``` - -For **global nav bar plugins** (those registered via `config.json`), the UI opens without a client ID: - -``` -/plugins/ -``` - -The server reads your `.html`, extracts the body content, and renders it inside the standard Overlord layout. Your CSS and generated/copied JS are loaded from `/plugins//assets/`. - -To get the `clientId` in your JS (for per-client plugins): - -```js -const clientId = new URLSearchParams(window.location.search).get("clientId"); -``` - -For global plugins, `clientId` will be `null`. Use `/api/clients?status=online&pageSize=1000` to enumerate connected clients and broadcast events to all of them: - -```js -const res = await fetch("/api/clients?status=online&pageSize=1000"); -const { items } = await res.json(); -const onlineIds = items.filter(c => c.online).map(c => c.id); -``` - -## 6) Runtime: how events flow - -Overlord plugins have **two parts**: - -1. **UI (HTML/CSS/JS)** — Runs in the browser, calls server APIs. -2. **Optional agent module** — Plugin 2.0 uses WASM; legacy v1 uses native shared libraries. - -Plugins can also include optional **server-side logic** through `server.js` or compiled `src/server.ts`. That code runs in the server plugin worker and exposes RPC/broadcast APIs to the UI. - -### UI → agent (plugin event) - -From your UI JS: - -``` -POST /api/clients//plugins//event -{ - "event": "ui_message", - "payload": { "message": "hello" } -} -``` - -If the plugin is not loaded yet, the server will load it on the client, queue the event, and deliver it once ready. - -### Agent → plugin - -For WASM plugins, the agent calls `overlord_on_event(eventPtr, eventLen, payloadPtr, payloadLen)`. For legacy native plugins, the agent calls `PluginOnEvent(event, payload)`. In both cases the payload is JSON-encoded data from the UI/server. - -### Plugin → agent/server events - -WASM plugins send events with `overlord_emit(event, eventLen, payload, payloadLen)`. Legacy native plugins use the callback received during `PluginOnLoad`. - -**Go:** -```go -send("echo", []byte(`{"message":"hello back"}`)) -``` - -**C / C++:** -```c -g_callback("echo", 4, "{\"message\":\"hello back\"}", 24); -``` - -**Rust:** -```rust -let event = b"echo"; -let payload = b"{\"message\":\"hello back\"}"; -(g_callback)(event.as_ptr(), event.len(), payload.as_ptr(), payload.len()); -``` +## Read The Focused Docs -On Windows, the callback uses `__stdcall` calling convention. On Unix, it's a standard C call with the context pointer as the first argument. +| Topic | Read this | +|-------|-----------| +| Bundle layout, `config.json`, TypeScript UI/server entries | [docs/bundle-format.md](docs/bundle-format.md) | +| Server-only plugins, plugin data, RPC/SSE, build hooks, build settings/buttons | [docs/server-side-plugins.md](docs/server-side-plugins.md) | +| WASM Plugin 2.0 agent modules and permissioned file bridges | [docs/wasm-plugins.md](docs/wasm-plugins.md) | +| Install/upload flow, Plugin Manager behavior, auto-load rules | [docs/install-and-manage.md](docs/install-and-manage.md) | +| Sample plugin directory guide | [docs/samples.md](docs/samples.md) | +| Legacy native plugins: OS APIs, native ABI, build scripts, Linux static-agent shim | [docs/legacy-native-plugins.md](docs/legacy-native-plugins.md) | +| Plugin signing and trusted keys | [docs/signing.md](docs/signing.md) | -### Plugin lifecycle events +## Runtime Choices -The agent sends these events to the server: +Use `runtime: "server"` for server-side extensions. These can add server UI, store files, expose RPCs, subscribe to plugin events, and hook the build pipeline. Server-only plugins are shown as server extensions in the Plugin Manager, are not sent to clients, and do not use auto-load. -- `loaded` on successful load -- `unloaded` when unloaded -- `error` if load or runtime fails - -## 7) What can plugins do? - -WASM Plugin 2.0 agent modules are sandboxed. They can: - -- Receive UI/server events. -- Send events back through `overlord_emit`. -- Read HostInfo (`clientId`, `os`, `arch`, `version`). -- Use declared file bridges after the user approves the plugin's `needs`. - -WASM modules cannot directly access arbitrary files, processes, network sockets, or OS APIs unless Overlord exposes an explicit host bridge. - -Legacy native v1 plugins run as native code and can: - -- Call any system API (file I/O, network, processes, etc.) -- Use any library available to their language -- Spawn threads / goroutines -- Access hardware -- Do anything a normal native program can do - -Legacy native plugins have the same capabilities as the agent itself. - -| Language | Can be fully unloaded? | Runtime overhead | -|----------|----------------------|------------------| -| C | Yes | None | -| C++ | Yes | Minimal (STL) | -| Rust | Yes | Minimal | -| Go | **No** ([#11100](https://github.com/golang/go/issues/11100)) | Go runtime + GC | - -### UI embedding - -Plugin UI pages are embedded **directly** into the main Overlord page — no iframe, no sandbox, no bridge. The server reads your plugin's HTML, extracts the `` content and any ``/`