feat(core): initialize project skeleton with Electron + React + TypeScript
Set up the complete project foundation for ClawX, a graphical AI assistant: - Electron main process with IPC handlers, menu, tray, and gateway management - React renderer with routing, layout components, and page scaffolding - Zustand state management for gateway, settings, channels, skills, chat, and cron - shadcn/ui components with Tailwind CSS and CSS variable theming - Build tooling with Vite, electron-builder, and TypeScript configuration - Testing setup with Vitest and Playwright - Development configurations (ESLint, Prettier, gitignore, env example)
This commit is contained in:
159
electron/preload/index.ts
Normal file
159
electron/preload/index.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Preload Script
|
||||
* Exposes safe APIs to the renderer process via contextBridge
|
||||
*/
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
/**
|
||||
* IPC renderer methods exposed to the renderer process
|
||||
*/
|
||||
const electronAPI = {
|
||||
/**
|
||||
* IPC invoke (request-response pattern)
|
||||
*/
|
||||
ipcRenderer: {
|
||||
invoke: (channel: string, ...args: unknown[]) => {
|
||||
const validChannels = [
|
||||
// Gateway
|
||||
'gateway:status',
|
||||
'gateway:start',
|
||||
'gateway:stop',
|
||||
'gateway:restart',
|
||||
'gateway:rpc',
|
||||
// Shell
|
||||
'shell:openExternal',
|
||||
'shell:showItemInFolder',
|
||||
'shell:openPath',
|
||||
// Dialog
|
||||
'dialog:open',
|
||||
'dialog:save',
|
||||
'dialog:message',
|
||||
// App
|
||||
'app:version',
|
||||
'app:name',
|
||||
'app:getPath',
|
||||
'app:platform',
|
||||
'app:quit',
|
||||
'app:relaunch',
|
||||
// Settings
|
||||
'settings:get',
|
||||
'settings:set',
|
||||
'settings:getAll',
|
||||
'settings:reset',
|
||||
// Update
|
||||
'update:check',
|
||||
'update:download',
|
||||
'update:install',
|
||||
'update:getStatus',
|
||||
// Env
|
||||
'env:getConfig',
|
||||
'env:setApiKey',
|
||||
'env:deleteApiKey',
|
||||
// Provider
|
||||
'provider:validateKey',
|
||||
// Cron
|
||||
'cron:list',
|
||||
'cron:create',
|
||||
'cron:update',
|
||||
'cron:delete',
|
||||
'cron:toggle',
|
||||
'cron:trigger',
|
||||
];
|
||||
|
||||
if (validChannels.includes(channel)) {
|
||||
return ipcRenderer.invoke(channel, ...args);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid IPC channel: ${channel}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listen for events from main process
|
||||
*/
|
||||
on: (channel: string, callback: (...args: unknown[]) => void) => {
|
||||
const validChannels = [
|
||||
'gateway:status-changed',
|
||||
'gateway:message',
|
||||
'gateway:exit',
|
||||
'gateway:error',
|
||||
'navigate',
|
||||
'update:available',
|
||||
'update:downloaded',
|
||||
'update:status',
|
||||
'cron:updated',
|
||||
];
|
||||
|
||||
if (validChannels.includes(channel)) {
|
||||
// Wrap the callback to strip the event
|
||||
const subscription = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => {
|
||||
callback(...args);
|
||||
};
|
||||
ipcRenderer.on(channel, subscription);
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => {
|
||||
ipcRenderer.removeListener(channel, subscription);
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Invalid IPC channel: ${channel}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listen for a single event from main process
|
||||
*/
|
||||
once: (channel: string, callback: (...args: unknown[]) => void) => {
|
||||
const validChannels = [
|
||||
'gateway:status-changed',
|
||||
'gateway:message',
|
||||
'gateway:exit',
|
||||
'gateway:error',
|
||||
'navigate',
|
||||
'update:available',
|
||||
'update:downloaded',
|
||||
'update:status',
|
||||
];
|
||||
|
||||
if (validChannels.includes(channel)) {
|
||||
ipcRenderer.once(channel, (_event, ...args) => callback(...args));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid IPC channel: ${channel}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all listeners for a channel
|
||||
*/
|
||||
off: (channel: string, callback?: (...args: unknown[]) => void) => {
|
||||
if (callback) {
|
||||
ipcRenderer.removeListener(channel, callback as any);
|
||||
} else {
|
||||
ipcRenderer.removeAllListeners(channel);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Open external URL in default browser
|
||||
*/
|
||||
openExternal: (url: string) => {
|
||||
return ipcRenderer.invoke('shell:openExternal', url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current platform
|
||||
*/
|
||||
platform: process.platform,
|
||||
|
||||
/**
|
||||
* Check if running in development
|
||||
*/
|
||||
isDev: process.env.NODE_ENV === 'development' || !!process.env.VITE_DEV_SERVER_URL,
|
||||
};
|
||||
|
||||
// Expose the API to the renderer process
|
||||
contextBridge.exposeInMainWorld('electron', electronAPI);
|
||||
|
||||
// Type declarations for the renderer process
|
||||
export type ElectronAPI = typeof electronAPI;
|
||||
Reference in New Issue
Block a user