Skip to main content
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": "" }
  ]
}
FieldRequiredDescription
idNoUnique plugin identifier (defaults to folder name)
nameNoDisplay name in the UI (defaults to folder name)
versionNoVersion string (defaults to 0.0.0)
settingsNoArray of user-configurable settings

Setting Types

TypeControlExtra Options
toggleOn/off switch
selectDropdownoptions: array of strings or { value, label } objects
numberNumber inputmin, max (optional)
textText 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.

Toolbar Actions

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.