import { contextBridge, ipcRenderer } from 'electron'; // Expose protected methods that allow the renderer process to use // ipcRenderer without exposing the entire object contextBridge.exposeInMainWorld('electronAPI', { // Projects projects: { create: (data: { name: string; description?: string; slug?: string; dataPath?: string }) => ipcRenderer.invoke('projects:create', data), update: (id: string, data: unknown) => ipcRenderer.invoke('projects:update', id, data), delete: (id: string) => ipcRenderer.invoke('projects:delete', id), deleteWithData: (id: string) => ipcRenderer.invoke('projects:deleteWithData', id), get: (id: string) => ipcRenderer.invoke('projects:get', id), getAll: () => ipcRenderer.invoke('projects:getAll'), getActive: () => ipcRenderer.invoke('projects:getActive'), setActive: (id: string) => ipcRenderer.invoke('projects:setActive', id), }, // Posts posts: { create: (data: unknown) => ipcRenderer.invoke('posts:create', data), update: (id: string, data: unknown) => ipcRenderer.invoke('posts:update', id, data), delete: (id: string) => ipcRenderer.invoke('posts:delete', id), get: (id: string) => ipcRenderer.invoke('posts:get', id), getAll: (options?: { limit?: number; offset?: number }) => ipcRenderer.invoke('posts:getAll', options), getByStatus: (status: string) => ipcRenderer.invoke('posts:getByStatus', status), publish: (id: string) => ipcRenderer.invoke('posts:publish', id), discard: (id: string) => ipcRenderer.invoke('posts:discard', id), hasPublishedVersion: (id: string) => ipcRenderer.invoke('posts:hasPublishedVersion', id), rebuildFromFiles: () => ipcRenderer.invoke('posts:rebuildFromFiles'), reindexText: () => ipcRenderer.invoke('posts:reindexText'), search: (query: string) => ipcRenderer.invoke('posts:search', query), filter: (filter: unknown) => ipcRenderer.invoke('posts:filter', filter), getTags: () => ipcRenderer.invoke('posts:getTags'), getCategories: () => ipcRenderer.invoke('posts:getCategories'), getByYearMonth: () => ipcRenderer.invoke('posts:getByYearMonth'), getTagsWithCounts: () => ipcRenderer.invoke('posts:getTagsWithCounts'), getCategoriesWithCounts: () => ipcRenderer.invoke('posts:getCategoriesWithCounts'), getDashboardStats: () => ipcRenderer.invoke('posts:getDashboardStats'), getLinksTo: (id: string) => ipcRenderer.invoke('posts:getLinksTo', id), getLinkedBy: (id: string) => ipcRenderer.invoke('posts:getLinkedBy', id), rebuildLinks: () => ipcRenderer.invoke('posts:rebuildLinks'), isSlugAvailable: (slug: string, excludePostId?: string) => ipcRenderer.invoke('posts:isSlugAvailable', slug, excludePostId), generateUniqueSlug: (title: string, excludePostId?: string) => ipcRenderer.invoke('posts:generateUniqueSlug', title, excludePostId), }, // Media media: { import: (sourcePath: string, metadata?: unknown) => ipcRenderer.invoke('media:import', sourcePath, metadata), importDialog: () => ipcRenderer.invoke('media:importDialog'), update: (id: string, data: unknown) => ipcRenderer.invoke('media:update', id, data), replaceFile: (id: string, newSourcePath: string) => ipcRenderer.invoke('media:replaceFile', id, newSourcePath), replaceFileDialog: (id: string) => ipcRenderer.invoke('media:replaceFileDialog', id), delete: (id: string) => ipcRenderer.invoke('media:delete', id), get: (id: string) => ipcRenderer.invoke('media:get', id), getUrl: (id: string) => ipcRenderer.invoke('media:getUrl', id), getFilePath: (id: string) => ipcRenderer.invoke('media:getFilePath', id), getAll: () => ipcRenderer.invoke('media:getAll'), filter: (filter: unknown) => ipcRenderer.invoke('media:filter', filter), search: (query: string) => ipcRenderer.invoke('media:search', query), getByYearMonth: () => ipcRenderer.invoke('media:getByYearMonth'), getTags: () => ipcRenderer.invoke('media:getTags'), getTagsWithCounts: () => ipcRenderer.invoke('media:getTagsWithCounts'), rebuildFromFiles: () => ipcRenderer.invoke('media:rebuildFromFiles'), reindexText: () => ipcRenderer.invoke('media:reindexText'), getThumbnail: (id: string, size?: 'small' | 'medium' | 'large') => ipcRenderer.invoke('media:getThumbnail', id, size), regenerateThumbnails: (id: string) => ipcRenderer.invoke('media:regenerateThumbnails', id), regenerateMissingThumbnails: () => ipcRenderer.invoke('media:regenerateMissingThumbnails'), }, // Post-Media Links postMedia: { link: (postId: string, mediaId: string) => ipcRenderer.invoke('postMedia:link', postId, mediaId), unlink: (postId: string, mediaId: string) => ipcRenderer.invoke('postMedia:unlink', postId, mediaId), linkMany: (postId: string, mediaIds: string[]) => ipcRenderer.invoke('postMedia:linkMany', postId, mediaIds), unlinkMany: (postId: string, mediaIds: string[]) => ipcRenderer.invoke('postMedia:unlinkMany', postId, mediaIds), getForPost: (postId: string) => ipcRenderer.invoke('postMedia:getForPost', postId), getForMedia: (mediaId: string) => ipcRenderer.invoke('postMedia:getForMedia', mediaId), getMediaDataForPost: (postId: string) => ipcRenderer.invoke('postMedia:getMediaDataForPost', postId), reorder: (postId: string, mediaIds: string[]) => ipcRenderer.invoke('postMedia:reorder', postId, mediaIds), isLinked: (postId: string, mediaId: string) => ipcRenderer.invoke('postMedia:isLinked', postId, mediaId), import: (postId: string, filePath: string) => ipcRenderer.invoke('postMedia:import', postId, filePath), rebuild: () => ipcRenderer.invoke('postMedia:rebuild'), }, // Tasks tasks: { getAll: () => ipcRenderer.invoke('tasks:getAll'), getRunning: () => ipcRenderer.invoke('tasks:getRunning'), cancel: (taskId: string) => ipcRenderer.invoke('tasks:cancel', taskId), clearCompleted: () => ipcRenderer.invoke('tasks:clearCompleted'), }, // App app: { getDataPaths: () => ipcRenderer.invoke('app:getDataPaths'), openFolder: (folderPath: string) => ipcRenderer.invoke('app:openFolder', folderPath), showItemInFolder: (itemPath: string) => ipcRenderer.invoke('app:showItemInFolder', itemPath), selectFolder: (title?: string) => ipcRenderer.invoke('app:selectFolder', title), getDefaultProjectPath: (projectId: string) => ipcRenderer.invoke('app:getDefaultProjectPath', projectId), readProjectMetadata: (folderPath: string) => ipcRenderer.invoke('app:readProjectMetadata', folderPath), }, // Meta (tags, categories, and project metadata) meta: { getTags: () => ipcRenderer.invoke('meta:getTags'), getCategories: () => ipcRenderer.invoke('meta:getCategories'), addTag: (tag: string) => ipcRenderer.invoke('meta:addTag', tag), removeTag: (tag: string) => ipcRenderer.invoke('meta:removeTag', tag), addCategory: (category: string) => ipcRenderer.invoke('meta:addCategory', category), removeCategory: (category: string) => ipcRenderer.invoke('meta:removeCategory', category), syncOnStartup: () => ipcRenderer.invoke('meta:syncOnStartup'), getProjectMetadata: () => ipcRenderer.invoke('meta:getProjectMetadata'), setProjectMetadata: (metadata: { name: string; description?: string }) => ipcRenderer.invoke('meta:setProjectMetadata', metadata), updateProjectMetadata: (updates: { name?: string; description?: string; dataPath?: string; mainLanguage?: string }) => ipcRenderer.invoke('meta:updateProjectMetadata', updates), }, // Tag Management (advanced tag operations) tags: { getAll: () => ipcRenderer.invoke('tags:getAll'), getWithCounts: () => ipcRenderer.invoke('tags:getWithCounts'), get: (id: string) => ipcRenderer.invoke('tags:get', id), getByName: (name: string) => ipcRenderer.invoke('tags:getByName', name), create: (data: { name: string; color?: string }) => ipcRenderer.invoke('tags:create', data), update: (id: string, data: { name?: string; color?: string | null }) => ipcRenderer.invoke('tags:update', id, data), delete: (id: string) => ipcRenderer.invoke('tags:delete', id), merge: (sourceTagIds: string[], targetTagId: string) => ipcRenderer.invoke('tags:merge', sourceTagIds, targetTagId), rename: (id: string, newName: string) => ipcRenderer.invoke('tags:rename', id, newName), getPostsWithTag: (tagId: string) => ipcRenderer.invoke('tags:getPostsWithTag', tagId), syncFromPosts: () => ipcRenderer.invoke('tags:syncFromPosts'), }, // Import Analysis import: { selectAndAnalyze: (uploadsFolder?: string) => ipcRenderer.invoke('import:selectAndAnalyze', uploadsFolder), analyzeFile: (filePath: string, uploadsFolder?: string) => ipcRenderer.invoke('import:analyzeFile', filePath, uploadsFolder), selectUploadsFolder: () => ipcRenderer.invoke('import:selectUploadsFolder'), execute: (reportJson: string, uploadsFolder?: string) => ipcRenderer.invoke('import:execute', reportJson, uploadsFolder), onProgress: (callback: (data: { step: string; detail?: string }) => void) => { const subscription = (_event: Electron.IpcRendererEvent, data: { step: string; detail?: string }) => callback(data); ipcRenderer.on('import:progress', subscription); return () => ipcRenderer.removeListener('import:progress', subscription); }, onExecutionProgress: (callback: (data: { taskId: string; phase: string; current: number; total: number; detail?: string; eta?: number; }) => void) => { const subscription = (_event: Electron.IpcRendererEvent, data: { taskId: string; phase: string; current: number; total: number; detail?: string; eta?: number; }) => callback(data); ipcRenderer.on('import:executionProgress', subscription); return () => ipcRenderer.removeListener('import:executionProgress', subscription); }, onComplete: (callback: (data: { taskId: string; success: boolean; posts: { imported: number; skipped: number; errors: number }; media: { imported: number; skipped: number; errors: number }; pages: { imported: number; skipped: number; errors: number }; tags: { created: number; skipped: number }; }) => void) => { const subscription = (_event: Electron.IpcRendererEvent, data: { taskId: string; success: boolean; posts: { imported: number; skipped: number; errors: number }; media: { imported: number; skipped: number; errors: number }; pages: { imported: number; skipped: number; errors: number }; tags: { created: number; skipped: number }; }) => callback(data); ipcRenderer.on('import:complete', subscription); return () => ipcRenderer.removeListener('import:complete', subscription); }, }, // Import Definition CRUD importDefinitions: { create: (name?: string) => ipcRenderer.invoke('importDefinitions:create', name), get: (id: string) => ipcRenderer.invoke('importDefinitions:get', id), getAll: () => ipcRenderer.invoke('importDefinitions:getAll'), update: (id: string, updates: unknown) => ipcRenderer.invoke('importDefinitions:update', id, updates), delete: (id: string) => ipcRenderer.invoke('importDefinitions:delete', id), onNameUpdated: (callback: (data: { definitionId: string; name: string }) => void) => { const subscription = (_event: Electron.IpcRendererEvent, data: { definitionId: string; name: string }) => callback(data); ipcRenderer.on('importDefinition-name-updated', subscription); return () => ipcRenderer.removeListener('importDefinition-name-updated', subscription); }, }, // AI Chat (OpenCode Zen API integration) chat: { // API Key Management checkReady: () => ipcRenderer.invoke('chat:checkReady'), validateApiKey: (apiKey: string) => ipcRenderer.invoke('chat:validateApiKey', apiKey), setApiKey: (apiKey: string) => ipcRenderer.invoke('chat:setApiKey', apiKey), getApiKey: () => ipcRenderer.invoke('chat:getApiKey'), // Settings getAvailableModels: () => ipcRenderer.invoke('chat:getAvailableModels'), setDefaultModel: (modelId: string) => ipcRenderer.invoke('chat:setDefaultModel', modelId), getSystemPrompt: () => ipcRenderer.invoke('chat:getSystemPrompt'), setSystemPrompt: (prompt: string) => ipcRenderer.invoke('chat:setSystemPrompt', prompt), // Conversations getConversations: () => ipcRenderer.invoke('chat:getConversations'), createConversation: (title?: string, model?: string) => ipcRenderer.invoke('chat:createConversation', title, model), getConversation: (id: string) => ipcRenderer.invoke('chat:getConversation', id), updateConversation: (id: string, updates: { title?: string; model?: string }) => ipcRenderer.invoke('chat:updateConversation', id, updates), deleteConversation: (id: string) => ipcRenderer.invoke('chat:deleteConversation', id), // Messaging sendMessage: (conversationId: string, message: string) => ipcRenderer.invoke('chat:sendMessage', conversationId, message), abortMessage: (conversationId: string) => ipcRenderer.invoke('chat:abortMessage', conversationId), getHistory: (conversationId: string) => ipcRenderer.invoke('chat:getHistory', conversationId), clearMessages: (conversationId: string) => ipcRenderer.invoke('chat:clearMessages', conversationId), setConversationModel: (conversationId: string, modelId: string) => ipcRenderer.invoke('chat:setConversationModel', conversationId, modelId), // Taxonomy Analysis analyzeTaxonomy: (categories: Array<{ name: string; slug: string; existsInProject: boolean }>, tags: Array<{ name: string; slug: string; existsInProject: boolean }>, modelId: string) => ipcRenderer.invoke('chat:analyzeTaxonomy', categories, tags, modelId), // Media Analysis analyzeMediaImage: (mediaId: string, language?: string) => ipcRenderer.invoke('chat:analyzeMediaImage', mediaId, language), // Event listeners for streaming/progress onStreamDelta: (callback: (data: { conversationId: string; delta: string }) => void) => { const subscription = (_event: Electron.IpcRendererEvent, data: { conversationId: string; delta: string }) => callback(data); ipcRenderer.on('chat-stream-delta', subscription); return () => ipcRenderer.removeListener('chat-stream-delta', subscription); }, onToolCall: (callback: (data: { conversationId: string; toolCall: unknown }) => void) => { const subscription = (_event: Electron.IpcRendererEvent, data: { conversationId: string; toolCall: unknown }) => callback(data); ipcRenderer.on('chat-tool-call', subscription); return () => ipcRenderer.removeListener('chat-tool-call', subscription); }, onToolResult: (callback: (data: { conversationId: string; result: unknown }) => void) => { const subscription = (_event: Electron.IpcRendererEvent, data: { conversationId: string; result: unknown }) => callback(data); ipcRenderer.on('chat-tool-result', subscription); return () => ipcRenderer.removeListener('chat-tool-result', subscription); }, onTitleUpdated: (callback: (data: { conversationId: string; title: string }) => void) => { const subscription = (_event: Electron.IpcRendererEvent, data: { conversationId: string; title: string }) => callback(data); ipcRenderer.on('chat-title-updated', subscription); return () => ipcRenderer.removeListener('chat-title-updated', subscription); }, }, // Event listeners on: (channel: string, callback: (...args: unknown[]) => void) => { const subscription = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args); ipcRenderer.on(channel, subscription); return () => ipcRenderer.removeListener(channel, subscription); }, once: (channel: string, callback: (...args: unknown[]) => void) => { ipcRenderer.once(channel, (_event, ...args) => callback(...args)); }, }); // Type definitions for the exposed API export interface ElectronAPI { projects: { create: (data: { name: string; description?: string; slug?: string; dataPath?: string }) => Promise; update: (id: string, data: unknown) => Promise; delete: (id: string) => Promise; get: (id: string) => Promise; getAll: () => Promise; getActive: () => Promise; setActive: (id: string) => Promise; }; posts: { create: (data: unknown) => Promise; update: (id: string, data: unknown) => Promise; delete: (id: string) => Promise; get: (id: string) => Promise; getAll: () => Promise; getByStatus: (status: string) => Promise; publish: (id: string) => Promise; unpublish: (id: string) => Promise; rebuildFromFiles: () => Promise; search: (query: string) => Promise; filter: (filter: unknown) => Promise; getTags: () => Promise; getCategories: () => Promise; getByYearMonth: () => Promise<{ year: number; month: number; count: number }[]>; getTagsWithCounts: () => Promise<{ tag: string; count: number }[]>; getCategoriesWithCounts: () => Promise<{ category: string; count: number }[]>; getDashboardStats: () => Promise<{ totalPosts: number; draftCount: number; publishedCount: number; archivedCount: number }>; getLinksTo: (id: string) => Promise<{ id: string; title: string; slug: string }[]>; getLinkedBy: (id: string) => Promise<{ id: string; title: string; slug: string }[]>; rebuildLinks: () => Promise; }; media: { import: (sourcePath: string, metadata?: unknown) => Promise; importDialog: () => Promise; update: (id: string, data: unknown) => Promise; delete: (id: string) => Promise; get: (id: string) => Promise; getAll: () => Promise; rebuildFromFiles: () => Promise; }; dropbox: { configure: (config: unknown) => Promise; isConfigured: () => Promise; getStatus: () => Promise; syncAll: () => Promise; startWatching: () => Promise; stopWatching: () => Promise; startPolling: () => Promise; stopPolling: () => Promise; getConflicts: () => Promise; resolveConflict: (conflictId: string, resolution: string) => Promise; getLastSyncTime: () => Promise; }; tasks: { getAll: () => Promise; getRunning: () => Promise; cancel: (taskId: string) => Promise; clearCompleted: () => Promise; }; app: { getDataPaths: () => Promise<{ database: string; posts: string; media: string }>; openFolder: (folderPath: string) => Promise; showItemInFolder: (itemPath: string) => Promise; selectFolder: (title?: string) => Promise; getDefaultProjectPath: (projectId: string) => Promise; }; meta: { getTags: () => Promise; getCategories: () => Promise; addTag: (tag: string) => Promise; removeTag: (tag: string) => Promise; addCategory: (category: string) => Promise; removeCategory: (category: string) => Promise; syncOnStartup: () => Promise<{ tags: string[]; categories: string[] }>; }; tags: { getAll: () => Promise; getWithCounts: () => Promise; get: (id: string) => Promise; getByName: (name: string) => Promise; create: (data: { name: string; color?: string }) => Promise; update: (id: string, data: { name?: string; color?: string | null }) => Promise; delete: (id: string) => Promise; merge: (sourceTagIds: string[], targetTagId: string) => Promise; rename: (id: string, newName: string) => Promise; getPostsWithTag: (tagId: string) => Promise; syncFromPosts: () => Promise; }; import: { selectAndAnalyze: (uploadsFolder?: string) => Promise; analyzeFile: (filePath: string, uploadsFolder?: string) => Promise; selectUploadsFolder: () => Promise; execute: (reportJson: string, uploadsFolder?: string) => Promise<{ taskId: string; totalItems: number }>; onProgress: (callback: (data: { step: string; detail?: string }) => void) => () => void; onExecutionProgress: (callback: (data: { taskId: string; phase: string; current: number; total: number; detail?: string; eta?: number; }) => void) => () => void; }; importDefinitions: { create: (name?: string) => Promise; get: (id: string) => Promise; getAll: () => Promise; update: (id: string, updates: unknown) => Promise; delete: (id: string) => Promise; }; chat: { // API Key Management checkReady: () => Promise<{ ready: boolean; error?: string; backend?: string }>; validateApiKey: (apiKey: string) => Promise<{ isValid: boolean; models: Array<{ id: string; name: string }> }>; setApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>; getApiKey: () => Promise<{ hasKey: boolean; maskedKey: string }>; // Settings getAvailableModels: () => Promise<{ success: boolean; models?: Array<{ id: string; name: string }>; selectedModel?: string; error?: string }>; setDefaultModel: (modelId: string) => Promise<{ success: boolean; error?: string }>; getSystemPrompt: () => Promise<{ success: boolean; prompt?: string; error?: string }>; setSystemPrompt: (prompt: string) => Promise<{ success: boolean; error?: string }>; // Conversations getConversations: () => Promise; createConversation: (title?: string, model?: string) => Promise; getConversation: (id: string) => Promise; updateConversation: (id: string, updates: { title?: string; model?: string }) => Promise; deleteConversation: (id: string) => Promise; // Messaging sendMessage: (conversationId: string, message: string) => Promise; abortMessage: (conversationId: string) => Promise; getHistory: (conversationId: string) => Promise; clearMessages: (conversationId: string) => Promise; setConversationModel: (conversationId: string, modelId: string) => Promise; // Event listeners onStreamDelta: (callback: (data: { conversationId: string; delta: string }) => void) => () => void; onToolCall: (callback: (data: { conversationId: string; toolCall: unknown }) => void) => () => void; onToolResult: (callback: (data: { conversationId: string; result: unknown }) => void) => () => void; onTitleUpdated: (callback: (data: { conversationId: string; title: string }) => void) => () => void; }; on: (channel: string, callback: (...args: unknown[]) => void) => () => void; once: (channel: string, callback: (...args: unknown[]) => void) => void; } declare global { interface Window { electronAPI: ElectronAPI; } }