
Desktop
- 1.5k installs
- 81.3k repo stars
- Updated August 5, 2026
- lobehub/lobe-chat
This is a copy of desktop by lobehub - installs and ranking accrue to the original listing.
desktop is an agent skill that adds native desktop notification, filesystem, and system API capabilities to Lobe Chat or similar Electron AI chat apps via main-process controllers and IPC handlers.
About
desktop is an agent skill from lobehub/lobe-chat for developers adding native desktop capabilities to an AI-powered chat or agent application built on Electron. The architecture splits main-process Controllers handling IPC from renderer-process Service Layers and Store Actions managing UI state. Implementation steps start with creating a Controller IPC handler, wiring system APIs for filesystem and network access, and connecting renderer services that invoke native notifications and OS integrations. Reach for desktop when extending Lobe Chat or a fork with file pickers, local file reads, desktop notifications, or other main-process privileges unavailable in pure web builds. The guide uses TypeScript across main and renderer boundaries with explicit IPC contracts.
- Implements clean Main/Renderer process separation using Electron IPC
- Provides typed ControllerModule pattern with @IpcMethod decorators
- Includes System APIs access for fs, network, and native notifications
- Delivers production-ready NotificationCtr with error handling and platform checks
- 3-step implementation workflow: Controller → Service Layer → Store Actions
Desktop by the numbers
- 1,549 all-time installs (skills.sh)
- +85 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lobehub/lobe-chat --skill desktopAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.5k |
|---|---|
| repo stars | ★ 81.3k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 5, 2026 |
| Repository | lobehub/lobe-chat ↗ |
How do you add native desktop APIs to Electron chat apps?
Add native desktop notification, filesystem, and system API capabilities to their AI-powered chat or agent application.
Who is it for?
Frontend developers extending Lobe Chat or Electron-based AI apps who need filesystem, notifications, or system API access via IPC.
Skip if: Pure web-only chat deployments without Electron or Tauri desktop packaging requirements.
When should I use this skill?
A developer adds native desktop notifications, filesystem access, or system APIs to an Electron AI chat or agent application.
What you get
Main-process Controller with IPC handlers, renderer Service Layer, and Store Actions exposing native desktop capabilities.
- Main-process IPC Controller
- Renderer Service Layer
- Store Actions for desktop UI state
Files
Desktop Development Guide
Architecture Overview
LobeHub desktop is built on Electron with main-renderer architecture:
1. Main Process (apps/desktop/src/main): App lifecycle, system APIs, window management 2. Renderer Process: Reuses web code from src/ 3. Preload Scripts (apps/desktop/src/preload): Securely expose main process to renderer
Adding New Desktop Features
1. Create Controller
Location: apps/desktop/src/main/controllers/
import { ControllerModule, IpcMethod } from '@/controllers';
export default class NewFeatureCtr extends ControllerModule {
static override readonly groupName = 'newFeature';
@IpcMethod()
async doSomething(params: SomeParams): Promise<SomeResult> {
// Implementation
return { success: true };
}
}Register in apps/desktop/src/main/controllers/registry.ts.
2. Define IPC Types
Location: packages/electron-client-ipc/src/types.ts
export interface SomeParams {
/* ... */
}
export interface SomeResult {
success: boolean;
error?: string;
}3. Create Renderer Service
Location: src/services/electron/
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const newFeatureService = async (params: SomeParams) => {
return ipc.newFeature.doSomething(params);
};4. Implement Store Action
Location: src/store/
5. Add Tests
Location: apps/desktop/src/main/controllers/__tests__/
Detailed Guides
See references/ for specific topics:
- Feature implementation:
references/feature-implementation.md - Local tools workflow:
references/local-tools.md - Menu configuration:
references/menu-config.md - Window management:
references/window-management.md
Best Practices
1. Security: Validate inputs, limit exposed APIs 2. Performance: Use async methods, batch data transfers 3. UX: Add progress indicators, provide error feedback 4. Code organization: Follow existing patterns, add documentation
Desktop Feature Implementation Guide
Architecture Overview
Main Process Renderer Process
┌──────────────────┐ ┌──────────────────┐
│ Controller │◄──IPC───►│ Service Layer │
│ (IPC Handler) │ │ │
└──────────────────┘ └──────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ System APIs │ │ Store Actions │
│ (fs, network) │ │ (UI State) │
└──────────────────┘ └──────────────────┘Step-by-Step Implementation
1. Create Controller
// apps/desktop/src/main/controllers/NotificationCtr.ts
import type {
ShowDesktopNotificationParams,
DesktopNotificationResult,
} from '@lobechat/electron-client-ipc';
import { Notification } from 'electron';
import { ControllerModule, IpcMethod } from '@/controllers';
export default class NotificationCtr extends ControllerModule {
static override readonly groupName = 'notification';
@IpcMethod()
async showDesktopNotification(
params: ShowDesktopNotificationParams,
): Promise<DesktopNotificationResult> {
if (!Notification.isSupported()) {
return { error: 'Notifications not supported', success: false };
}
try {
const notification = new Notification({ body: params.body, title: params.title });
notification.show();
return { success: true };
} catch (error) {
console.error('[NotificationCtr] Failed:', error);
return { error: error instanceof Error ? error.message : 'Unknown error', success: false };
}
}
}2. Define IPC Types
// packages/electron-client-ipc/src/types.ts
export interface ShowDesktopNotificationParams {
title: string;
body: string;
}
export interface DesktopNotificationResult {
success: boolean;
error?: string;
}3. Create Service Layer
// src/services/electron/notificationService.ts
import type { ShowDesktopNotificationParams } from '@lobechat/electron-client-ipc';
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const notificationService = {
show: (params: ShowDesktopNotificationParams) => ipc.notification.showDesktopNotification(params),
};4. Implement Store Action
// src/store/.../actions.ts
showNotification: async (title: string, body: string) => {
if (!isElectron) return;
const result = await notificationService.show({ title, body });
if (!result.success) {
console.error('Notification failed:', result.error);
}
},Best Practices
1. Security: Validate inputs, limit exposed APIs 2. Performance: Use async methods for heavy operations 3. Error handling: Always return structured results 4. UX: Provide loading states and error feedback
Desktop Local Tools Implementation
Workflow Overview
1. Define tool interface (Manifest) 2. Define related types 3. Implement Store Action 4. Implement Service Layer 5. Implement Controller (IPC Handler) 6. Update Agent documentation
Step 1: Define Tool Interface (Manifest)
Location: src/tools/[tool_category]/index.ts
// src/tools/local-files/index.ts
export const LocalFilesApiName = {
RenameFile: 'renameFile',
MoveFile: 'moveFile',
} as const;
export const LocalFilesManifest = {
api: [
{
name: LocalFilesApiName.RenameFile,
description: 'Rename a local file',
parameters: {
type: 'object',
properties: {
oldPath: { type: 'string', description: 'Current file path' },
newName: { type: 'string', description: 'New file name' },
},
required: ['oldPath', 'newName'],
},
},
],
};Step 2: Define Types
// packages/electron-client-ipc/src/types.ts
export interface RenameLocalFileParams {
oldPath: string;
newName: string;
}
// src/tools/local-files/type.ts
export interface LocalRenameFileState {
success: boolean;
error?: string;
oldPath: string;
newPath: string;
}Step 3: Implement Store Action
// src/store/chat/slices/builtinTool/actions/localFile.ts
renameLocalFile: async (id: string, params: RenameLocalFileParams) => {
const { toggleLocalFileLoading, updatePluginState, internal_updateMessageContent } = get();
toggleLocalFileLoading(id, true);
try {
const result = await localFileService.renameFile(params);
if (result.success) {
updatePluginState(id, { success: true, ...result });
internal_updateMessageContent(id, JSON.stringify({ success: true }));
} else {
updatePluginState(id, { success: false, error: result.error });
internal_updateMessageContent(id, JSON.stringify({ error: result.error }));
}
return result.success;
} catch (e) {
console.error(e);
updatePluginState(id, { success: false, error: e.message });
return false;
} finally {
toggleLocalFileLoading(id, false);
}
},Step 4: Implement Service Layer
// src/services/electron/localFileService.ts
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const localFileService = {
renameFile: (params: RenameLocalFileParams) => ipc.localFiles.renameFile(params),
};Step 5: Implement Controller
// apps/desktop/src/main/controllers/LocalFileCtr.ts
import * as fs from 'fs/promises';
import * as path from 'path';
import { ControllerModule, IpcMethod } from '@/controllers';
export default class LocalFileCtr extends ControllerModule {
static override readonly groupName = 'localFiles';
@IpcMethod()
async renameFile(params: RenameLocalFileParams) {
const { oldPath, newName } = params;
const newPath = path.join(path.dirname(oldPath), newName);
try {
await fs.rename(oldPath, newPath);
return { success: true, newPath };
} catch (error) {
return { success: false, error: error.message };
}
}
}Step 6: Update Agent Documentation
Location: src/tools/[tool_category]/systemRole.ts
Add tool description to <core_capabilities> and usage guidelines to <tool_usage_guidelines>.
Desktop Menu Configuration Guide
Menu Types
1. App Menu: Top of window (macOS) or title bar (Windows/Linux) 2. Context Menu: Right-click menus 3. Tray Menu: System tray icon menus
File Structure
apps/desktop/src/main/
├── menus/
│ ├── appMenu.ts # App menu config
│ ├── contextMenu.ts # Context menu config
│ └── factory.ts # Menu factory functions
├── controllers/
│ ├── MenuCtr.ts # Menu controller
│ └── TrayMenuCtr.ts # Tray menu controllerApp Menu Configuration
// apps/desktop/src/main/menus/appMenu.ts
import { BrowserWindow, Menu, MenuItemConstructorOptions } from 'electron';
export const createAppMenu = (win: BrowserWindow) => {
const template: MenuItemConstructorOptions[] = [
{
label: 'File',
submenu: [
{
label: 'New',
accelerator: 'CmdOrCtrl+N',
click: () => {
/* ... */
},
},
{ type: 'separator' },
{ role: 'quit' },
],
},
// ...
];
return Menu.buildFromTemplate(template);
};
// Register in MenuCtr.ts
Menu.setApplicationMenu(menu);Context Menu
export const createContextMenu = () => {
const template = [
{ label: 'Copy', role: 'copy' },
{ label: 'Paste', role: 'paste' },
];
return Menu.buildFromTemplate(template);
};
// Show on right-click
const menu = createContextMenu();
menu.popup();Tray Menu
// TrayMenuCtr.ts
this.tray = new Tray(trayIconPath);
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show Window', click: this.showMainWindow },
{ type: 'separator' },
{ label: 'Quit', click: () => app.quit() },
]);
this.tray.setContextMenu(contextMenu);i18n Support
import { i18n } from '../locales';
const template = [
{
label: i18n.t('menu.file'),
submenu: [{ label: i18n.t('menu.new'), click: createNew }],
},
];Best Practices
1. Use standard roles (role: 'copy') for native behavior 2. Use CmdOrCtrl for cross-platform shortcuts 3. Use { type: 'separator' } to group related items 4. Handle platform differences with process.platform
if (process.platform === 'darwin') {
template.unshift({ role: 'appMenu' });
}Desktop Window Management Guide
Window Management Overview
1. Window creation and configuration 2. Window state management (size, position, maximize) 3. Multi-window coordination 4. Window event handling
File Structure
apps/desktop/src/main/
├── appBrowsers.ts # Core window management
├── controllers/
│ └── BrowserWindowsCtr.ts # Window controller
└── modules/
└── browserWindowManager.ts # Window manager moduleWindow Creation
export const createMainWindow = () => {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 600,
minHeight: 400,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
if (isDev) {
mainWindow.loadURL('http://localhost:3000');
} else {
mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html'));
}
return mainWindow;
};Window State Persistence
const saveWindowState = (window: BrowserWindow) => {
if (!window.isMinimized() && !window.isMaximized()) {
const [x, y] = window.getPosition();
const [width, height] = window.getSize();
settings.set('windowState', { x, y, width, height });
}
};
const restoreWindowState = (window: BrowserWindow) => {
const state = settings.get('windowState');
if (state) {
window.setBounds({ x: state.x, y: state.y, width: state.width, height: state.height });
}
};
window.on('close', () => saveWindowState(window));Multi-Window Management
export class WindowManager {
private windows: Map<string, BrowserWindow> = new Map();
createWindow(id: string, options: BrowserWindowConstructorOptions) {
const window = new BrowserWindow(options);
this.windows.set(id, window);
window.on('closed', () => this.windows.delete(id));
return window;
}
getWindow(id: string) {
return this.windows.get(id);
}
}Window IPC Controller
// apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
export default class BrowserWindowsCtr extends ControllerModule {
static override readonly groupName = 'windows';
@IpcMethod()
minimizeWindow() {
BrowserWindow.getFocusedWindow()?.minimize();
return { success: true };
}
@IpcMethod()
maximizeWindow() {
const win = BrowserWindow.getFocusedWindow();
win?.isMaximized() ? win.restore() : win?.maximize();
return { success: true };
}
}Renderer Service
// src/services/electron/windowService.ts
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const windowService = {
minimize: () => ipc.windows.minimizeWindow(),
maximize: () => ipc.windows.maximizeWindow(),
close: () => ipc.windows.closeWindow(),
};Frameless Window
const window = new BrowserWindow({
frame: false,
titleBarStyle: 'hidden',
});.titlebar {
-webkit-app-region: drag;
}
.titlebar-button {
-webkit-app-region: no-drag;
}Best Practices
1. Use show: false initially, show after content loads 2. Always set secure webPreferences 3. Handle webContents.on('crashed') for recovery 4. Clean up resources on window.on('closed')
Related skills
How it compares
Pick desktop for Electron IPC native features; use standard web frontend skills for browser-only chat UI without main-process access.
FAQ
What architecture does the desktop skill use?
The desktop skill uses Electron with main-process Controllers handling IPC, renderer Service Layers calling those handlers, and Store Actions managing UI state for notifications, filesystem, and system APIs.
Which native capabilities does desktop add?
The desktop skill adds native desktop notifications, filesystem access, and system APIs such as network operations to AI chat apps by wiring main-process controllers to renderer services.
Is desktop for web-only Lobe Chat deployments?
No. The desktop skill targets Electron-packaged Lobe Chat or similar desktop AI apps requiring main-process IPC for native OS integrations unavailable in browser-only builds.
Is Desktop safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.