pluginengine01 / README.md
krystv's picture
Upload 107 files
3374e90 verified
# BEX Engine v6 — WASM Plugin Engine
A **production-grade WASM/Wasmtime Component Model** plugin engine built in Rust. BEX enables sandboxed, deterministic plugin execution using WebAssembly components with WIT interface definitions. Designed for media streaming, metadata, and content provider applications with a **Pure C ABI** integration layer — no cxx dependency.
## What's New in v6
This version incorporates all fixes from the QuickJS Integration Plan v3 review:
### Critical Bug Fixes
- **`eval-js` now accepts `input` parameter** — user data is safely injected as a global variable instead of being concatenated into JS source code, eliminating code injection vulnerabilities
- **`call-js-fn` now accepts `fn-source` parameter** — functions are registered and auto-re-registered when source changes, eliminating the broken `track_functions` text parser
- **`TextEncoder`/`TextDecoder` are now spec-correct UTF-8** — properly handles CJK characters, emoji, and all Unicode code points
- **`crypto.getRandomValues` uses Rust-backed CSPRNG**`rand::thread_rng()` instead of `Math.random()`
- **`crypto.subtle` is now fully implemented** — SHA-1/256/384/512, AES-CBC encrypt/decrypt, HMAC-SHA256/512, PBKDF2, all in pure JS with immediate-resolved Promises
- **`args_json` is passed as a string, not eval'd** — eliminates JS injection attack vector
- **Pool dispatch is non-blocking** — uses `try_send` instead of blocking `send`, returns `PoolBusy` instead of hanging Wasmtime threads
- **Pool shutdown uses SeqCst ordering** — fixes race condition on ARM/Apple Silicon
### Missing Features Now Implemented
- **`console.log` routes to Rust tracing** — no longer silently dropped
- **`setTimeout`/`setInterval` call callbacks synchronously** — no longer silently skipped
- **`clear-js-fn` WIT function** — allows unregistering JS functions when cipher rotates
- **`JsPoolConfig` wired into `EngineConfig`** — JS pool settings are configurable from engine config
### Design Improvements
- **Idle context eviction throttled to 30-second intervals** — reduces overhead from checking on every loop iteration
- **`apply_fn` helper removed** — `func.call((args_json,))` used directly
- **`max_stack_bytes` configurable via `JsPoolConfig`** — default 512KB
- **All compiler warnings fixed** — unused imports, dead code, unused variables
- **`atob`/`btoa` are Rust-backed** — correct Latin-1 handling via base64 crate
- **Additional polyfills**`URL`, `URLSearchParams`, `performance.now()`, `structuredClone`, `navigator`, `location`, `queueMicrotask`
## Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ C++ Application │
│ (via Pure C ABI) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ BexResultCallback (function pointer) │ │
│ │ bex_submit_*() → request_id │ │
│ │ callback(user_data, req_id, success, payload, len) │ │
│ │ bex_cancel_request(request_id) │ │
│ └──────────────────────┬───────────────────────────────┘ │
├─────────────────────────┼───────────────────────────────────┤
│ BexRuntime (async callback-driven) │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ Scheduler │ │ Cancellation│ │ Tokio Runtime │ │
│ │ (3 lanes) │ │ Tokens │ │ (async tasks) │ │
│ └──────┬──────┘ └──────┬───────┘ └─────────┬─────────┘ │
├─────────┼────────────────┼─────────────────────┼────────────┤
│ │ BEX Engine (Wasmtime) │ │
│ ┌──────▼──────┐ ┌────▼─────┐ ┌─────────────▼──────────┐ │
│ │ Wasmtime │ │ Host │ │ Plugin Registry │ │
│ │ Component │ │ APIs │ │ (circuit breaker) │ │
│ │ Model │ │ │ │ │ │
│ └──────┬──────┘ └────┬─────┘ └────────────────────────┘ │
│ │ │ │
│ ┌──────▼──────┐ ┌────▼─────┐ ┌─────────────┐ │
│ │ WASM │ │ HTTP │ │ Redb DB │ │
│ │ Components │ │ (reqwest)│ │ (storage) │ │
│ └──────────────┘ └──────────┘ └─────────────┘ │
│ │
│ FlatBuffers (wire format) │ JSON (CLI/debug) │
└──────────────────────────────┴────────────────────────────────┘
```
### Core Design Principles
1. **WASM-Only**: All plugins run as WebAssembly components via Wasmtime — no native plugins, no dual-mode engine
2. **Component Model**: Uses Wasmtime's Component Model with WIT interface definitions for type-safe host-guest communication
3. **Callback-Driven**: C++ backend submits requests via `bex_submit_*()`, receives results via a C function pointer callback (`BexResultCallback`) invoked from a background Tokio thread. No polling, no event queue, no drain pattern.
4. **Self-Describing IDs**: The engine treats IDs as opaque strings — it does not know or care what they mean. Each plugin defines its own ID format and parses IDs internally. For example, `get_servers` takes a single `id` parameter; the plugin parses `slug$ep=1$sub=1$dub=0` because it knows its own encoding scheme. There is no `episode_id` parameter anywhere in the engine.
5. **Sandboxed Execution**: Fuel-based metering, stack limits, epoch-based timeouts, and capability-based host APIs
6. **Lane-Based Scheduling**: Three concurrency lanes — Control (1), User (4), Background (2) — with semaphores, plus global WASM (4) and HTTP (8) permit limits
7. **Cancellation**: Each request gets a `CancellationToken`; cancel via `bex_cancel_request()`
8. **Pure C ABI**: The Rust engine exports `extern "C"` functions matching `bex_engine.h`. No cxx, no bridge codegen, no special build steps. Link the Rust static/shared library natively.
## Workspace Structure
```
bex-engine/
├── crates/
│ ├── bex-types/ # Shared types (Manifest, Capabilities, BexError, PluginInfo, etc.)
│ ├── bex-pkg/ # BEX package format (pack/unpack/verify with zstd+CRC32+SHA256)
│ ├── bex-db/ # Redb-backed storage (KV, secrets, plugins, WASM blobs)
│ ├── bex-wire/ # FlatBuffer wire-format types + builders
│ ├── bex-core/ # Core engine (Wasmtime, host APIs, linker, compile cache)
│ ├── bex-runtime/ # Async runtime (scheduler, cancellation, Pure C FFI via ffi.rs)
│ └── bex-cli/ # Rust CLI tool (clap-based)
├── plugins/
│ ├── bex-gogoanime/ # GogoAnime/Anitaku streaming plugin
│ ├── bex-kaianime/ # KaiAnime streaming plugin
│ ├── bex-hianime/ # HiAnime streaming plugin
│ ├── bex-imdb/ # IMDb metadata plugin
│ └── bex-kisskh/ # KissKH streaming plugin
├── cpp-cli/
│ ├── bex_engine.h # Pure C ABI header (the FFI boundary)
│ ├── bexcli.cpp # C++ CLI tool (uses promise/future + C callbacks)
│ ├── CMakeLists.txt # CMake build configuration (links libbex_runtime natively)
│ └── wire_gen/ # Generated FlatBuffer C++ headers
├── wit/
│ └── plugin.wit # Master WIT interface definitions
├── dist/ # Built WASM components and manifests
├── build-plugins.sh # Build, convert, and pack all plugins
└── Cargo.toml # Workspace root
```
## Pure C ABI
The Rust engine exposes a **Pure C ABI** through `bex_engine.h`. No cxx, no code generation, no bridge crate. The Rust library compiles as both `cdylib` and `staticlib`, and CMake links it natively.
### How It Works
1. C++ calls `bex_submit_search(engine, plugin_id, query, callback, user_data)`
2. Rust spawns a Tokio task that does the work
3. On completion, Rust invokes `callback(user_data, request_id, success, payload, len)` from the Tokio background thread
4. C++ receives the result in the callback and can parse/copy the payload before the callback returns
### C++ Integration Example
```cpp
#include "bex_engine.h"
// 1. Define a callback handler
extern "C" void on_result(void* user_data, uint64_t req_id,
bool success, const uint8_t* payload, size_t len) {
auto* promise = static_cast<std::promise<std::string>*>(user_data);
if (success) {
promise->set_value(std::string(reinterpret_cast<const char*>(payload), len));
} else {
promise->set_exception(std::make_exception_ptr(
std::runtime_error(std::string(reinterpret_cast<const char*>(payload), len))));
}
}
// 2. Create engine
BexEngine* engine = bex_engine_new("/path/to/data");
// 3. Submit async requests — returns request_id immediately
std::promise<std::string> promise;
uint64_t req1 = bex_submit_home(engine, "bex.gogoanime", on_result, &promise);
uint64_t req2 = bex_submit_search(engine, "bex.gogoanime", "one piece", on_result, &promise);
uint64_t req3 = bex_submit_info(engine, "bex.gogoanime", "one-piece", on_result, &promise);
uint64_t req4 = bex_submit_servers(engine, "bex.gogoanime", "one-piece$ep=1$sub=1$dub=0",
on_result, &promise);
// 4. Wait for results (or use the callback in your event loop)
std::string result = promise.get_future().get();
// 5. Cancel a request
bex_cancel_request(engine, req2);
// 6. Plugin management (synchronous)
bex_engine_install(engine, "/path/to/plugin.bex");
bex_engine_uninstall(engine, "bex.gogoanime");
BexPluginInfoList plugins = bex_engine_list_plugins(engine);
bex_plugin_info_list_free(plugins);
// 7. API key management (synchronous)
bex_engine_secret_set(engine, "bex.imdb", "api-key", "your-key");
// 8. Shutdown
bex_engine_free(engine);
```
### Full C ABI Function Reference
#### Lifecycle
| Function | Description |
|----------|-------------|
| `bex_engine_new(data_dir)` | Create engine → `BexEngine*` |
| `bex_engine_free(engine)` | Graceful shutdown and free |
#### Plugin Management (synchronous)
| Function | Description |
|----------|-------------|
| `bex_engine_install(engine, path)` | Install .bex plugin package → `int` |
| `bex_engine_uninstall(engine, id)` | Uninstall plugin by ID → `int` |
| `bex_engine_list_plugins(engine)` | List installed plugins → `BexPluginInfoList` |
| `bex_engine_plugin_info(engine, id, out)` | Get detailed plugin info → `int` |
| `bex_engine_enable(engine, id)` | Enable plugin → `int` |
| `bex_engine_disable(engine, id)` | Disable plugin → `int` |
| `bex_plugin_info_list_free(list)` | Free plugin list |
| `bex_plugin_info_free(info)` | Free plugin info struct |
#### Secret / API Key Management (synchronous)
| Function | Description |
|----------|-------------|
| `bex_engine_secret_set(engine, plugin_id, key, value)` | Store a secret → `int` |
| `bex_engine_secret_get(engine, plugin_id, key, out_buf, out_buf_len)` | Retrieve a secret value → `int` |
| `bex_engine_secret_delete(engine, plugin_id, key)` | Delete a secret → `int` |
| `bex_engine_secret_keys(engine, plugin_id)` | List key names (comma-separated) → `char*` |
| `bex_string_free(s)` | Free a string returned by the engine |
#### Async Operations (callback-driven)
| Function | Description |
|----------|-------------|
| `bex_submit_home(engine, plugin_id, callback, user_data)` | Submit home request → `uint64_t` request_id |
| `bex_submit_search(engine, plugin_id, query, callback, user_data)` | Submit search request → `uint64_t` request_id |
| `bex_submit_info(engine, plugin_id, media_id, callback, user_data)` | Submit info request → `uint64_t` request_id |
| `bex_submit_servers(engine, plugin_id, id, callback, user_data)` | Submit servers request → `uint64_t` request_id |
| `bex_submit_stream(engine, plugin_id, server_json, callback, user_data)` | Submit stream resolve → `uint64_t` request_id |
#### Cancellation & Stats
| Function | Description |
|----------|-------------|
| `bex_cancel_request(engine, request_id)` | Cancel a pending request → `bool` |
| `bex_engine_stats(engine)` | Get engine stats (JSON) → `char*` |
| `bex_engine_last_error(engine)` | Get last error message → `char*` |
## Quick Start
### Prerequisites
- Rust toolchain (stable)
- `wasm-tools` CLI: `cargo install wasm-tools-cli`
- C++17 compiler (for C++ CLI / integration)
- CMake 3.16+ (for C++ CLI / integration)
### Build the Engine
```bash
# Build the Rust engine and CLI
cargo build --release
# Build and pack all plugins (compile → component convert → pack)
bash build-plugins.sh
# Build the C++ CLI with CMake
cd cpp-cli && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
```
### Build a Single Plugin (Manual)
Plugins are built in three steps: compile to WASM, convert to a component, then pack.
```bash
# Step 1: Compile to wasm32-wasip1
cargo build -p bex-gogoanime --target wasm32-wasip1 --release
# Step 2: Convert to a WASM Component (requires wasm-tools + WASI adapter)
wasm-tools component new \
target/wasm32-wasip1/release/bex_gogoanime.wasm \
-o target/components/bex-gogoanime.component.wasm \
--adapt ~/.cargo/registry/src/index.crates.io-*/wasi-preview1-component-adapter-provider-*/artefacts/wasi_snapshot_preview1.reactor.wasm
# Step 3: Pack into a .bex package
cargo run -p bex-cli --release -- pack \
dist/bex-gogoanime.yaml \
target/components/bex-gogoanime.component.wasm \
dist/bex-gogoanime.bex
```
### Install and Use
```bash
# Install a plugin
cargo run -p bex-cli --release -- install dist/bex-gogoanime.bex
# List installed plugins
cargo run -p bex-cli --release -- list
# Get detailed plugin info
cargo run -p bex-cli --release -- plugin-info bex.gogoanime
```
## Plugin Management
### Rust CLI
```bash
# Install / uninstall
bex install dist/bex-gogoanime.bex
bex uninstall bex.gogoanime
# List installed plugins
bex list
# Show detailed plugin info (capabilities, enabled state, etc.)
bex plugin-info bex.gogoanime
# Enable / disable
bex enable bex.gogoanime
bex disable bex.gogoanime
# Inspect a .bex package without installing
bex inspect dist/bex-gogoanime.bex
# Engine stats
bex stats
```
### C++ CLI
```bash
# Install / uninstall
./bexcli install dist/bex-gogoanime.bex
./bexcli uninstall bex.gogoanime
# List plugins (with capabilities column)
./bexcli list
# Detailed plugin info (includes API keys list)
./bexcli info-plugin bex.gogoanime
# Enable / disable
./bexcli enable bex.gogoanime
./bexcli disable bex.gogoanime
# Engine stats
./bexcli stats
```
## API Key / Secret Management
The engine provides per-plugin secret storage backed by Redb. Secrets are scoped to a plugin ID and are accessible to the plugin at runtime via the `secrets` WIT interface. This is the mechanism for storing API keys, tokens, and other credentials that plugins need.
### Rust CLI
```bash
# Set an API key
bex set-key bex.imdb api-key "your-api-key-here"
# Get an API key value
bex get-key bex.imdb api-key
# Delete an API key
bex delete-key bex.imdb api-key
# List all keys for a plugin
bex list-keys bex.imdb
```
### C++ CLI
```bash
# Set an API key
./bexcli set-key bex.imdb api-key "your-api-key-here"
# Get an API key value
./bexcli get-key bex.imdb api-key
# Delete an API key
./bexcli delete-key bex.imdb api-key
# List all keys for a plugin
./bexcli list-keys bex.imdb
```
### C API
```c
// Store a secret
bex_engine_secret_set(engine, "bex.imdb", "api-key", "your-key");
// Retrieve a secret
char buf[4096];
size_t buf_len = sizeof(buf);
bex_engine_secret_get(engine, "bex.imdb", "api-key", buf, &buf_len);
// Delete a secret
bex_engine_secret_delete(engine, "bex.imdb", "api-key");
// List all secret key names (comma-separated, caller frees with bex_string_free)
char* keys = bex_engine_secret_keys(engine, "bex.imdb");
bex_string_free(keys);
```
### Plugin Access (WIT)
Inside a plugin, secrets are accessed read-only through the `secrets` host interface:
```rust
use bindings::bex::plugin::secrets;
fn get_api_key() -> Option<String> {
secrets::get("api-key")
}
```
Secrets that a plugin expects are declared in the manifest:
```yaml
secrets:
- api-key
- tmdb-token
```
## Self-Describing IDs
Self-describing IDs are the core design pattern for how the BEX engine handles typed identifiers. The engine itself treats all IDs as opaque strings — it does not parse, validate, or interpret them. Only the plugin knows what its IDs mean and how to decode them.
This means:
- **`get_servers` takes a single `id` parameter** — there is no separate `episode_id` parameter
- **The engine never parses IDs** — it passes them straight through to the plugin
- **Each plugin defines its own ID encoding** — different plugins can use entirely different schemes
- **IDs are portable** — they can be stored, serialized, and passed between systems without the engine needing to understand them
### Example: GogoAnime Episode IDs
The GogoAnime plugin encodes episode context directly in the ID:
```
{slug}$ep={episode_number}$sub={0|1}$dub={0|1}
```
| ID | Meaning |
|----|---------|
| `one-piece$ep=1$sub=1$dub=0` | One Piece episode 1, subbed |
| `jujutsu-kaisen-tv$ep=24$sub=0$dub=1` | Jujutsu Kaisen episode 24, dubbed |
When `get_servers` is called with this ID, the GogoAnime plugin splits on `$` and parses each key-value pair to determine the slug, episode number, and sub/dub flags. The engine never does this parsing — it just passes the string through.
### Example: IMDb Media IDs
The IMDb plugin might use a different scheme entirely (e.g., `tt1234567`), and the engine works equally well because it does not interpret the ID.
### Usage
```bash
# The ID is self-describing — pass it directly
bex servers bex.gogoanime 'one-piece$ep=1$sub=1$dub=0'
# C++ CLI works the same way
./bexcli servers bex.gogoanime 'one-piece$ep=1$sub=1$dub=0'
```
## WIT Interface Definitions
### Host-Provided APIs (imports — plugins call these)
| Interface | Functions | Description |
|-----------|-----------|-------------|
| `http` | `send-request` | HTTP client with caching, redirect control, size limits |
| `kv` | `set`, `get`, `remove`, `keys` | Scoped key-value storage |
| `secrets` | `get` | Read-only secret/API key access |
| `log` | `write` | Structured logging through host |
| `clock` | `now-ms`, `monotonic` | Time access |
| `rng` | `bytes` | Secure random bytes |
| `js` | `eval-js`, `eval-js-opts`, `call-js-fn`, `clear-js-fn` | QuickJS sandbox — safe JS eval, function call, and cleanup |
### Plugin-Provided APIs (exports — host calls these)
| Function | Description |
|----------|-------------|
| `get-home` | Get home page sections |
| `get-category` | Browse by category with pagination |
| `search` | Search media content |
| `get-info` | Get detailed media info with episodes |
| `get-servers` | Get streaming servers for an episode |
| `resolve-stream` | Resolve stream source from server |
| `search-subtitles` | Search for subtitles |
| `download-subtitle` | Download subtitle file |
| `get-articles` | Get article sections |
| `search-articles` | Search articles |
## Writing a Plugin
### 1. Create a plugin project
```bash
cargo init --lib my-plugin
cd my-plugin
```
Add to `Cargo.toml`:
```toml
[package]
name = "my-plugin"
version = "0.1.0"
edition = "2021"
[dependencies]
wit-bindgen = { version = "0.57.1", features = ["bitflags"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "bex:my-plugin"
[package.metadata.component.dependencies]
```
### 2. Copy the WIT definitions
Copy `wit/plugin.wit` from the engine repository into your plugin's `wit/` directory.
### 3. Generate bindings
Run `cargo build --target wasm32-wasip1 --release` once to generate `src/bindings.rs`, then implement the `Guest` trait:
```rust
#[allow(warnings)]
mod bindings;
use bindings::bex::plugin::common::*;
use bindings::bex::plugin::http;
use bindings::exports::api::Guest;
struct Component;
impl Guest for Component {
fn get_home(_ctx: RequestContext) -> Result<Vec<HomeSection>, PluginError> {
Ok(vec![HomeSection {
id: "home".to_string(),
title: "My Plugin".to_string(),
subtitle: None,
items: vec![],
next_page: None,
layout: CardLayout::Grid,
show_rank: false,
categories: vec![],
extra: vec![],
}])
}
fn search(_ctx: RequestContext, query: String, _filters: SearchFilters) -> Result<PagedResult, PluginError> {
let response = http::send_request(&http::Request {
method: http::Method::Get,
url: format!("https://api.example.com/search?q={}", query),
headers: vec![],
body: None,
timeout_ms: Some(10000),
follow_redirects: true,
cache_mode: http::CacheMode::Normal,
max_bytes: Some(1024 * 1024),
}).map_err(|e| PluginError::Network(format!("{:?}", e)))?;
Ok(PagedResult { items: vec![], categories: vec![], next_page: None })
}
fn get_servers(_ctx: RequestContext, id: String) -> Result<Vec<Server>, PluginError> {
// The ID is self-describing — parse it however your plugin needs
// The engine does not interpret the ID, only your plugin does
let parts: Vec<&str> = id.split('$').collect();
// ... parse and fetch servers ...
Ok(vec![])
}
// ... implement other methods ...
}
bindings::export!(Component with_types_in bindings);
```
### 4. Build, convert, and pack
```bash
# Step 1: Compile to WASM
cargo build --target wasm32-wasip1 --release
# Step 2: Convert to a WASM Component
wasm-tools component new \
target/wasm32-wasip1/release/my_plugin.wasm \
-o target/components/my-plugin.component.wasm \
--adapt /path/to/wasi_snapshot_preview1.reactor.wasm
# Step 3: Pack into a .bex package
bex pack manifest.yaml target/components/my-plugin.component.wasm my-plugin.bex
# Step 4: Install
bex install my-plugin.bex
```
### Plugin Manifest
```yaml
schema: 1
id: bex.my-plugin
name: My Plugin
version: 1.0.0
authors:
- Your Name
abi: ">=1.0.0,<2.0.0"
provides:
home: true
search: true
info: true
servers: true
stream: true
network:
hosts:
- "api.example.com"
concurrent: 4
storage: true
secrets:
- api-key
display:
description: My awesome plugin
tags:
- streaming
priority: 100
```
### Capability Bits
| Bit | Name | Methods |
|-----|------|---------|
| 0 | `HOME` | `get_home` |
| 1 | `CATEGORY` | `get_category` |
| 2 | `SEARCH` | `search` |
| 3 | `INFO` | `get_info` |
| 4 | `SERVERS` | `get_servers` |
| 5 | `STREAM` | `resolve_stream` |
| 6 | `SUBTITLES` | `search_subtitles`, `download_subtitle` |
| 7 | `ARTICLES` | `get_articles`, `search_articles` |
## CMake Integration
The C++ CLI's `CMakeLists.txt` is designed to be self-contained and reusable. You can integrate the BEX engine into any C++ project with minimal setup. The only requirement is the `bex_engine.h` header and the Rust static/shared library — no cxx bridge, no code generation, no special include paths.
### Quick Integration
1. Build the Rust library:
```bash
cargo build -p bex-runtime --release
```
2. Copy the `cpp-cli/` directory into your project (or reference it via `BEX_ENGINE_ROOT`).
3. In your `CMakeLists.txt`:
```cmake
cmake_minimum_required(VERSION 3.16)
project(myapp LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
# Point to the bex-engine root (required if not inside the repo)
set(BEX_ENGINE_ROOT "/path/to/bex-engine")
# Add the bex engine subdirectory
add_subdirectory(${BEX_ENGINE_ROOT}/cpp-cli bex_engine_build)
# Link against the imported bex::engine target
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE bex::engine)
```
### BEX_ENGINE_ROOT Variable
The CMake build uses `BEX_ENGINE_ROOT` to locate the Rust library and the C header:
- **Default**: If not set, it walks up from `CMAKE_SOURCE_DIR` to find a directory containing `Cargo.toml`
- **Override**: Set `-DBEX_ENGINE_ROOT=/path/to/bex-engine` when running cmake
### Imported Target
The CMakeLists.txt creates an INTERFACE library target `bex::engine` with all include paths and link libraries configured:
```cmake
target_link_libraries(myapp PRIVATE bex::engine)
```
### Helper Target
A `rustlib` custom target is provided to build the Rust library from CMake:
```bash
make rustlib
```
### Required Files
| File | Purpose |
|------|---------|
| `cpp-cli/bex_engine.h` | Pure C ABI header (the FFI boundary) |
| `target/release/libbex_runtime.a` | Rust static library (Pure C ABI exports) |
That's it. No generated headers, no bridge codegen, no extra include paths.
## Error Code Reference
| Code | Meaning |
|------|---------|
| `ABI_MISMATCH` | Plugin ABI version incompatible |
| `INVALID_MANIFEST` | Manifest validation failed |
| `HASH_MISMATCH` | Package integrity check failed |
| `NOT_FOUND` | Plugin or resource not found |
| `DISABLED` | Plugin is disabled |
| `UNSUPPORTED` | Operation not supported by plugin |
| `NETWORK_BLOCKED` | Host not in plugin's allowed list |
| `TIMEOUT` | Request timed out |
| `FUEL_EXHAUSTED` | WASM fuel limit exceeded |
| `CANCELLED` | Request was cancelled |
| `PLUGIN_FAULT` | Plugin panicked or crashed |
| `PLUGIN_ERROR` | Plugin returned an error |
| `NETWORK` | Network error |
| `STORAGE` | Storage error |
| `NOT_READY` | Engine not ready |
| `INTERNAL` | Internal engine error |
## Technologies
| Component | Technology | Version |
|-----------|-----------|---------|
| WASM Runtime | Wasmtime | 30 |
| Interface Types | WIT (Component Model) | - |
| WASI | WASI Preview 1/2 | - |
| Bindings | wit-bindgen | 0.57.1 |
| Database | Redb | 2 |
| HTTP | reqwest (rustls) | 0.12 |
| C++ FFI | Pure C ABI (extern "C") | - |
| Wire Format | FlatBuffers | - |
| Compression | zstd | 0.13 |
| Async Runtime | tokio | 1 |
| Cancellation | tokio-util (CancellationToken) | - |
| Package Format | Custom (BEX v1) | - |
## License
MIT