How to Customize the OmniRoute Electron Desktop App with Plugins and Theming
The OmniRoute Electron desktop app supports deep customization through a sandboxed plugin framework that intercepts the request pipeline and by modifying static assets in the electron/assets/ directory to change icons, tray appearance, and window styling.
The diegosouzapw/OmniRoute repository wraps a Next.js frontend in a secure Electron shell, providing multiple extension points. Customizing the OmniRoute Electron desktop app requires understanding both the plugin system—which runs isolated code in worker threads—and the asset layer, where you replace icons and window configurations to alter the visual identity.
Architecture Overview
The application separates concerns across three layers to maintain security while enabling extensibility.
electron/main.js– The main process launches the Next.js server, creates theBrowserWindow, and manages the system tray. It implements awaitForServer()polling mechanism (usingstdio: 'pipe'for log capture) and enforces a strict Content-Security-Policy before exposing the UI.electron/preload.js– Runs in a secure preload context with Node integration disabled and context isolation enabled. It exposes only whitelisted IPC methods (safeInvoke,safeSend,safeOn) to the renderer.src/shared/hooks/useElectron.ts– React hooks consume IPC events viauseSyncExternalStore, enabling zero-render UI updates for states like server status and Electron environment detection.
Extending Functionality with Plugins
The plugin engine discovers extensions from ~/.omniroute/plugins/ (or a custom path set via OMNIROUTE_PLUGIN_PATH), validates manifests using src/lib/plugins/manifest.ts, and executes logic inside isolated worker threads (src/lib/plugins/pluginWorker.ts). Plugins can register lifecycle hooks (onRequest, onResponse, onError) to transform traffic between the UI and AI backends.
Creating a Type-Safe Plugin
Use the definePlugin helper from src/lib/plugins/sdk.ts to ensure type safety and register hook functions. Each plugin requires a plugin.json manifest in its root directory for validation and integrity checking.
// ~/.omniroute/plugins/welcome-banner/index.ts
import { definePlugin } from "omniroute/plugins/sdk";
export default definePlugin({
name: "welcome-banner",
priority: 10,
onResponse: async (ctx, response) => {
// Prepend a friendly banner to every chat response
if (typeof response === "object" && "choices" in response) {
(response as any).choices[0].message.content =
"👋 Welcome! " + (response as any).choices[0].message.content;
}
return response;
},
});
The manifest (plugin.json) must include fields such as name, version, main, and optional integrity for tamper detection:
{
"name": "welcome-banner",
"version": "1.0.0",
"main": "index.ts",
"integrity": "sha256-abc123..."
}
Installing and Activating Plugins
Install plugins via the CLI or programmatically through the /api/plugins/install endpoint. The pluginManager.loadAll() function initializes plugins on startup, while watcher/startWatching enables hot-reloading when files change in the plugin directory.
# Install from a local folder
omniroute plugin install ~/.omniroute/plugins/welcome-banner
# Activate the plugin
omniroute plugin activate welcome-banner
Controlling Plugin Permissions
By default, plugins cannot spawn child processes. Set the environment variable OMNIROUTE_PLUGINS_ALLOW_EXEC=1 to grant the exec permission, which is required for plugins that need to run system commands. Without this flag, the sandbox terminates any process-spawning attempts for security.
Theming the Desktop Application
Visual customization involves replacing static assets and modifying window properties in the main process.
Replacing Application Icons
Store custom icons in electron/assets/ using the exact filenames the packager expects:
icon.ico– Windows application icon (256 × 256 recommended)icon.icns– macOS icon bundleicon.png– Linux/general-purpose icon (512 × 512 recommended)tray-icon.png– System tray icon (16 × 16 or 32 × 32 with transparency)
Rebuild the application after replacing these files to bundle the new assets.
Modifying Window Properties
Customize the window chrome and behavior by editing electron/main.js. For example, use titleBarStyle: 'hiddenInset' on macOS to create a transparent title bar, or adjust the backgroundColor property to change the splash screen color during load.
// electron/main.js
win = new BrowserWindow({
title: "My Custom OmniRoute",
titleBarStyle: 'hiddenInset',
backgroundColor: '#1a1a1a',
// ...other options
});
Runtime UI Customization
The React frontend can trigger main-process actions via the whitelisted IPC channels. Use the useElectron hooks to safely invoke main-process methods from the renderer.
import { useElectronAppInfo } from "src/shared/hooks/useElectron";
function ChangeTrayIconButton() {
const setIcon = async () => {
await window.electron.safeInvoke(
"set-tray-icon",
"assets/custom-tray.png"
);
};
return <button onClick={setIcon}>Use Custom Tray Icon</button>;
}
Since the Electron wrapper loads the built standalone Next.js app, any CSS changes (Tailwind or global stylesheets) in the frontend source automatically reflect in the desktop interface after rebuilding.
Advanced Plugin Examples
Logging Response Metadata
Create a plugin that monitors traffic without modifying it:
// src/lib/plugins/example/log-response.ts
import { definePlugin } from "omniroute/plugins/sdk";
export default definePlugin({
name: "log-response",
onResponse: async (ctx, response) => {
console.log(`[${ctx.requestId}] Response size: ${JSON.stringify(response).length}`);
return response; // Must return the (possibly modified) response
},
});
Marketplace Integration
Browse and install remote plugins using the marketplace client defined in src/lib/plugins/marketplace.ts, which handles searching, versioning, and secure download verification before placing files in the plugin directory.
Summary
- Plugin Development – Use
definePluginfromsrc/lib/plugins/sdk.tsto create type-safe extensions that hook intoonRequest,onResponse, oronErrorevents within isolated worker threads. - Plugin Installation – Place plugins in
~/.omniroute/plugins/or use the CLI/API installer; control dangerous permissions viaOMNIROUTE_PLUGINS_ALLOW_EXEC. - Asset Theming – Replace icons in
electron/assets/(icon.ico,icon.icns,icon.png,tray-icon.png) and modifyelectron/main.jswindow options to rebrand the application. - Security Model – The preload script (
electron/preload.js) enforces context isolation, exposing onlysafeInvoke,safeSend, andsafeOnto the renderer, while the plugin sandbox prevents unauthorized system access unless explicitly permitted.
Frequently Asked Questions
Where should I place custom plugins for the OmniRoute Electron desktop app?
Store plugins in the ~/.omniroute/plugins/ directory, or define a custom path using the OMNIROUTE_PLUGIN_PATH environment variable. Each plugin folder must contain a plugin.json manifest and the main entry file referenced by the main field.
Is it safe to install third-party plugins?
Plugins execute inside worker threads (src/lib/plugins/pluginWorker.ts) with limited privileges. By default, they cannot access Node.js APIs or spawn processes. Set OMNIROUTE_PLUGINS_ALLOW_EXEC=1 only if a specific plugin requires child process access, and verify the integrity hash in the manifest (src/lib/plugins/manifest.ts) to prevent tampering.
Can I change the tray icon without rebuilding the application?
Yes. Use the safeInvoke IPC method exposed by electron/preload.js to send a 'set-tray-icon' message from the renderer process, passing the path to your new icon asset. The React hook useElectronAppInfo from src/shared/hooks/useElectron.ts provides the interface for these calls.
Does OmniRoute support hot-reloading for plugin development?
Yes. The plugin manager uses watcher/startWatching to monitor the plugin directory for file changes. During development, modifications to plugin code automatically trigger reloads without requiring a full application restart, though you must still re-activate the plugin if you change the manifest metadata.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →