Plugins let you add new capabilities to Termix without modifying the core. A plugin can observe terminal output, transform input, react to status changes, add toolbar buttons, and communicate between backend and frontend — all through a simple API.
Some examples of what plugins can do:
- Transcribe voice input and send it to an agent
- Copy trimmed/cleaned output to the clipboard
- Build orchestration flows across multiple agents
- Add custom UI controls to the terminal toolbar
Installing a Plugin
Each plugin is a folder inside ~/.termix/plugins/:
~/.termix/plugins/my-plugin/
index.js # backend code (required)
termix-plugin.json # manifest (optional, recommended)
client.js # frontend code (optional)
public/ # static assets (optional)
Drop the folder in, restart Termix. That’s it.
Plugins run with full Node.js access — same trust model as npm packages. Only install plugins you trust or have reviewed.
Managing Plugins
Installed plugins appear in the Plugins panel (circuit icon in the sidebar rail). If a plugin has settings, they show up as controls (toggles, dropdowns, text fields, number inputs) that you can change on the fly.
To remove a plugin, delete its folder from ~/.termix/plugins/ and restart Termix.
Creating a Plugin
The Manifest
A termix-plugin.json manifest lets you define your plugin’s identity and settings. If omitted, the folder name is used as the ID and name.
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"settings": [
{ "key": "enabled", "label": "Enable feature", "type": "toggle", "default": true },
{ "key": "mode", "label": "Mode", "type": "select", "options": ["fast", "slow"], "default": "fast" },
{ "key": "limit", "label": "Max items", "type": "number", "min": 1, "max": 100, "default": 10 },
{ "key": "apiKey", "label": "API Key", "type": "text", "default": "" }
]
}
| Field | Required | Description |
|---|
id | No | Unique plugin identifier (defaults to folder name) |
name | No | Display name in the UI (defaults to folder name) |
version | No | Version string (defaults to 0.0.0) |
settings | No | Array of user-configurable settings |
Setting Types
| Type | Control | Extra Options |
|---|
toggle | On/off switch | — |
select | Dropdown | options: array of strings or { value, label } objects |
number | Number input | min, max (optional) |
text | Text field | — |
All setting types support an optional description field for hint text below the control.
Settings are validated against the manifest — type, allowed options, and min/max are enforced automatically.
Backend API (index.js)
The backend entry point exports an init function that receives the plugin API:
module.exports = {
init(api) {
// Your plugin code here
}
};
Session Hooks
// Transform input before it reaches the terminal
// Return a string to replace the input, or nothing to leave it unchanged
api.onSessionInput((sessionId, data) => {
return data;
});
// Observe terminal output (read-only)
api.onSessionOutput((sessionId, data) => {
// data is the raw terminal output
});
// React to working/idle transitions
api.onStatusChange((sessionId, working) => {
// working: true = agent started working, false = agent went idle
});
Input hooks run as a chain — each hook’s return value becomes the input for the next hook. This lets multiple plugins transform input in sequence.
Session Info
api.getSessions(); // [{ id, name, cwd, commandId, themeId, projectId }]
api.getSession(id); // single session object, or null
Frontend Messaging
Backend and frontend communicate through namespaced messages:
// Send a message to the frontend
api.sendToFrontend('my-event', { key: 'value' });
// Receive a message from the frontend
api.onFrontendMessage('my-action', (msg) => {
// handle message
});
Messages are automatically namespaced as plugin.<id>.<event> — no collision between plugins.
api.addToolbarAction({
id: 'do-thing',
title: 'Do Thing',
icon: '<svg>...</svg>', // inline SVG, 16x16
});
Settings
api.getSetting('mode'); // current value for a single key
api.getSettings(); // all settings as { key: value }
api.onSettingsChange((key, value) => {
// called when the user changes a setting in the UI
});
Utilities
api.log('message'); // log with [plugin:my-plugin] prefix
api.pluginId; // this plugin's ID
api.pluginDir; // absolute path to this plugin's folder
api.version; // API version (currently 1)
api.onShutdown(() => {
// cleanup when Termix shuts down
});
Frontend API (client.js)
The optional frontend entry point is an ES module:
export function init(api) {
// Send a message to the backend
api.send('my-action', { key: 'value' });
// Receive a message from the backend
api.onMessage('my-event', (msg) => {
// msg is the full message object including type
});
// Add a button to the terminal toolbar
api.addToolbarButton({
title: 'Do Thing',
icon: '<svg>...</svg>',
onClick: () => { /* handle click */ }
});
// Session helpers
api.getActiveSessionId(); // current active session ID
api.writeToSession(id, text); // write text to a session's terminal
}
Static Assets
Files in the public/ subdirectory are served at /plugins/<id>/<filename>. Use this for images, CSS, or additional scripts your plugin needs.
Example: Simple Logger Plugin
A minimal plugin that logs when agents start and stop working:
~/.termix/plugins/work-logger/
termix-plugin.json
index.js
termix-plugin.json:
{
"id": "work-logger",
"name": "Work Logger",
"version": "1.0.0"
}
index.js:
module.exports = {
init(api) {
api.onStatusChange((sessionId, working) => {
const session = api.getSession(sessionId);
const name = session?.name || sessionId;
api.log(`${name} is now ${working ? 'working' : 'idle'}`);
});
api.onShutdown(() => {
api.log('shutting down');
});
}
};
Error Handling
Plugin errors are caught and logged — one plugin can’t crash Termix or affect other plugins. If your plugin throws during init, it’s unloaded and its hooks are removed.