Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Augani/kael/llms.txt

Use this file to discover all available pages before exploring further.

Every Kael plugin is described by a manifest that the framework reads before spawning or loading anything. The manifest tells the host who you are, what API version you target, how your code runs, which system resources you need, and what UI surface area you want to occupy. Getting the manifest right is the foundation of working plugin development.

The PluginManifest struct

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PluginManifest {
    pub id: String,
    pub name: String,
    pub version: String,
    pub api_version: String,
    pub description: Option<String>,
    pub author: Option<String>,
    pub entry_point: String,
    pub execution_model: ExecutionModel,
    pub capabilities: Vec<Capability>,
    pub args: Vec<String>,
    pub contributions: Contributions,
}
FieldRequiredDescription
idYesReverse-DNS identifier, e.g. com.example.my-plugin
nameYesHuman-readable display name
versionYesSemver version string
api_versionYesHost API version targeted — currently "1.0.0"
entry_pointYesExecutable path or WASM module path
execution_modelYesExternalProcess or Wasm
descriptionNoShort description shown in plugin listings
authorNoAuthor name or contact
capabilitiesNoCapabilities requested from the host
argsNoExtra command-line arguments forwarded to the entry point
contributionsNoUI contribution points (commands, menus, panels, settings)
The id, name, version, and entry_point fields must not be empty. validate() returns an error for any of these if blank, and from_json, from_toml, and load all call validate() before returning.

Execution models

The ExecutionModel enum controls where and how your plugin code runs.
pub enum ExecutionModel {
    /// Run as a separate native OS process.
    ExternalProcess,
    /// Run in a sandboxed WASM runtime.
    Wasm,
}
The host spawns your binary as a child process and passes an IPC socket path in the environment:
KAEL_EXTENSION_SOCKET — path to the IPC transport socket
KAEL_EXTENSION_ID     — your plugin identifier
KAEL_API_VERSION      — host API version
Your binary must connect to that socket and complete the RPC handshake before doing anything else. This is the most common model for Rust plugins.

Writing a manifest file

Kael reads manifest.json and manifest.toml natively. Use whichever format fits your toolchain.
{
  "id": "com.example.my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "api_version": "1.0.0",
  "entry_point": "my-plugin",
  "execution_model": "ExternalProcess",
  "capabilities": [],
  "contributions": {}
}

Loading a manifest in Rust

use kael::plugin::PluginManifest;

// From a file path (.json or .toml detected by extension)
let manifest = PluginManifest::load("/path/to/manifest.json")?;

// From a JSON string
let manifest = PluginManifest::from_json(json_str)?;

// From a TOML string
let manifest = PluginManifest::from_toml(toml_str)?;
All three paths call validate() internally. If any required field is missing, you get an Err with a descriptive message.

Building a manifest with the builder API

The builder lets you construct a manifest programmatically without constructing the struct directly:
use kael::plugin::{ExecutionModel, PluginManifest};
use kael::security::Capability;

let manifest = PluginManifest::builder(
    "com.example.my-plugin",
    "My Plugin",
    "1.0.0",
    "1.0.0",
    "my-plugin",
    ExecutionModel::ExternalProcess,
)
.description("A demonstration plugin")
.author("Alice")
.capability(Capability::Notification)
.build()?;
Each builder method returns Self, so you can chain calls freely. .build() calls validate() and returns Result<PluginManifest>.

Capabilities

Your plugin must declare every system resource it needs. Capabilities not listed are denied by the host’s PermissionBroker.
pub enum Capability {
    OpenExternalUrl,
    FilesystemRead { scope: PathScope },
    FilesystemWrite { scope: PathScope },
    ShellExecute,
    ClipboardRead,
    ClipboardWrite,
    Notification,
    Network { hosts: Vec<String> },
    Microphone,
    Camera,
    ScreenCapture,
}
PathScope controls filesystem reach:
ScopeAccessible paths
AppDataApplication data directory only
DownloadsUser’s downloads directory only
UserSelectedPaths the user picks via a file dialog
AnyUnrestricted — high-risk
The following capabilities are classified as high-risk and require explicit opt-in from the application’s permission broker: ShellExecute, ClipboardRead, Network, Camera, ScreenCapture, FilesystemRead { scope: Any }, and FilesystemWrite { scope: Any }. Only request what your plugin genuinely needs.

Checking high-risk capabilities

let risky = manifest.high_risk_capabilities();
if !risky.is_empty() {
    // warn the user or require confirmation before loading
}

Capability declaration in JSON

