"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const electron_1 = require("electron"); const main_1 = __importDefault(require("electron-log/main")); const ipcHandlers_1 = require("./ipcHandlers"); const fs = __importStar(require("fs")); const crypto = __importStar(require("crypto")); const readline = __importStar(require("readline")); const utils_1 = require("./utils"); const languageServer_1 = require("./languageServer"); const updater_1 = require("./updater"); const constants_1 = require("./constants"); const tray_1 = require("./tray"); const storage_1 = require("./storage"); const paths_1 = require("./paths"); const menu_1 = require("./menu"); const customScheme_1 = require("./customScheme"); const settingsService_1 = require("./services/settingsService"); const ideInstall_1 = require("./ideInstall"); const providerService_1 = require("./services/providerService"); const apiProxy_1 = require("./services/apiProxy"); const providerSettings_1 = require("./providerSettings"); const path_2 = require("path"); const fs_1 = require("fs"); const gotTheLock = electron_1.app.requestSingleInstanceLock(); if (!gotTheLock) { electron_1.app.quit(); process.exit(0); } // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- let storageManager; let settingsService; let hasStartedMainApplication = false; let isQuitting = false; let providerService; let apiProxy; // --------------------------------------------------------------------------- // Config // --------------------------------------------------------------------------- // Driven by ELECTRON_OZONE_PLATFORM_HINT=headless env var. // This single env var both prevents GTK from crashing (Electron 33+) // and tells our code to skip createWindow(). const HEADLESS = process.env.ELECTRON_OZONE_PLATFORM_HINT === 'headless'; // When set, skip LS startup and load this URL directly (for dev iteration). const DEV_URL = process.env.DEV_URL; if (HEADLESS) { electron_1.app.commandLine.appendSwitch('ozone-platform', 'headless'); electron_1.app.commandLine.appendSwitch('headless'); electron_1.app.commandLine.appendSwitch('disable-gpu'); electron_1.app.commandLine.appendSwitch('no-sandbox'); } if (!electron_1.app.commandLine.hasSwitch('remote-debugging-port')) { electron_1.app.commandLine.appendSwitch('remote-debugging-port', '0'); } // --------------------------------------------------------------------------- // Application Lifecycle // --------------------------------------------------------------------------- let pendingDeepLink = null; function handleDeepLink(url) { const wins = electron_1.BrowserWindow.getAllWindows(); // This block handles deep links when windows are already open. if (wins.length > 0) { if (wins[0].isMinimized()) { wins[0].restore(); } wins[0].show(); wins[0].focus(); electron_1.app.focus({ steal: true }); wins[0].webContents.send('deep-link', url); } else { pendingDeepLink = url; } } electron_1.app.on('second-instance', (event, commandLine) => { const wins = electron_1.BrowserWindow.getAllWindows(); if (wins.length > 0) { if (wins[0].isMinimized()) { wins[0].restore(); } wins[0].show(); wins[0].focus(); electron_1.app.focus({ steal: true }); } const url = commandLine.find((arg) => arg.startsWith('ag-x://')); if (url) { handleDeepLink(url); } }); (0, customScheme_1.registerCustomSchemes)(); // Register as default protocol client for deep linking const PROTOCOL = 'ag-x'; if (!electron_1.app.isDefaultProtocolClient(PROTOCOL)) { electron_1.app.setAsDefaultProtocolClient(PROTOCOL); } electron_1.app.on('open-url', (event, url) => { event.preventDefault(); handleDeepLink(url); }); /** * App entry point. Runs once Electron has finished initializing. * Validates the LS binary, frees the port if needed, spawns the LS, * and opens the initial browser window. */ electron_1.app .whenReady() .then(async () => { // Initialize electron-log and override console main_1.default.initialize(); Object.assign(console, main_1.default.functions); const storagePath = (0, paths_1.getAppStoragePath)(); storageManager = new storage_1.StorageManager(storagePath, settingsService_1.DEFAULTS); settingsService = new settingsService_1.SettingsService(storageManager); // Initialize AI Provider Service providerService = new providerService_1.ProviderService(storageManager); console.log(`[Provider] Active provider: ${providerService.getActiveProvider()}`); // Start API proxy if a non-Gemini provider is active if (providerService.needsProxy()) { apiProxy = new apiProxy_1.ApiProxy(providerService); apiProxy.start(); console.log(`[Provider] API proxy started for ${providerService.getActiveProvider()}`); } // Handle deep link URL from command line arguments (All platforms) const deepLinkFromArg = process.argv.find((arg) => arg.startsWith('ag-x://')); if (deepLinkFromArg) { console.log('Launched with deep link:', deepLinkFromArg); pendingDeepLink = deepLinkFromArg; } // Register IPC handlers (0, ipcHandlers_1.registerIpcHandlers)(storageManager); // Register provider IPC handlers electron_1.ipcMain.handle('provider:get-active', async () => { return providerService.getActiveProvider(); }); electron_1.ipcMain.handle('provider:get-all', async () => { return providerService.getAllProviders(); }); electron_1.ipcMain.handle('provider:open-settings', async () => { (0, providerSettings_1.openProviderSettings)(providerService); }); electron_1.ipcMain.handle('deep-link:get-stored', () => { const link = pendingDeepLink; pendingDeepLink = null; // Clear after read return link; }); // Handle requests coming from custom schemes (0, customScheme_1.registerCustomSchemeHandlers)(); // Set About panel options with LS CL const cl = await (0, languageServer_1.getLsCL)(); electron_1.app.setAboutPanelOptions({ applicationName: 'AG X', applicationVersion: electron_1.app.getVersion(), version: cl || undefined, }); // Pre-onboarding: check if we should offer to re-install the IDE. // This runs before the LS starts so we can show a standalone wizard. if (!HEADLESS) { await (0, ideInstall_1.maybeShowIdeInstallWizard)(storageManager); } if (DEV_URL) { console.log('Starting in dev mode with URL:', DEV_URL); (0, utils_1.createWindow)(DEV_URL); hasStartedMainApplication = true; return; } if (!fs.existsSync(languageServer_1.LS_BINARY)) { const msg = `language_server binary not found at:\n${languageServer_1.LS_BINARY}\n\nPlease build set a valid location.`; if (HEADLESS) { console.error('ERROR:', msg); } else { await electron_1.dialog.showErrorBox('Binary not found', msg); } electron_1.app.quit(); return; } const csrf = crypto.randomUUID(); console.log(`Starting app (v${electron_1.app.getVersion()}) with dynamic port…`); // Check if first run BEFORE starting LS so we can configure provider first const isFirstRun = !fs_1.existsSync(providerService.configPath); if (isFirstRun && !HEADLESS) { console.log('[Welcome] First run detected — showing provider choice screen'); await showWelcomeScreen('about:blank'); console.log('[Welcome] User selected provider:', providerService.getActiveProvider()); // Sync to endpoints.json (for Google, the handler already set the endpoint; // for custom, the settings save handler synced. This is a safety sync.) if (providerService.getActiveProvider() !== 'google_gemini') { syncProviderToEndpoints(providerService); } } let handle; const targetPort = Number(process.env.JETSKI_LS_PORT) || constants_1.DYNAMIC_PORT; try { handle = await (0, languageServer_1.startAndMonitorLanguageServer)(targetPort, csrf, { headless: HEADLESS, onPortChanged: (newPort) => { const newUrl = `${constants_1.WINDOW_ORIGIN}:${newPort}/`; console.log(`[Auto-Restart] Port changed! Reloading all windows with URL: ${newUrl}`); (0, languageServer_1.setupLocalCertTrust)(); if (!HEADLESS) { const windows = electron_1.BrowserWindow.getAllWindows(); for (const win of windows) { if (win.getTitle() === 'Welcome to AG X' || win.getTitle() === 'AI Provider Settings') continue; void win.loadURL(newUrl); } } }, }); } catch (err) { const msg = err.message; if (HEADLESS) { console.error('Startup failed:', msg); } else { await electron_1.dialog.showErrorBox('Startup failed', msg); } electron_1.app.quit(); return; } const url = `${constants_1.WINDOW_ORIGIN}:${handle.port}/`; console.log('\n' + '='.repeat(60)); console.log(` Local: ${url}`); console.log(` LS Logs: ${(0, paths_1.getLsLogPath)()}`); console.log(` Electron Logs: ${main_1.default.transports.file.getFile().path}`); console.log(` Provider: ${providerService.getActiveProvider()}`); console.log('='.repeat(60) + '\n'); if (HEADLESS) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); rl.on('line', (line) => { const lsProc = (0, languageServer_1.getLsProcess)(); if (lsProc && lsProc.stdin) { lsProc.stdin.write(line + '\n'); console.log('-> Forwarded input to Language Server.'); } else { console.log('Language Server process is not running.'); } }); } // Initial window — opened once after the LS has successfully started. if (!HEADLESS) { (0, menu_1.setupApplicationMenu)(url); // Create the main window (welcome screen already completed above for first run) (0, utils_1.createWindow)(url); if (electron_1.app.dock) { const dockMenu = electron_1.Menu.buildFromTemplate([ { label: 'New Window', click: () => (0, utils_1.createWindow)(url), }, ]); electron_1.app.dock.setMenu(dockMenu); } (0, tray_1.createTray)([ { id: 'running-agents', label: 'No agents running', enabled: false, }, { type: 'separator' }, { label: `Open ${electron_1.app.getName()}`, click: () => (0, utils_1.showOrCreateWindow)((0, languageServer_1.getLsPort)()), }, { label: 'AI Provider Settings', click: () => (0, providerSettings_1.openProviderSettings)(providerService), }, { type: 'separator' }, { label: 'Quit', click: () => { // Triggers 'before-quit' to run graceful cleanup without confirmation. electron_1.app.quit(); }, }, ]); } // Start checking for app updates. (0, updater_1.initAutoUpdater)(HEADLESS); hasStartedMainApplication = true; }) .catch(() => { hasStartedMainApplication = true; }); /** * Shows the first-run welcome screen for provider selection. * Returns a Promise that resolves when the user has made their choice. */ // === AG X: Sync provider config to ~/.codex/endpoints.json === function syncProviderToEndpoints(providerService) { try { const os = require('os'); const fs = require('fs'); const pathMod = require('path'); const home = os.homedir(); const endpointsPath = pathMod.join(home, '.codex', 'endpoints.json'); const activePath = pathMod.join(home, '.codex', '.active-endpoint.json'); const activeProvider = providerService.getActiveProvider(); const providerConfig = providerService.getProviderConfig(activeProvider); if (!providerConfig) { console.log('[Sync] No provider config to sync'); return; } let endpointsData = { default: '', endpoints: [] }; if (fs.existsSync(endpointsPath)) { try { endpointsData = JSON.parse(fs.readFileSync(endpointsPath, 'utf8')); } catch (e) {} } const endpointName = 'AG X: ' + (providerConfig.name || activeProvider); const existingIdx = endpointsData.endpoints.findIndex(e => e.name === endpointName); const endpoint = { name: endpointName, backend_type: providerConfig.backendType || 'openai-compat', base_url: providerConfig.apiUrl || '', api_key: providerConfig.apiKey || '', default_model: providerConfig.model || providerConfig.defaultModel || '', models: providerConfig.models || [], provider_preset: 'AG X', }; if (existingIdx >= 0) { endpointsData.endpoints[existingIdx] = endpoint; } else { endpointsData.endpoints.push(endpoint); } endpointsData.default = endpointName; fs.writeFileSync(endpointsPath, JSON.stringify(endpointsData, null, 2), 'utf-8'); fs.writeFileSync(activePath, JSON.stringify({ active: endpointName }, null, 2), 'utf-8'); console.log('[Sync] Provider synced to endpoints.json:', endpointName); } catch (err) { console.error('[Sync] Failed to sync provider to endpoints:', err); } } // === End sync function === function showWelcomeScreen(mainUrl) { return new Promise((resolve) => { const welcomeWin = new electron_1.BrowserWindow({ width: 720, height: 560, title: 'Welcome to AG X', resizable: false, minimizable: false, maximizable: false, fullscreenable: false, backgroundColor: '#0a0a1a', show: true, center: true, frame: false, titleBarStyle: 'hidden', webPreferences: { nodeIntegration: true, contextIsolation: false, enableRemoteModule: false, }, }); welcomeWin.loadFile(path_2.join(__dirname, 'provider', 'welcome.html')).catch((err) => { console.error('[Welcome] Failed to load welcome.html:', err); }); welcomeWin.webContents.on('did-finish-load', () => { console.log('[Welcome] Welcome screen loaded successfully'); }); welcomeWin.webContents.on('did-fail-load', (_event, code, desc) => { console.error('[Welcome] Failed to load:', code, desc); }); console.log('[Welcome] Window created, title:', welcomeWin.getTitle()); welcomeWin.on('closed', () => { // If closed without choosing, default to Google Gemini if (!resolved) { resolved = true; providerService.setActiveProvider('google_gemini'); providerService.saveConfig(); resolve(); } }); let resolved = false; electron_1.ipcMain.once('welcome:choice', (_event, data) => { if (resolved) return; resolved = true; if (data.provider === 'google_gemini') { console.log('[Welcome] User chose Google Gemini (OAuth)'); providerService.setActiveProvider('google_gemini'); providerService.saveConfig(); // Set Google as active endpoint in ~/.codex/ try { const os = require('os'); const fs = require('fs'); const pathMod = require('path'); const activePath = pathMod.join(os.homedir(), '.codex', '.active-endpoint.json'); const endpointsPath = pathMod.join(os.homedir(), '.codex', 'endpoints.json'); let epData = { default: '', endpoints: [] }; if (fs.existsSync(endpointsPath)) { try { epData = JSON.parse(fs.readFileSync(endpointsPath, 'utf8')); } catch(e) {} } let googleEp = epData.endpoints.find(e => e.name === 'Google Gemini'); if (!googleEp) { googleEp = { name: 'Google Gemini', backend_type: 'gemini-native', base_url: 'https://daily-cloudcode-pa.sandbox.googleapis.com', api_key: '', default_model: 'gemini-2.5-pro', models: ['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.0-flash'], provider_preset: 'AG X', }; epData.endpoints.push(googleEp); } epData.default = 'Google Gemini'; fs.writeFileSync(endpointsPath, JSON.stringify(epData, null, 2), 'utf-8'); fs.writeFileSync(activePath, JSON.stringify({ active: 'Google Gemini' }, null, 2), 'utf-8'); console.log('[Welcome] Google Gemini endpoint set as active'); } catch(e) { console.error('[Welcome] Failed to set Google endpoint:', e); } welcomeWin.close(); // Don't createWindow here — LS hasn't started yet, main window opens after LS starts } else { console.log('[Welcome] User chose custom provider — opening settings'); welcomeWin.close(); // Open provider settings and wait for user to save (0, providerSettings_1.openProviderSettings)(providerService); // Listen for the settings save to complete before continuing const settingsSaveHandler = async () => { console.log('[Welcome] Provider settings saved, syncing and continuing startup'); syncProviderToEndpoints(providerService); electron_1.ipcMain.removeListener('provider:settings-saved', settingsSaveHandler); resolve(); }; electron_1.ipcMain.on('provider:settings-saved', settingsSaveHandler); return; // Don't resolve yet - wait for settings save } resolve(); }); }); } /** * Fired when all windows have been closed. * On macOS the app (and LS) stay alive so the user can re-open via the tray. * On all other platforms, shut down the LS and quit. */ electron_1.app.on('window-all-closed', async () => { if (isQuitting) { return; } if (!hasStartedMainApplication) { return; } // Show provider settings on first launch if no API key configured const activeProviderType = providerService.getActiveProvider(); const activeProviderConfig = providerService.getProviderConfig(activeProviderType); if (activeProviderConfig && activeProviderConfig.requiresApiKey && !activeProviderConfig.apiKey) { console.log('[Provider] No API key configured for active provider, showing settings...'); // Delay slightly so the main window renders first setTimeout(() => { (0, providerSettings_1.openProviderSettings)(providerService); }, 2000); } const runInBackground = await settingsService.getSetting(settingsService_1.SettingKey.RUN_IN_BACKGROUND); if (!runInBackground) { // Triggers 'before-quit' to run graceful cleanup without confirmation. electron_1.app.quit(); } else { electron_1.app.dock?.hide(); } }); /** * Fired just before the app quits (e.g. Cmd+Q on macOS, or after * window-all-closed on non-macOS). Ensures the LS is terminated even if * window-all-closed didn't handle it (e.g. on macOS quit via menu). */ electron_1.app.on('before-quit', async (event) => { if (isQuitting) { return; } if (!utils_1.showQuitConfirmation) { event.preventDefault(); isQuitting = true; // Destroy all windows to terminate renderers and release keep-alive sockets const windows = electron_1.BrowserWindow.getAllWindows(); for (const win of windows) { win.destroy(); } // Stop API proxy if (apiProxy) { apiProxy.stop(); } // Close all active connections and kill the language server in parallel await Promise.all([ electron_1.session.defaultSession.closeAllConnections().catch((err) => { console.error('Failed to close session connections:', err); }), (0, languageServer_1.killLanguageServer)(), ]); electron_1.app.quit(); return; } // Show a confirmation dialog before quitting event.preventDefault(); const win = electron_1.BrowserWindow.getFocusedWindow() || electron_1.BrowserWindow.getAllWindows()[0]; const options = { type: 'question', buttons: ['Cancel', 'Quit'], defaultId: 1, cancelId: 0, title: 'Confirm Quit', message: 'Are you sure you want to quit?', detail: 'There may be agents or background tasks running.', }; (0, utils_1.setShowQuitConfirmation)(false); if (win) { void electron_1.dialog.showMessageBox(win, options).then((result) => { if (result.response === 1) { // Quit - this will retrigger 'before-quit' electron_1.app.quit(); } }); } }); /** * Fired when the app is re-activated (e.g. clicking the dock icon on macOS). * Re-opens a window if none are currently open. */ electron_1.app.on('activate', () => { // On Mac, re-open a window when the user clicks the dock // icon and no windows are open. if (!HEADLESS && electron_1.BrowserWindow.getAllWindows().length === 0) { const url = DEV_URL ?? `${constants_1.WINDOW_ORIGIN}:${(0, languageServer_1.getLsPort)()}/`; (0, utils_1.createWindow)(url); } });