Codumentor logo Codumentor

Plugin Quick Start

Build a working plugin in 15 minutes.


Step 1: Create the Backend Plugin

Create a Python class with a meta property, a register method, and at least one hook handler:

class HelloPlugin:
    def __init__(self, config=None, **kwargs):
        self.meta = {
            "name": "hello_plugin",
            "version": "1.0.0",
            "description": "A hello-world plugin",
            "priority": 50,
        }

    async def register(self, bus):
        """Subscribe to lifecycle hooks."""
        bus.on("onInputReceived", self.on_input, priority=10)

    async def on_input(self, agent, ctx, payload):
        """Handle the onInputReceived event."""
        raw_input = payload["raw"]
        return {"type": "continue", "patch": {"raw": f"[Hello] {raw_input}"}}

    def capabilities(self):
        """Return plugin capabilities."""
        return {"greeting": True}

Step 2: Enable in Configuration

Add the plugin to codumentor.yaml:

plugins:
  - module: "my_plugins.hello"
    class: "HelloPlugin"
    args:
      greeting: "Hello, world!"

Step 3: Create the UI Plugin

  1. Copy the template:

``bash
cp -r ui/plugins/_template ui/plugins/hello_plugin
``

  1. Update manifest.json:

``json
{
"plugin_id": "hello_plugin",
"name": "Hello Plugin",
"version": "1.0.0",
"description": "A hello-world plugin for learning",
"event_types": ["plugin.hello_plugin.greeted"],
"ui": {
"bundle": "index.js",
"sdk_version": "1.0",
"custom_elements": ["x-hello-plugin-card"],
"placements": ["transcript-card"]
}
}
``

Optional: Register markdown code-block renderers

If your UI bundle wants to render specific fenced code blocks (for example, a custom diagram language), add markdown_renderers in the ui manifest:

{
  "ui": {
    "bundle": "index.js",
    "sdk_version": "1.0",
    "custom_elements": ["x-hello-plugin-card"],
    "placements": ["transcript-card"],
    "markdown_renderers": [
      {
        "id": "hello_plugin.diagram_renderer",
        "languages": ["mydiagram"],
        "export_name": "myDiagramRenderer",
        "priority": 50,
        "override_existing": false
      }
    ]
  }
}

Your bundle must export myDiagramRenderer with the markdown renderer contract (id, languages, and a React component). The host validates and registers it after the extension bundle loads.

Streaming-safety guidance:

  1. Create your component in src/index.ts:

```typescript
import {
BasePluginElement,
bindExpandClick,
getBaseStyles,
escapeHtml,
} from "@codumentor/plugin-sdk";

interface HelloData {
message: string;
}

class HelloPluginCard extends BasePluginElement {
render(): void {
const { message } = this._data;

this._shadowRoot.innerHTML =
<style>${getBaseStyles()}</style>
<div class="card ${this._status}">
<div class="header">
<span class="icon">${this.getStatusIcon()}</span>
<span class="title">Hello Plugin</span>
</div>
<p>${escapeHtml(message)}</p>
</div>
;

bindExpandClick(this._shadowRoot, ".header", () => this.toggleExpanded());
}
}

if (!customElements.get("x-hello-plugin-card")) {
customElements.define("x-hello-plugin-card", HelloPluginCard);
}

export { HelloPluginCard };
```

Step 4: Build

cd ui && npm run build:plugins

Step 5: Test

Run Codumentor and verify the plugin loads:

python -m codumentor

Check the logs for hello_plugin registration messages. Send a message through the UI and confirm the [Hello] prefix appears in the processed input.


Plugin Interface Reference

Method / PropertyRequiredDescription
metaYesDict with name, version, description, priority, optional dependencies (list of plugin names that must register before this one — see Plugin Load Order)
register(bus)YesSubscribe to lifecycle hooks via bus.on()
capabilities()NoReturn a dict describing plugin capabilities
ui_manifest()NoReturn UI extension configuration. See Backend Hooks
api_router()NoReturn a FastAPI APIRouter for HTTP endpoints. See API Routes
cleanup()NoInstance-level teardown — close connections, cancel tasks, release resources. Called once per instance on application shutdown
shutdown_all()NoClassmethod. Tear down class-level shared resources (e.g. shared subprocess pools). Called once per plugin class, after all instance cleanup() calls. See Shutdown & Cleanup

Next Steps