원클릭으로
electron
// Electron patterns for building secure, cross-platform desktop applications. Trigger: When building desktop apps, working with Electron main/renderer processes, IPC communication, native integrations, packaging, or auto-updates.
// Electron patterns for building secure, cross-platform desktop applications. Trigger: When building desktop apps, working with Electron main/renderer processes, IPC communication, native integrations, packaging, or auto-updates.
Use this skill when creating, editing, debugging, reviewing, or documenting Standard Agents or AgentBuilder projects. Apply it for work on agents, prompts, models, tools, hooks, threads, APIs, subagents, provider setup, model selection, environment variables, and spec-aligned architecture, even if the user only says things like "build an agent", "write a prompt", "choose a model", or "fix my AgentBuilder app" without explicitly naming Standard Agents.
Writes and reviews semantic, accessible HTML and template markup that stays readable and low-noise. Use when creating or refactoring HTML or Svelte templates, cleaning up div soup, choosing better elements, improving form markup, fixing heading or landmark structure, or replacing custom controls with native HTML.
Master typographer specializing in font pairing, typographic hierarchy, OpenType features, variable fonts, and performance-optimized web typography. Use for font selection, type scales, web font optimization, and typographic systems. Activate on "typography", "font pairing", "type scale", "variable fonts", "web fonts", "OpenType", "font loading". NOT for logo design, icon fonts, general CSS styling, or image-based typography.
Designs or reviews user interfaces that are self-evident, low-friction, and easy to understand with minimal explanation. Use when simplifying UI text, removing helper copy, improving affordances, tightening hierarchy, making forms more obvious, or evaluating whether an interface works without instructions.
Use when the user asks to create, generate, write, or apply a Changesets release note/version bump from current code changes (for example "make a changeset", "add a changeset", "apply changesets", or "version packages").
Use when tracking complex multi-step tasks, creating task hierarchies, maintaining persistent task state across sessions, building backlogs, or when the user explicitly asks to "use dex" for task management. Dex provides persistent memory for AI agents with GitHub/Shortcut sync capabilities.
| name | electron |
| description | Electron patterns for building secure, cross-platform desktop applications. Trigger: When building desktop apps, working with Electron main/renderer processes, IPC communication, native integrations, packaging, or auto-updates. |
| metadata | {"author":"gentleman-programming","version":"2.0","source":"https://github.com/Gentleman-Programming/Gentleman-Skills/tree/main/community/electron","scope":"general-purpose"} |
Load this skill when:
src/
|-- main/ # Main process (Node.js)
| |-- index.ts # Entry point
| |-- ipc/ # IPC handlers
| | |-- handlers.ts
| | `-- channels.ts
| |-- services/ # Native services (store, updater)
| `-- windows/ # Window management
|-- preload/ # Safe bridge API for renderer
| `-- index.ts
|-- shared/ # Shared types/contracts
| `-- ipc.ts
`-- renderer/ # UI app (framework of choice)
|-- src/
| |-- App.(tsx|vue|svelte)
| `-- lib/
`-- index.html
Always use contextBridge for secure communication:
// preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';
import type { ElectronAPI, IpcChannels } from '../shared/ipc';
const electronAPI: ElectronAPI = {
send: <T extends keyof IpcChannels>(channel: T, data: IpcChannels[T]['request']) => {
ipcRenderer.send(channel, data);
},
invoke: <T extends keyof IpcChannels>(
channel: T,
data: IpcChannels[T]['request']
): Promise<IpcChannels[T]['response']> => {
return ipcRenderer.invoke(channel, data);
},
on: <T extends keyof IpcChannels>(
channel: T,
callback: (data: IpcChannels[T]['response']) => void
) => {
const subscription = (_: Electron.IpcRendererEvent, data: IpcChannels[T]['response']) => {
callback(data);
};
ipcRenderer.on(channel, subscription);
return () => ipcRenderer.removeListener(channel, subscription);
}
};
contextBridge.exposeInMainWorld('electron', electronAPI);
Define all channels with request/response types:
// shared/ipc.ts
export interface IpcChannels {
'app:get-version': {
request: void;
response: string;
};
'file:read': {
request: { path: string };
response: { content: string } | { error: string };
};
'file:write': {
request: { path: string; content: string };
response: { success: boolean };
};
'dialog:open-file': {
request: { filters?: Electron.FileFilter[] };
response: string | null;
};
'store:get': {
request: { key: string };
response: unknown;
};
'store:set': {
request: { key: string; value: unknown };
response: void;
};
}
export type ElectronAPI = {
send<T extends keyof IpcChannels>(channel: T, data: IpcChannels[T]['request']): void;
invoke<T extends keyof IpcChannels>(
channel: T,
data: IpcChannels[T]['request']
): Promise<IpcChannels[T]['response']>;
on<T extends keyof IpcChannels>(
channel: T,
callback: (data: IpcChannels[T]['response']) => void
): () => void;
};
declare global {
interface Window {
electron: ElectronAPI;
}
}
// main/index.ts
import { app, BrowserWindow } from 'electron';
import path from 'path';
import { registerIpcHandlers } from './ipc/handlers';
let mainWindow: BrowserWindow | null = null;
async function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true
}
});
registerIpcHandlers();
if (process.env.NODE_ENV === 'development') {
await mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools({ mode: 'detach' });
} else {
await mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
// main/ipc/handlers.ts
import { app, dialog, ipcMain } from 'electron';
import fs from 'node:fs/promises';
import Store from 'electron-store';
const store = new Store();
export function registerIpcHandlers() {
ipcMain.handle('app:get-version', () => app.getVersion());
ipcMain.handle('file:read', async (_, { path }) => {
try {
const content = await fs.readFile(path, 'utf-8');
return { content };
} catch (error) {
return { error: (error as Error).message };
}
});
ipcMain.handle('file:write', async (_, { path, content }) => {
try {
await fs.writeFile(path, content, 'utf-8');
return { success: true };
} catch {
return { success: false };
}
});
ipcMain.handle('dialog:open-file', async (_, { filters }) => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: filters || [{ name: 'All Files', extensions: ['*'] }]
});
return result.canceled ? null : result.filePaths[0];
});
ipcMain.handle('store:get', (_, { key }) => store.get(key));
ipcMain.handle('store:set', (_, { key, value }) => {
store.set(key, value);
});
}
React hook:
// renderer/src/hooks/useIPC.ts
import { useEffect, useState } from 'react';
export function useAppVersion() {
const [version, setVersion] = useState<string | null>(null);
useEffect(() => {
window.electron.invoke('app:get-version', undefined).then(setVersion);
}, []);
return version;
}
Vue composable:
// renderer/src/composables/useAppVersion.ts
import { onMounted, ref } from 'vue';
export function useAppVersion() {
const version = ref<string | null>(null);
onMounted(async () => {
version.value = await window.electron.invoke('app:get-version', undefined);
});
return { version };
}
SvelteKit runes module:
// renderer/src/lib/electron/ipc.svelte.ts
export function createVersionQuery() {
const state = $state({ value: null as string | null, loading: false });
const run = async () => {
state.loading = true;
state.value = await window.electron.invoke('app:get-version', undefined);
state.loading = false;
};
return { state, run };
}
// main/services/updater.ts
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import type { BrowserWindow } from 'electron';
export function setupAutoUpdater(mainWindow: BrowserWindow) {
autoUpdater.logger = log;
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.on('checking-for-update', () => mainWindow.webContents.send('updater:checking'));
autoUpdater.on('update-available', (info) => mainWindow.webContents.send('updater:available', info));
autoUpdater.on('update-not-available', () => mainWindow.webContents.send('updater:not-available'));
autoUpdater.on('download-progress', (progress) => mainWindow.webContents.send('updater:progress', progress));
autoUpdater.on('update-downloaded', () => mainWindow.webContents.send('updater:downloaded'));
autoUpdater.on('error', (error) => mainWindow.webContents.send('updater:error', error.message));
setTimeout(() => {
autoUpdater.checkForUpdates();
}, 5000);
}
// main/menu.ts
import { app, BrowserWindow, Menu, shell } from 'electron';
export function createMenu(mainWindow: BrowserWindow) {
const isMac = process.platform === 'darwin';
const template: Electron.MenuItemConstructorOptions[] = [
...(isMac
? [{ label: app.name, submenu: [{ role: 'about' }, { type: 'separator' }, { role: 'quit' }] }]
: []),
{
label: 'File',
submenu: [
{
label: 'Open File',
accelerator: 'CmdOrCtrl+O',
click: () => mainWindow.webContents.send('menu:open-file')
},
{ type: 'separator' },
isMac ? { role: 'close' } : { role: 'quit' }
]
},
{
label: 'Help',
submenu: [{ label: 'Documentation', click: () => shell.openExternal('https://www.electronjs.org/docs/latest') }]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
// BAD
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
// GOOD
const winSafe = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true
}
});
// BAD
const { BrowserWindow } = require('@electron/remote');
// GOOD
const path = await window.electron.invoke('dialog:open-file', {});
// BAD
contextBridge.exposeInMainWorld('electron', { ipcRenderer });
// GOOD
contextBridge.exposeInMainWorld('electron', {
invoke: (channel: string, data: unknown) => {
const allowedChannels = ['app:get-version', 'file:read'];
if (!allowedChannels.includes(channel)) {
throw new Error(`Channel ${channel} not allowed`);
}
return ipcRenderer.invoke(channel, data);
}
});
| Task | Pattern |
|---|---|
| Create project | npm create electron-vite@latest |
| Main process file access | Use Node.js fs module in main |
| Renderer file access | IPC through preload |
| Persistent storage | electron-store in main process |
| Auto-updates | electron-updater |
| Native notifications | new Notification() in main |
| System tray | Tray class in main |
| Keyboard shortcuts | globalShortcut.register() |
| Deep linking | app.setAsDefaultProtocolClient() |
| Code signing | electron-builder config |