Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.ts and docs/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 your activate() function
  • Generates plugin_deactivate() calling your deactivate() function
  • Exports symbols with -rdynamic for 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