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:
| Registry | Purpose |
|---|---|
| Server | Python servers (STT, TTS) |
| TTS Provider | Custom voice backends |
| Provider | Non-server LLM providers |
| Plugin Component | Lazy-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
| Field | Type | Req | Notes |
|---|---|---|---|
| type | string | yes | Unique server type identifier |
| port | number | yes | Must not conflict with existing plugins |
| label | string | yes | Display name |
| runtime | string | no | 'python' | 'binary' | 'node' |
| packages | string[] | no | Pip packages, always pin versions |
| scriptName | string | no | Filename for the server script |
| healthCheck | string | no | Health 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.1only (never0.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
- OwnTerm scans
~/.ownterm/plugins/formanifest.jsonfiles on startup - Manifests are validated and registered into the appropriate registries
- When a server plugin is needed:
lifecycle.ensureServer(type)checks if already running - If not installed: creates venv, pip installs packages, writes script to disk
- Process manager spawns the server and writes PID to
~/.ownterm/pids/ - Health check polls the health endpoint (60s timeout)
- 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.