---
name: rterm-plugin
description: Develop, test, and deploy custom RTerm plugins — drop a folder into plugins/ with a plugin.json manifest + a register(ctx) entry module, and RTerm auto-integrates it (agent tools, event-driven triggers, dashboard panels). Use when an agent needs to create a new plugin, scaffold a plugin template, test plugin loading, or deploy a plugin to an rterm-backend instance. Includes the manifest schema, PluginContext API, templates, and ready-made examples.
---

# RTerm Plugin Development Skill

Build custom plugins that **auto-integrate with RTerm**. You write a folder with a
`plugin.json` manifest + a small code file, drop it into `plugins/`, and RTerm
discovers it, loads it, calls its `register(ctx)` with RTerm's services, and your
capabilities appear automatically — **agent tools, event-driven triggers, and
dashboard panels** — with **no RTerm code changes**.

This skill gives an agent everything to **create, test, and deploy** plugins: the
manifest schema, the PluginContext API, ready-to-copy templates, a scaffold
command, a local test runner, and complete working examples.

---

## 1. The model (mental picture)

```
plugins/
  my-plugin/
    plugin.json   ──►  RTerm PluginRegistry discovers it
    index.ts      ──►  loads index.ts, calls register(ctx) ──►  your capabilities
                         (agent tools, triggers, panels) appear in RTerm
```

- **Discover** — RTerm scans `plugins/` (and `<dataDir>/plugins`) on boot/reload.
- **Load** — reads `plugin.json`, then dynamic-imports your entry module.
- **Register** — calls `register(ctx)`; your `registerTool`/`registerTrigger`/`registerPanel` calls are captured.
- **Integrate** — your tools become agent tools, your triggers join the TriggerEngine, your panels render in the dashboard.

---

## 2. The manifest (`plugin.json`)

```jsonc
{
  "name": "my-plugin",               // required — unique name
  "version": "1.0.0",                // required — semver
  "description": "what it does",      // optional
  "author": "you",                    // optional
  "entry": "index.ts",                // optional — entry module (default: index.js|index.ts|index.mjs)
  "tools": ["my_tool"],               // optional — declared agent tools
  "triggers": [{ "name": "my-trigger", "kind": "pattern", "match": "ERROR" }],  // optional
  "panels": ["my-panel"],             // optional — declared dashboard panels
  "permissions": ["exec_command", "read_ledger"]  // optional — requested permissions
}
```

`name` + `version` are **required**. Everything else is optional. The registry
validates it and records an error (without crashing other plugins) when a plugin
is invalid.

---

## 3. The PluginContext API (what `register(ctx)` gets)

Your entry module exports a `register(ctx)` function. RTerm calls it with a
`PluginContext`:

| Method | What it does |
|---|---|
| `ctx.registerTool(tool)` | Register an **agent tool** the agent can call: `{ name, description, handler(args) => result }`. |
| `ctx.registerTrigger(trigger)` | Register an **event-driven trigger**: `{ name, kind: 'pattern'\|'threshold'\|'webhook'\|'schedule', match?, metric?, op?, value?, action }`. |
| `ctx.registerPanel(name, render)` | Register a **dashboard panel**: `render()` returns HTML. |
| `ctx.exec(command, opts?)` | Run a command on a host (the agent's policy-gated exec path). `opts.host` selects the target. |
| `ctx.readLedger(name, query?)` | Read RTerm's ledgers (`'metrics'`, `'incidents'`, …). |
| `ctx.log(line)` | Write a line to the RTerm log. |

A plugin may also export `unregister()` for teardown on disable/uninstall.

---

## 4. A minimal plugin (copy this)

**`plugins/hello/plugin.json`**
```json
{
  "name": "hello",
  "version": "1.0.0",
  "description": "A hello-world plugin",
  "tools": ["hello_greet"],
  "permissions": []
}
```

**`plugins/hello/index.ts`**
```ts
export function register(ctx) {
  ctx.log('[hello] registering')
  ctx.registerTool({
    name: 'hello_greet',
    description: 'Greet someone by name.',
    handler: async (args) => ({ greeting: `Hello, ${args.name ?? 'world'}! (from the hello plugin)` }),
  })
}
```

Drop it in `plugins/` and RTerm auto-integrates it — the agent can now call
`hello_greet` ("greet olu") and get back `{"greeting":"Hello, olu! (from the hello plugin)"}`.

---

## 5. A full plugin (tools + trigger + panel)

See `examples/host-health-plugin/` — a complete working plugin that registers an
agent tool (evaluate host health from the metrics ledger), a CPU-threshold trigger,
and a dashboard panel. Copy it as your starting point.

---

## 6. Scaffold a new plugin (one command)

```bash
node scripts/scaffold-plugin.mjs --name my-plugin --out ./plugins
# -> creates ./plugins/my-plugin/{plugin.json,index.ts} ready to edit
```

Or copy `templates/plugin-template/` manually.

---

## 7. Test your plugin locally (before deploying)

The skill ships a **test runner** that loads your plugin through the real
`PluginRegistry` (the same code RTerm uses) and prints what it registered:

```bash
node scripts/test-plugin.mjs --dir ./plugins/hello
# -> discovered: hello@1.0.0 | tools: hello_greet | triggers: 0 | panels: 0
# -> calls hello_greet to prove it executes
```

This runs the exact `pluginRegistry.ts` from RTerm, so a pass here = it loads in RTerm.

---

## 8. Deploy to an rterm-backend instance

1. **Copy the plugin folder** into the backend's `plugins/` dir (or `<GYBACKEND_DATA_DIR>/plugins/`):
   ```bash
   scp -r plugins/hello user@backend:/opt/rterm-backend/plugins/
   ```
2. **Reload** — the backend discovers new plugins on boot; to hot-reload, restart or call the reload path (the registry reloads on `reload()`).
3. **Verify** — ask the agent: "list the loaded plugins" or "call hello_greet with name=olu".

The sample plugin `plugins/sample-k8s-slo` (shipped in RTerm) is a reference deployment.

---

## 9. The plugin lifecycle (manage it)

- **Enable/disable** — `pluginRegistry.setEnabled(name, enabled)` gates a plugin's capabilities without uninstalling it.
- **Uninstall** — `pluginRegistry.uninstall(name)` drops it and its capabilities.
- **Error handling** — a plugin that fails to load records an `error` on its record, is excluded from `allTools`/`allTriggers`/`allPanels`, but stays in the registry for diagnosis.
- **Dedupe** — reloading the same `name` replaces the previous record (new id).
- **Hot reload** — `pluginRegistry.reload()` re-discovers everything in the scan roots.

---

## 10. Real examples in this skill

- **`examples/host-health-plugin/`** — a complete plugin (tool + threshold trigger + panel) you can run today.
- **`examples/host-health-plugin/test.mjs`** — a self-contained test that loads it via the registry and calls its tool.
- **`templates/plugin-template/`** — the minimal scaffold to copy.

---

## Supporting files

- `scripts/scaffold-plugin.mjs` — scaffold a new plugin folder (manifest + entry).
- `scripts/test-plugin.mjs` — load a plugin via the real PluginRegistry and report what it registered.
- `templates/plugin-template/` — minimal plugin scaffold (plugin.json + index.ts).
- `examples/host-health-plugin/` — a complete working plugin (tool + trigger + panel) + its test.
