Creating Plugins
Status: wired (#189 closed). See Plugin System Overview — Status for the full surface. Snippets below are compile-link verified by the doc-tests harness against
docs/examples/plugins/plugin_snippets.tsanddocs/examples/plugins/host_snippets.ts.
Build Perry plugins as shared libraries that extend host applications.
Step 1: Write the Plugin
let count = 0
export function activate(api: PluginApi) {
api.setMetadata("counter", "1.0.0", "Counts hook invocations")
api.registerHook("onRequest", (data) => {
count++
console.log(`Request #${count}`)
return data
})
api.registerTool("getCount", "returns request count", () => count)
}
export function deactivate() {
console.log(`Total requests processed: ${count}`)
}
Step 2: Compile as Shared Library
perry counter-plugin.ts --output-type dylib -o counter-plugin.dylib
The --output-type dylib flag tells Perry to produce a .dylib (macOS) or .so (Linux) instead of an executable.
Perry automatically:
- Generates
perry_plugin_abi_version()returning the current ABI version - Generates
plugin_activate(api_handle)calling youractivate()function - Generates
plugin_deactivate()calling yourdeactivate()function - Exports symbols with
-rdynamicfor the host to find
Step 3: Load from Host
import {
loadPlugin, unloadPlugin,
emitHook, emitEvent, invokeTool,
setPluginConfig,
discoverPlugins, listPlugins, listHooks, listTools,
pluginCount, initPlugins,
} from "perry/plugin"
const id = loadPlugin("./counter-plugin.dylib")
console.log(`load returned: ${id !== 0 ? "ok" : "fail"}`)
const found = discoverPlugins("./plugins/")
console.log(`discovered ${found.length} plugin(s)`)
const result = emitHook("beforeSave", { content: "hello world" })
const greeting = invokeTool("greet", { name: "Perry" })
const formatted = invokeTool("formatCode", {
code: "const x=1",
language: "typescript",
})
Plugin API Reference
The api: PluginApi passed to activate() provides:
Metadata
api.setMetadata(name: string, version: string, description: string): void
Hooks
api.registerHook(name: string, handler: (ctx: unknown) => unknown): void
api.registerHookEx(name: string, handler: (ctx: unknown) => unknown, priority: number, mode: number): void
registerHook defaults to priority 10 / mode 0 (filter). Use registerHookEx
for explicit priority and mode (0=filter, 1=action, 2=waterfall). Lower
priority numbers run first.
Tools
api.registerTool(name: string, description: string, handler: (args: unknown) => unknown): void
Tools are invoked by name from the host.
Configuration
const value = api.getConfig(key: string) // Read host-provided config
Events
api.on(event: string, handler: (data: unknown) => void): void // Listen for events
api.emit(event: string, data: unknown): void // Emit to other plugins
Next Steps
- Hooks & Events — Hook modes, event bus
- Overview — Plugin system overview