import { contextBridge, clipboard, ipcRenderer } from 'electron' /** * The single bridge between renderer and main. The renderer NEVER touches the * filesystem, git or the OS clipboard directly — everything goes through here. * (PTYs land here next, for the terminals.) */ const api = { platform: process.platform, clipboard: { writeText: (text: string) => clipboard.writeText(text), }, project: { current: () => ipcRenderer.invoke('project:current'), open: () => ipcRenderer.invoke('project:open'), openPath: (path: string) => ipcRenderer.invoke('project:openPath', path), recent: (): Promise<{ path: string; name: string }[]> => ipcRenderer.invoke('projects:recent'), }, fs: { tree: () => ipcRenderer.invoke('fs:tree'), files: () => ipcRenderer.invoke('fs:files'), read: (path: string) => ipcRenderer.invoke('fs:read', path), write: (path: string, content: string): Promise => ipcRenderer.invoke('fs:write', path, content), }, git: { load: () => ipcRenderer.invoke('git:load'), stage: (paths: string[]) => ipcRenderer.invoke('git:stage', paths), unstage: (paths: string[]) => ipcRenderer.invoke('git:unstage', paths), commit: (message: string) => ipcRenderer.invoke('git:commit', message), discard: (paths: string[]) => ipcRenderer.invoke('git:discard', paths), }, pty: { available: (): Promise => ipcRenderer.invoke('pty:available'), create: (kind: 'agent' | 'shell', cols: number, rows: number): Promise => ipcRenderer.invoke('pty:create', kind, cols, rows), write: (id: number, data: string): void => ipcRenderer.send('pty:write', id, data), resize: (id: number, cols: number, rows: number): void => ipcRenderer.send('pty:resize', id, cols, rows), kill: (id: number): void => ipcRenderer.send('pty:kill', id), onData: (cb: (id: number, data: string) => void): (() => void) => { const h = (_e: unknown, p: { id: number; data: string }): void => cb(p.id, p.data) ipcRenderer.on('pty:data', h) return () => ipcRenderer.removeListener('pty:data', h) }, onExit: (cb: (id: number) => void): (() => void) => { const h = (_e: unknown, p: { id: number }): void => cb(p.id) ipcRenderer.on('pty:exit', h) return () => ipcRenderer.removeListener('pty:exit', h) }, }, config: { get: () => ipcRenderer.invoke('config:get'), theme: (): Promise => ipcRenderer.invoke('config:theme'), }, recent: { get: (): Promise => ipcRenderer.invoke('recent:get'), set: (list: string[]): Promise => ipcRenderer.invoke('recent:set', list), }, search: { content: (query: string) => ipcRenderer.invoke('search:content', query), files: (): Promise => ipcRenderer.invoke('search:files'), }, dialog: { unsavedClose: (path: string): Promise<'save' | 'discard' | 'cancel'> => ipcRenderer.invoke('dialog:unsavedClose', path), }, /** Subscribe to "the project changed on disk" pings. Returns an unsubscribe. */ onProjectChanged: (cb: () => void): (() => void) => { const handler = (): void => cb() ipcRenderer.on('project:changed', handler) return () => ipcRenderer.removeListener('project:changed', handler) }, /** Subscribe to .helder config/theme edits. Returns an unsubscribe. */ onConfigChanged: (cb: () => void): (() => void) => { const handler = (): void => cb() ipcRenderer.on('config:changed', handler) return () => ipcRenderer.removeListener('config:changed', handler) }, } if (process.contextIsolated) { contextBridge.exposeInMainWorld('helder', api) } else { ;(globalThis as unknown as { helder: typeof api }).helder = api } export type HelderApi = typeof api