{
  "capabilities": [
    "Notification",
    { "FilesystemRead": { "scope": "AppData" } },
    { "Network": { "hosts": ["api.example.com"] } }
  ]
}

Contribution points

The Contributions struct describes the UI surface area your plugin registers:
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Contributions {
    pub commands: Vec<ContributedCommand>,
    pub menu_items: Vec<ContributedMenuItem>,
    pub panels: Vec<ContributedPanel>,
    pub settings_schema: Option<serde_json::Value>,
}

Commands

Commands appear in the application’s command palette and can carry a keybinding:
pub struct ContributedCommand {
    pub id: String,
    pub title: String,
    pub keybinding: Option<String>,
}
{
  "contributions": {
    "commands": [
      { "id": "my-plugin.hello", "title": "Say Hello", "keybinding": "cmd+shift+h" }
    ]
  }
}
Menu items attach to existing named application menus ("file", "edit", "view", etc.) and invoke a command when activated:
pub struct ContributedMenuItem {
    pub target_menu: String,
    pub label: String,
    pub command_id: String,
}

Panels

Panels dock into the workspace layout at a fixed position by default:
pub struct ContributedPanel {
    pub id: String,
    pub title: String,
    pub default_position: PanelPosition,
}

pub enum PanelPosition {
    Left,
    Right,
    Bottom,
    Floating,
}

Settings schema

Supply a JSON Schema value to register a settings namespace for your plugin:
.settings_schema(serde_json::json!({
    "type": "object",
    "properties": {
        "enabled": { "type": "boolean", "default": true }
    }
}))

Minimal working plugin

1

Create the plugin directory

my-plugin/
  manifest.json
  src/
    main.rs
2

Write manifest.json

{
  "id": "com.example.my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "api_version": "1.0.0",
  "entry_point": "my-plugin",
  "execution_model": "ExternalProcess",
  "capabilities": ["Notification"],
  "contributions": {
    "commands": [
      { "id": "my-plugin.hello", "title": "Hello World", "keybinding": null }
    ],
    "panels": [
      { "id": "my-plugin.panel", "title": "My Panel", "default_position": "Right" }
    ]
  }
}
3

Write src/main.rs

use kael::{
    ExtensionHandshake, ExtensionMessage, ExtensionRequest, ExtensionResponse,
    ExtensionTransport, EXTENSION_RPC_VERSION, UnixDomainSocketTransport,
};

fn main() {
    let socket = std::env::var("KAEL_EXTENSION_SOCKET").unwrap();
    let transport = UnixDomainSocketTransport::connect(&socket).unwrap();
    let mut transport = ExtensionTransport::new(Box::new(transport));

    // Handshake
    if let Ok(ExtensionMessage::Handshake(ExtensionHandshake::Host { .. })) =
        transport.recv_message()
    {
        transport
            .send_handshake(ExtensionHandshake::Extension {
                version: EXTENSION_RPC_VERSION,
                accepted: true,
            })
            .unwrap();
    }

    // Main request loop
    loop {
        match transport.recv_message() {
            Ok(ExtensionMessage::Rpc(kael::process_model::IpcMessage::Request {
                id,
                body,
            })) => match body {
                ExtensionRequest::Shutdown => {
                    transport
                        .send_response(id, Ok(ExtensionResponse::Ack))
                        .unwrap();
                    break;
                }
                ExtensionRequest::ExecuteCommand { command_id, .. } => {
                    eprintln!("executing: {}", command_id);
                    transport
                        .send_response(id, Ok(ExtensionResponse::Ack))
                        .unwrap();
                }
                _ => {
                    transport
                        .send_response(id, Ok(ExtensionResponse::Ack))
                        .unwrap();
                }
            },
            Err(_) => break,
            _ => {}
        }
    }
}
4

Load the plugin in your application

use kael::ExtensionHostRuntime;

let mut runtime = ExtensionHostRuntime::new("/app/extensions", "my-app");

// Dev mode: load directly from source directory (no copy)
runtime.load_from_directory("/path/to/my-plugin")?;

Security best practices

The host’s PermissionBroker checks every capability your plugin declares before activation. Requesting capabilities you do not use creates unnecessary risk and makes users less likely to trust your plugin.
When you receive ExtensionRequest::Shutdown, flush any pending state and acknowledge before exiting. The host may terminate the process shortly after the response if you do not exit cleanly.
Treat ExecuteCommand arguments and any other host-supplied values as untrusted. Validate types and ranges before acting on them.
Call manifest.high_risk_capabilities() during development to audit what you declared, and design your plugin to degrade gracefully when those capabilities are denied at runtime.

Build docs developers (and LLMs) love