bmux_plugin_sdk 0.0.1-alpha.0

Plugin SDK for bmux — the types and traits plugin authors need
docs.rs failed to build bmux_plugin_sdk-0.0.1-alpha.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

bmux Plugin SDK

Everything you need to write a bmux plugin.

Quick Start

A bmux plugin is a Rust crate with three files: a manifest, a library, and a Cargo.toml.

1. plugin.toml

id      = "example.hello"
name    = "Hello Plugin"
version = "0.1.0"

[[commands]]
name          = "hello"
summary       = "Print a greeting"
expose_in_cli = true

2. src/lib.rs

use bmux_plugin_sdk::prelude::*;

#[derive(Default)]
pub struct HelloPlugin;

impl RustPlugin for HelloPlugin {
    fn run_command(&mut self, ctx: NativeCommandContext) -> Result<i32, PluginCommandError> {
        bmux_plugin_sdk::route_command!(ctx, {
            "hello" => {
                let name = ctx.arguments.first().map_or("world", String::as_str);
                println!("Hello, {name}!");
                Ok(EXIT_OK)
            },
        })
    }
}

bmux_plugin_sdk::export_plugin!(HelloPlugin, include_str!("../plugin.toml"));

3. Cargo.toml

[package]
name    = "my_plugin"
edition = "2024"
version = "0.1.0"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
bmux_plugin_sdk = { version = "..." }

[features]
static-bundled = []

crate-type must include cdylib (for dynamic loading) and rlib (for static bundling into the host binary).

The RustPlugin Trait

Every plugin implements RustPlugin. All five methods have default implementations -- override only what your plugin needs:

Method Return type When to override
run_command Result<i32, PluginCommandError> Plugin provides CLI commands
invoke_service ServiceResponse Plugin provides services to other plugins
activate Result<i32, PluginCommandError> Plugin needs setup on activation
deactivate Result<i32, PluginCommandError> Plugin needs cleanup on deactivation
handle_event Result<i32, PluginCommandError> Plugin subscribes to system events

The trait requires Default + Send + 'static. Use #[derive(Default)] on your struct.

Writing Command Plugins

Dispatching commands

Use route_command! to match on the command name and auto-generate the unknown-command fallback:

fn run_command(&mut self, ctx: NativeCommandContext) -> Result<i32, PluginCommandError> {
    bmux_plugin_sdk::route_command!(ctx, {
        "list"   => handle_list(&ctx),
        "create" => handle_create(&ctx),
    })
}

Each arm must evaluate to Result<i32, PluginCommandError>. Unrecognised commands automatically return Err(PluginCommandError::unknown_command(...)).

Error handling

PluginCommandError implements From for common error types, so the ? operator works naturally:

fn handle_list(ctx: &NativeCommandContext) -> Result<i32, PluginCommandError> {
    let data = std::fs::read_to_string("config.toml")?;  // io::Error -> PluginCommandError
    let parsed: Config = toml::from_str(&data)?;          // toml::de::Error -> PluginCommandError
    println!("{parsed:?}");
    Ok(EXIT_OK)
}

Supported conversions: String, &str, std::io::Error, serde_json::Error, toml::de::Error, Box<dyn Error>, Box<dyn Error + Send + Sync>.

For custom error codes, construct directly:

Err(PluginCommandError::new(EXIT_USAGE, "missing required argument"))
Err(PluginCommandError::unavailable("feature not supported on this platform"))

Exit codes

Constant Value Meaning
EXIT_OK 0 Success
EXIT_ERROR 1 Generic failure
EXIT_USAGE 64 Bad arguments or unknown command
EXIT_UNAVAILABLE 70 Plugin unavailable

Arguments

Commands receive arguments as ctx.arguments: Vec<String>. The host CLI layer handles parsing and validation based on the argument declarations in plugin.toml -- the plugin receives the parsed values as strings.

Writing Service Plugins

Service plugins handle inbound requests from other plugins or the host runtime.

Dispatching services

Use route_service! to match on (interface_id, operation) pairs. Each handler receives a typed request and returns a typed response:

fn invoke_service(&mut self, context: NativeServiceContext) -> ServiceResponse {
    bmux_plugin_sdk::route_service!(context, {
        "my-service/v1", "do_thing" => |req: DoThingRequest, _ctx| {
            let result = do_the_thing(&req.input)?;
            Ok(DoThingResponse { output: result })
        },
    })
}

The macro wraps each handler in handle_service(), which handles request deserialization, response serialization, and error conversion. Unrecognised operations return a standard "unsupported" error.

Request and response types must implement serde::Deserialize and serde::Serialize respectively.

Returning errors from service handlers

