Plugins let you add new capabilities to CliDeck 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.
Plugins are a big deal — they let each user shape CliDeck into something uniquely their own. Build orchestration flows, add voice control, integrate with external services, or create custom UI controls. The plugin API is the same one the bundled plugins use, so you have full access to everything CliDeck can do.
Bundled Plugins
CliDeck ships with two plugins out of the box. They’re automatically installed into ~/.clideck/plugins/ on first run.
Trim Clip
A clipboard utility that cleans up copied text. Click the scissors icon in the terminal toolbar to trim trailing whitespace from every line in your clipboard. Useful when copying code from terminal output that has extra spaces.
| Setting | Description | Default |
|---|
| Enabled | Show the toolbar button | On |
| Trim leading whitespace | Also remove leading spaces (off preserves indentation) | Off |
Speak to your agents instead of typing. Press a hotkey (default: F4) to start recording, press again to stop. Your speech is transcribed and typed into the active terminal session.
Two transcription backends are available:
| Backend | Description | Requirements |
|---|
| OpenAI (default) | Uses OpenAI’s Whisper API (remote) | OpenAI API key |
| Local | Runs Whisper via MLX on your machine | macOS with Apple Silicon, Python 3 |
| Setting | Description | Default |
|---|
| Enabled | Enable voice input | Off |
| ASR Backend | OpenAI (remote) or Local (MLX) | OpenAI |
| OpenAI API Key | Required for OpenAI backend | — |
| Language | Transcription language (or auto-detect) | Auto |
| Record Key | Hotkey to start/stop recording | F4 |
| Replacements File | Path to a text replacements file for post-processing | — |
The replacements file uses a simple format — one rule per line:
wrong phrase => correct phrase
another mistake => fix | all
The | all flag makes the replacement case-insensitive.
Voice Input requires microphone access. Your browser will ask for permission the first time you record.
Installing Custom Plugins
Each plugin is a folder inside ~/.clideck/plugins/:
~/.clideck/plugins/my-plugin/
index.js # backend code (required)
clideck-plugin.json # manifest (optional, recommended)
client.js # frontend code (optional)
public/ # static assets (optional)
Drop the folder in, restart CliDeck. 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 ~/.clideck/plugins/ and restart CliDeck.
Creating a Plugin
The Manifest
A clideck-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 CliDeck 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
// Toast notifications
api.toast('Something happened', { type: 'success' });
}
Toast Notifications
Show toast notifications from your plugin. Returns an object with a dismiss() method.
| Option | Type | Default | Description |
|---|
type | string | 'info' | 'info', 'success', 'warn', or 'error' |
duration | number | 3000 | Auto-dismiss in ms. 0 = persistent (manual dismiss only) |
id | string | — | Dedupe key. A new toast with the same id replaces the previous one |
html | boolean | false | If true, message is rendered as HTML |
// Transient success
api.toast('Model loaded', { type: 'success' });
// Persistent progress (dismiss when done)
const t = api.toast('Installing dependencies...', { type: 'info', duration: 0, id: 'setup' });
// later...
t.dismiss();
// Replace previous toast with same id
api.toast('Dependencies installed', { type: 'success', id: 'setup' });
Multiple toasts from different plugins stack automatically without overlapping.
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:
~/.clideck/plugins/work-logger/
clideck-plugin.json
index.js
clideck-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 CliDeck or affect other plugins. If your plugin throws during init, it’s unloaded and its hooks are removed.