cradle-plugin-api 0.1.3

API for cradle plugins
Documentation
# `cradle-plugin-api`

This crate is intended for use when developing plugins for the Cradle agent
Documentation of the available functions is located in the [docs.rs](https://docs.rs/crate/cradle-plugin-api/) page.

> [!WARNING]
> Every plugin must implement `Default`, as the registration macro relies on it.
> Plugins must also be compiled as `cdylib` crates.

## Cargo.toml

```toml
[package]
name = "my-cradle-plugin"
version = "0.1.0"
edition = "2024"

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

[dependencies]
cradle-plugin-api = "0.1"
cradle-shared = "0.1"
```

## Minimal plugin

```rust
use cradle_plugin_api::*;

struct TestPlugin {
    results: PluginResults,
}

impl Default for TestPlugin {
    fn default() -> Self {
        Self {
            results: PluginResults::new("test-plugin"),
        }
    }
}

impl CradlePlugin for TestPlugin {
    fn name(&self) -> &str {
        "test-plugin"
    }

    fn on_event(&mut self, event: &AgentEvent) -> CradleResult {
        if let AgentEvent::ProcessCreated { target_path, .. } = event {
            self.results
                .pass("process-created", format!("process created: {target_path}"));
        }

        Ok(())
    }

    fn results(&mut self) -> Vec<CheckResult> {
        self.results.take()
    }
}

register_plugin!(TestPlugin);
```

## Emitting results

`PluginResults` helps build check output with the plugin name filled in
Use `take()` in your `results()` implementation so each result is returned only once

```rust
self.results.pass("loaded", "plugin initialized successfully");
self.results.info("target", "received process metadata");
self.results.warn_with("network", "direct IP connection observed", "no DNS event matched it");
self.results.fail("policy", "blocked behavior detected");
```

## Hooking an export

Plugins that need runtime inspection can install hooks during `init`
Hooking is unsafe because it reads and writes target process memory

```rust
use cradle_plugin_api::*;

struct HookPlugin {
    results: PluginResults,
}

impl Default for HookPlugin {
    fn default() -> Self {
        Self {
            results: PluginResults::new("hook-plugin"),
        }
    }
}

impl CradlePlugin for HookPlugin {
    fn name(&self) -> &str {
        "hook-plugin"
    }

    fn init(&mut self, engine: CradleMutex<cradle_hooks::HookEngine>) -> CradleResult {
        unsafe {
            engine.lock().hook_export("kernel32.dll", "CreateFileW", Box::new(|ctx| {
                let file_name_ptr = ctx.arg(0);
                let file_name = ctx.read_wide_string(file_name_ptr, 512);

                clog!("CreateFileW called for {file_name}");

                Ok(cradle_hooks::HookAction::Continue)
            }))
        }
    }

    fn results(&mut self) -> Vec<CheckResult> {
        self.results.take()
    }
}

register_plugin!(HookPlugin);
```

## Logging

Use `clog!` for general plugin logs or `plog!(self, "...")` to prefix logs with the plugin name.
Logs are stored by the plugin API and drained by the agent.

## ABI exports

Use `register_plugin!(YourPluginType)` unless you are intentionally implementing the raw FFI boundary yourself.
The macro exports the required symbols for API versioning, lifecycle callbacks, result transfer, and plugin-owned string cleanup.