Plugin Authoring Guide

Everything you need to build an OwnTerm plugin.

Architecture Overview

OwnTerm plugins live on disk as a directory with a manifest.json file. Each plugin registers itself into one or more registries via the manifest. The four registries are:

RegistryPurpose
ServerPython servers (STT, TTS)
TTS ProviderCustom voice backends
ProviderNon-server LLM providers
Plugin ComponentLazy-loaded React settings tabs

A single plugin can register into multiple registries (e.g., KokoClone registers a server, a TTS provider, and a UI component).

Plugin Types

Server-based (STT/TTS)

Spawns a local Python Flask server. Examples: whisper-stt, kokoro-tts, kokoclone.

~/.ownterm/plugins/my-stt/
  manifest.json        # metadata + settings + server config
  my_stt_server.py     # Python server script

Provider (no server)

Wraps an external binary or API. Example: claude-cli.

~/.ownterm/plugins/my-provider/
  manifest.json        # metadata + provider config

Tool Plugin (JavaScript handler)

Exposes tools the AI can call during conversations. Tools are defined in the manifest and executed by a handler file. Example: browser-bridge, goal-tools, web-tools.

~/.ownterm/plugins/my-tools/
  manifest.json        # metadata + tools array + toolConfig
  handler.js           # exports execute(toolName, args)

manifest.json

Every plugin requires a manifest.json at its root:

{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "What it does.",
  "author": "Your Name",
  "type": "stt",
  "icon": "⚙",
  "settings": [ ... ],
  "server": { ... },

  // Tool plugins: define tools and a handler
  "tools": [
    {
      "name": "my_tool",
      "description": "What this tool does",
      "parameters": {
        "type": "object",
        "properties": {
          "query": { "type": "string", "description": "Search query" }
        },
        "required": ["query"]
      },
      "tags": ["search"],
      "permissions": ["network"],
      "timeout": 10000
    }
  ],
  "toolHandler": "handler.js",
  "toolConfig": { "handlerType": "javascript" },

  // Optional: contribute data to the Morning Briefing
  "briefingSources": [
    {
      "name": "My Notifications",
      "tool": "my_tool",
      "args": { "query": "notifications" },
      "priority": 5
    }
  ]
}

Server Configuration

FieldTypeReqNotes
typestringyesUnique server type identifier
portnumberyesMust not conflict with existing plugins
labelstringyesDisplay name
runtimestringno'python' | 'binary' | 'node'
packagesstring[]noPip packages, always pin versions
scriptNamestringnoFilename for the server script
healthCheckstringnoHealth endpoint path (default: /)

Conventions

Ports

Pick a unique port that doesn't conflict. Current allocations: 3000 (Next.js), 8880 (Kokoro TTS), 8891 (KokoClone), 9000 (Whisper STT).

IDs

Always kebab-case. Plugin ID, server type, and directory name should be consistent.

Python Servers

  • Accept port as sys.argv[1]
  • Expose GET / health endpoint returning JSON
  • Set X-OwnTerm-Server: <serverType> response header
  • Print startup with [serverType] prefix, flush=True
  • Bind to 127.0.0.1 only (never 0.0.0.0)

Pip Packages

Always pin versions (e.g., flask==3.1.0).

Tool Handlers

Tool plugins export an execute(toolName, args) function in their handler file. Use execFile (not exec) for spawning external processes to prevent shell injection.

How to Use Description

When submitting a plugin, include a howToUse field in your submission. This is a plain-English guide shown to users on the plugin directory.

Briefing Sources

Plugins can contribute data to the Morning Briefing by declaring a briefingSources array in their manifest. Each source specifies a tool name, arguments, and a priority (lower = more important).

Plugin Lifecycle

  1. OwnTerm scans ~/.ownterm/plugins/ for manifest.json files on startup
  2. Manifests are validated and registered into the appropriate registries
  3. When a server plugin is needed: lifecycle.ensureServer(type) checks if already running
  4. If not installed: creates venv, pip installs packages, writes script to disk
  5. Process manager spawns the server and writes PID to ~/.ownterm/pids/
  6. Health check polls the health endpoint (60s timeout)
  7. Plugin state persists in ~/.ownterm/plugin-state.json

Ready to build?

Check out the built-in plugins as reference, then submit yours to the directory.