Service handler closures return Result<Resp, ServiceResponse>. Use map_err to convert domain errors:

"my-service/v1", "create" => |req: CreateRequest, _ctx| {
    create_thing(&req.name)
        .map_err(|e| ServiceResponse::error("create_failed", e.to_string()))
},

The handle_service function

If you need more control than route_service! provides, use handle_service directly:

handle_service(&context, |req: MyRequest, ctx| {
    // Full access to ctx for host API calls, self for plugin state
    Ok(MyResponse { ... })
})

The Prelude

use bmux_plugin_sdk::prelude::* imports the ~16 items most plugins need:

  • Trait: RustPlugin
  • Context types: NativeCommandContext, NativeLifecycleContext, NativeServiceContext
  • Error type: PluginCommandError
  • Exit codes: EXIT_OK, EXIT_ERROR, EXIT_USAGE, EXIT_UNAVAILABLE
  • Service types: ServiceKind, ServiceResponse
  • Events: PluginEvent
  • Helpers: handle_service, decode_service_message, encode_service_message

Types not in the prelude (import individually when needed):

  • Host service DTOs: SessionSelector, StorageGetRequest, ContextCreateRequest, etc.
  • Manifest types: PluginCommand, PluginService, PluginEventSubscription
  • Capability types: HostScope, PluginFeature

When to Also Depend on bmux_plugin

The bmux_plugin crate provides two traits that live outside the SDK:

  • ServiceCaller -- the low-level trait for dispatching cross-plugin service calls
  • HostRuntimeApi -- ergonomic methods like ctx.session_list(), ctx.storage_get(...), ctx.context_create(...)

If your plugin calls host services (session management, storage, pane operations, etc.), add bmux_plugin as a dependency and import these traits:

use bmux_plugin::{HostRuntimeApi, ServiceCaller};

Simple plugins that only handle commands or receive service calls do not need bmux_plugin.

Manifest Reference (plugin.toml)

Top-Level Fields

Field Type Required Default Description
id string Yes -- Unique plugin identifier (e.g. "bmux.clipboard")
name string Yes -- Human-readable display name
version string Yes -- Plugin version (semver)
description string No -- Optional description
homepage string No -- Optional URL
runtime string No "native" Runtime type (only "native" supported)
entry path No -- Path to .dylib/.so (only for external plugins)
entry_symbol string No "bmux_plugin_entry_v1" FFI entry symbol
provider_priority integer No 0 Ordering when multiple plugins provide the same capability
required_capabilities list No [] Host capabilities this plugin needs
provided_capabilities list No [] Capabilities this plugin provides
provided_features list No [] Feature flags this plugin provides

Compatibility (optional)

[plugin_api]
minimum = "1.0"    # default; omit entire section if 1.0 is fine
maximum = "2.0"    # optional upper bound

[native_abi]
minimum = "1.0"    # default; omit entire section if 1.0 is fine

Commands

[[commands]]
name          = "hello"             # required -- dispatch name (matches ctx.command)
summary       = "Print a greeting"  # required -- short description for help text
expose_in_cli = true                # default: false -- set true to show as CLI subcommand
path          = ["greet"]           # optional -- CLI subcommand path
aliases       = [["say", "hi"]]    # optional -- alternative CLI paths
execution     = "provider_exec"     # default: "provider_exec"
description   = "Longer help"       # optional

execution = "provider_exec" runs the command in the plugin provider process. Use execution = "caller_process" for commands that need caller-local runtime facilities, such as an attach client's prompt/modal host.

Command Arguments

[[commands.arguments]]
name          = "target"       # required
kind          = "string"       # required: string | integer | boolean | path | choice
required      = true           # default: false
position      = 0              # optional: positional index (omit for flags/options)
long          = "target"       # optional: --target
short         = "t"            # optional: -t
multiple      = true           # default: false
value_name    = "TARGET"       # optional: displayed in help
choice_values = ["a", "b"]    # for kind = "choice"

Services

[[services]]
capability   = "bmux.clipboard.write"    # required -- host capability scope
interface_id = "clipboard-write/v1"      # required -- service interface identifier
kind         = "command"                 # required -- "command" or "query"

Event Subscriptions

[[event_subscriptions]]
kinds = ["system", "window"]
names = ["server_started", "window_created"]

Dependencies

[[dependencies]]
plugin_id   = "bmux.permissions"
version_req = "=0.0.1-alpha.0"
required    = true                # default: true

Keybindings

[keybindings.runtime]
c = "plugin:bmux.windows:new-window"
"alt+w" = "plugin:bmux.windows:switch-window"

[keybindings.scroll]
y = "copy_scrollback"

[keybindings.global]
# global keybindings (active outside runtime mode)