import { app, BrowserWindow, nativeImage, dialog } from 'electron'; import path from 'path'; import { spawn, ChildProcess } from 'child_process'; import net from 'net'; let mainWindow: BrowserWindow | null = null; let serverProcess: ChildProcess | null = null; let serverPort: number | null = null; const isDev = !app.isPackaged; function getPort(): Promise { return new Promise((resolve, reject) => { const server = net.createServer(); server.unref(); server.on('error', reject); server.listen(0, '127.0.0.1', () => { const addr = server.address(); if (addr && typeof addr === 'object') { const port = addr.port; server.close(() => resolve(port)); } else { server.close(() => reject(new Error('Failed to get port'))); } }); }); } async function waitForServer(port: number, timeout = 30000): Promise { const start = Date.now(); while (Date.now() - start < timeout) { try { await new Promise((resolve, reject) => { const req = require('http').get(`http://127.0.0.1:${port}/api/health`, (res: { statusCode?: number }) => { if (res.statusCode === 200) resolve(); else reject(new Error(`Status ${res.statusCode}`)); }); req.on('error', reject); req.setTimeout(1000, () => { req.destroy(); reject(new Error('timeout')); }); }); return; } catch { await new Promise(r => setTimeout(r, 200)); } } throw new Error('Server startup timeout'); } function startServer(port: number): ChildProcess { const serverPath = path.join(process.resourcesPath, 'standalone', 'server.js'); // Use the bundled Node.js from Electron's helper const nodePath = process.execPath; const child = spawn(nodePath, [serverPath], { env: { ...process.env, PORT: String(port), HOSTNAME: '127.0.0.1', CLAUDE_GUI_DATA_DIR: app.getPath('userData'), // Tell Electron's Node not to load the Electron module ELECTRON_RUN_AS_NODE: '1', }, stdio: 'pipe', cwd: path.join(process.resourcesPath, 'standalone'), }); child.stdout?.on('data', (data: Buffer) => { console.log(`[server] ${data.toString().trim()}`); }); child.stderr?.on('data', (data: Buffer) => { console.error(`[server] ${data.toString().trim()}`); }); child.on('exit', (code) => { console.log(`Server process exited with code ${code}`); serverProcess = null; }); return child; } function getIconPath(): string { if (isDev) { return path.join(process.cwd(), 'build', 'icon.png'); } return path.join(process.resourcesPath, 'icon.icns'); } function createWindow(port: number) { mainWindow = new BrowserWindow({ width: 1280, height: 860, minWidth: 800, minHeight: 600, icon: getIconPath(), titleBarStyle: 'hiddenInset', webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, }, }); mainWindow.loadURL(`http://127.0.0.1:${port}`); if (isDev) { mainWindow.webContents.openDevTools(); } mainWindow.on('closed', () => { mainWindow = null; }); } app.whenReady().then(async () => { // Set macOS Dock icon if (process.platform === 'darwin' && app.dock) { const iconPath = getIconPath(); app.dock.setIcon(nativeImage.createFromPath(iconPath)); } try { let port: number; if (isDev) { port = 3000; console.log(`Dev mode: connecting to http://127.0.0.1:${port}`); } else { port = await getPort(); console.log(`Starting server on port ${port}...`); serverProcess = startServer(port); await waitForServer(port); console.log('Server is ready'); } serverPort = port; createWindow(port); } catch (err) { console.error('Failed to start:', err); dialog.showErrorBox( 'CodePilot - Failed to Start', `The internal server could not start.\n\n${err instanceof Error ? err.message : String(err)}\n\nPlease try restarting the application.` ); app.quit(); } }); app.on('window-all-closed', () => { if (serverProcess) { serverProcess.kill(); serverProcess = null; } if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', async () => { if (BrowserWindow.getAllWindows().length === 0) { try { if (!isDev && !serverProcess) { const port = await getPort(); serverProcess = startServer(port); await waitForServer(port); serverPort = port; } createWindow(serverPort || 3000); } catch (err) { console.error('Failed to restart server:', err); } } }); app.on('before-quit', () => { if (serverProcess) { serverProcess.kill(); serverProcess = null; } });