[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/eslint-recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:import/recommended\",\n    \"plugin:import/electron\",\n    \"plugin:import/typescript\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\"\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n.DS_Store\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# next.js build output\n.next\n\n# nuxt.js build output\n.nuxt\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# Webpack\n.webpack/\n\n# Vite\n.vite/\n\n# Electron-Forge\nout/\n"
  },
  {
    "path": "README.md",
    "content": "\n<img src=\"./assets/desktop.png\" alt=\"Browser Use Desktop App\" width=\"full\"/>\n\n<br/>\n\n[![GitHub stars](https://img.shields.io/github/stars/browser-use/desktop?style=social)](https://github.com/browser-use/desktop/stargazers)\n[![Discord](https://img.shields.io/discord/1303749220842340412?color=7289DA&label=Discord&logo=discord&logoColor=white)](https://link.browser-use.com/discord)\n[![Documentation](https://img.shields.io/badge/Documentation-📕-blue)](https://docs.browser-use.com)\n[![Browser-Use](https://img.shields.io/twitter/follow/browser_use?style=social)](https://x.com/browser_use)\n\nThis project is designed to make websites accessible for AI agents and builds upon the foundation of [browser-use](https://github.com/browser-use/browser-use) + [web-ui](https://github.com/browser-use/web-ui).\n\n**UI:** is built on Electron and Gradio and supports most `browser-use` functionalities. This UI is designed to be user-friendly and enables easy interaction with the browser agent.\n\n**Expanded LLM Support:** We've integrated support for various Large Language Models (LLMs), including: Google, OpenAI, Azure OpenAI, Anthropic, DeepSeek, Ollama etc. And we plan to add support for even more models in the future.\n\n**Custom Browser Support:** The desktop app uses your existing Google Chrome browser, eliminating the need to re-login to sites or deal with other authentication challenges. This feature also supports high-definition screen recording.\n\n<img width=\"100%\" alt=\"Screenshot of Browser Use Desktop running on macOS\" src=\"https://github.com/user-attachments/assets/94e9fec1-7b17-453e-af6d-2e35f54b46ab\" />\n\n\n## Get Started\n\n```bash\ngit clone https://github.com/browser-use/desktop\ncd desktop\n\nnpm install\nvite dev\n```\n\n## Download\n\n- Download for macOS *(Coming soon...)*\n- Download for Windows *(Coming soon...)*\n- Download for Linux *(Coming soon...)*\n"
  },
  {
    "path": "forge.config.ts",
    "content": "import type { ForgeConfig } from '@electron-forge/shared-types';\nimport { MakerSquirrel } from '@electron-forge/maker-squirrel';\nimport { MakerZIP } from '@electron-forge/maker-zip';\nimport { MakerDeb } from '@electron-forge/maker-deb';\nimport { MakerRpm } from '@electron-forge/maker-rpm';\nimport { VitePlugin } from '@electron-forge/plugin-vite';\nimport { FusesPlugin } from '@electron-forge/plugin-fuses';\nimport { FuseV1Options, FuseVersion } from '@electron/fuses';\n\nconst config: ForgeConfig = {\n  packagerConfig: {\n    asar: true,\n  },\n  rebuildConfig: {},\n  makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],\n  plugins: [\n    new VitePlugin({\n      // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.\n      // If you are familiar with Vite configuration, it will look really familiar.\n      build: [\n        {\n          // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.\n          entry: 'src/main.ts',\n          config: 'vite.main.config.ts',\n          target: 'main',\n        },\n        {\n          entry: 'src/preload.ts',\n          config: 'vite.preload.config.ts',\n          target: 'preload',\n        },\n      ],\n      renderer: [\n        {\n          name: 'main_window',\n          config: 'vite.renderer.config.ts',\n        },\n      ],\n    }),\n    // Fuses are used to enable/disable various Electron functionality\n    // at package time, before code signing the application\n    new FusesPlugin({\n      version: FuseVersion.V1,\n      [FuseV1Options.RunAsNode]: false,\n      [FuseV1Options.EnableCookieEncryption]: true,\n      [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,\n      [FuseV1Options.EnableNodeCliInspectArguments]: false,\n      [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,\n      [FuseV1Options.OnlyLoadAppFromAsar]: true,\n    }),\n  ],\n};\n\nexport default config;\n"
  },
  {
    "path": "forge.env.d.ts",
    "content": "/// <reference types=\"@electron-forge/plugin-vite/forge-vite-env\" />\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Browser-Use Desktop</title>\n    <style>\n      body {\n        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n        margin: 0;\n        padding: 20px;\n        background-color: #f5f5f5;\n        color: #333;\n      }\n      h1 {\n        margin-bottom: 20px;\n        color: #2c3e50;\n      }\n      main {\n        display: flex;\n        flex-direction: column;\n        gap: 20px;\n      }\n      .console-output {\n        background-color: #263238;\n        color: #eeffff;\n        padding: 0;\n        border-radius: 5px 5px 0 0;\n        height: 300px;\n        position: fixed;\n        bottom: 0;\n        left: 0;\n        right: 0;\n        z-index: 15;\n        transform: translateY(100%);\n        transition: transform 0.3s ease;\n        box-shadow: 0 -2px 10px rgba(0,0,0,0.3);\n        display: flex;\n        flex-direction: column;\n      }\n      \n      .console-output.visible {\n        transform: translateY(0);\n      }\n      \n      /* Adjust layout for loading state */\n      body.loading .console-output.visible {\n        height: 250px;\n      }\n      \n      .console-header {\n        background-color: #1e272c;\n        padding: 8px 15px;\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        border-bottom: 1px solid #37474F;\n      }\n      \n      .console-header h3 {\n        margin: 0;\n        font-size: 14px;\n        font-weight: normal;\n        color: #B0BEC5;\n      }\n      \n      .console-header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n      }\n      \n      .console-tabs {\n        display: flex;\n        gap: 8px;\n      }\n      \n      .console-tab {\n        background-color: #37474F;\n        border: none;\n        color: #B0BEC5;\n        padding: 5px 10px;\n        border-radius: 4px;\n        cursor: pointer;\n        font-size: 12px;\n      }\n      \n      .console-tab.active {\n        background-color: #546E7A;\n        color: #ECEFF1;\n      }\n      \n      .console-tab:hover {\n        background-color: #455A64;\n      }\n      \n      #console-actions {\n        background-color: #1e272c;\n        padding: 8px 15px;\n        display: flex;\n        border-bottom: 1px solid #37474F;\n      }\n      \n      .restart-btn {\n        background-color: #455A64;\n        color: #ECEFF1;\n        border: none;\n        border-radius: 4px;\n        padding: 6px 10px;\n        font-size: 12px;\n        cursor: pointer;\n        margin-right: 10px;\n      }\n      \n      .restart-btn:hover {\n        background-color: #546E7A;\n      }\n      \n      .action-row {\n        display: flex;\n        align-items: center;\n        width: 100%;\n      }\n      \n      .command-display {\n        margin-left: 15px;\n        font-family: monospace;\n        color: #81C784;\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        flex: 1;\n        font-size: 12px;\n      }\n      \n      .console-content {\n        overflow-y: auto;\n        padding: 15px;\n        font-family: monospace;\n        white-space: pre-wrap;\n        flex: 1;\n        height: 100%;\n      }\n      \n      .command-line {\n        color: #81C784;\n        font-weight: bold;\n        padding: 5px 0;\n        border-bottom: 1px solid #37474F;\n        margin-bottom: 10px;\n      }\n      \n      .close-btn {\n        font-size: 24px;\n        color: #B0BEC5;\n        cursor: pointer;\n        user-select: none;\n        line-height: 1;\n      }\n      \n      .close-btn:hover {\n        color: #ECEFF1;\n      }\n      \n      #console-content {\n        overflow-y: auto;\n        padding: 15px;\n        font-family: monospace;\n        white-space: pre-wrap;\n        flex: 1;\n      }\n      .console-output .stderr {\n        color: #ff5252;\n      }\n      .console-output .info {\n        color: #69f0ae;\n      }\n      .console-output .error {\n        color: #ff7043;\n      }\n      .loading-container {\n        margin: 20px 0;\n      }\n      .loading-bar {\n        height: 20px;\n        background-color: #ddd;\n        border-radius: 10px;\n        overflow: hidden;\n      }\n      .loading-progress {\n        height: 100%;\n        background-color: #4caf50;\n        width: 0%;\n        transition: width 0.3s ease;\n      }\n      .webview-container {\n        display: none;\n        position: absolute;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        width: 100%;\n        height: 100%;\n        z-index: 10;\n      }\n      \n      .controls {\n        position: fixed;\n        bottom: 10px;\n        right: 10px;\n        z-index: 20;\n        background-color: rgba(0,0,0,0.6);\n        border-radius: 5px;\n        padding: 8px;\n        display: none;\n        box-shadow: 0 2px 10px rgba(0,0,0,0.3);\n        transition: opacity 0.3s ease;\n      }\n      \n      .controls:hover {\n        opacity: 1;\n      }\n      \n      .controls button {\n        background-color: #2196f3;\n        color: white;\n        border: none;\n        border-radius: 4px;\n        padding: 10px 15px;\n        cursor: pointer;\n        font-weight: bold;\n        transition: background-color 0.2s ease;\n      }\n      \n      .controls button:hover {\n        background-color: #0d8aee;\n      }\n      webview {\n        width: 100%;\n        height: 100%;\n        border: none;\n      }\n      .launch-button {\n        display: none;\n        padding: 10px 20px;\n        background-color: #2196f3;\n        color: white;\n        border: none;\n        border-radius: 4px;\n        font-size: 16px;\n        cursor: pointer;\n        margin-top: 10px;\n      }\n      .launch-button:hover {\n        background-color: #1976d2;\n      }\n    </style>\n  </head>\n  <body class=\"loading\">\n    <h1>Browser-Use Desktop</h1>\n    <main>\n      <div id=\"loading-container\" class=\"loading-container\">\n        <h3>Starting Python web server...</h3>\n        <div class=\"loading-bar\">\n          <div id=\"loading-progress\" class=\"loading-progress\"></div>\n        </div>\n      </div>\n      \n      <div id=\"console-output\" class=\"console-output\">\n        <div class=\"console-header\">\n          <h3>Console Output</h3>\n          <div class=\"console-tabs\">\n            <button id=\"tab-python\" class=\"console-tab active\">Python</button>\n            <button id=\"tab-chrome\" class=\"console-tab\">Chrome</button>\n          </div>\n          <span id=\"close-console\" class=\"close-btn\">×</span>\n        </div>\n        <div id=\"console-actions\">\n          <div class=\"action-row\">\n            <button id=\"restart-python\" class=\"restart-btn\">Restart Python</button>\n            <div id=\"python-command\" class=\"command-display\"></div>\n          </div>\n          <div class=\"action-row\" style=\"display: none;\">\n            <button id=\"restart-chrome\" class=\"restart-btn\">Restart Chrome</button>\n            <div id=\"chrome-command\" class=\"command-display\"></div>\n          </div>\n        </div>\n        <div id=\"console-content\"></div>\n        <div id=\"chrome-console-content\" class=\"console-content\" style=\"display: none;\"></div>\n      </div>\n      \n      <button id=\"launch-button\" class=\"launch-button\">Open Web UI</button>\n      \n      <div id=\"webview-container\" class=\"webview-container\">\n        <webview id=\"webview\" src=\"about:blank\" webpreferences=\"contextIsolation=yes\" allowpopups></webview>\n      </div>\n      \n      <div id=\"controls\" class=\"controls\">\n        <button id=\"toggle-console\">Show Console</button>\n      </div>\n    </main>\n    \n    <script type=\"module\" src=\"/src/renderer.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"browser-use-desktop\",\n  \"productName\": \"browser-use-desktop\",\n  \"version\": \"1.0.0\",\n  \"description\": \"My Electron application description\",\n  \"main\": \".vite/build/main.js\",\n  \"scripts\": {\n    \"start\": \"electron-forge start\",\n    \"package\": \"electron-forge package\",\n    \"make\": \"electron-forge make\",\n    \"publish\": \"electron-forge publish\",\n    \"lint\": \"eslint --ext .ts,.tsx .\"\n  },\n  \"keywords\": [],\n  \"author\": {\n    \"name\": \"Nick Sweeting\",\n    \"email\": \"git@sweeting.me\"\n  },\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@electron-forge/cli\": \"^7.8.0\",\n    \"@electron-forge/maker-deb\": \"^7.8.0\",\n    \"@electron-forge/maker-rpm\": \"^7.8.0\",\n    \"@electron-forge/maker-squirrel\": \"^7.8.0\",\n    \"@electron-forge/maker-zip\": \"^7.8.0\",\n    \"@electron-forge/plugin-auto-unpack-natives\": \"^7.8.0\",\n    \"@electron-forge/plugin-fuses\": \"^7.8.0\",\n    \"@electron-forge/plugin-vite\": \"^7.8.0\",\n    \"@electron/fuses\": \"^1.8.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.62.0\",\n    \"@typescript-eslint/parser\": \"^5.62.0\",\n    \"electron\": \"35.1.2\",\n    \"eslint\": \"^8.57.1\",\n    \"eslint-plugin-import\": \"^2.31.0\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"~4.5.4\",\n    \"vite\": \"^5.4.15\"\n  },\n  \"dependencies\": {\n    \"electron-squirrel-startup\": \"^1.0.1\"\n  }\n}\n"
  },
  {
    "path": "src/config.ts",
    "content": "// Configuration for processes\nexport const pythonCommand = {\n  path: './.venv/bin/python',\n  args: ['webui.py', '--ip', '127.0.0.1', '--port', '7788'],\n  workingDir: 'lib/web-ui',\n  // Full command string for display\n  get display(): string {\n    return `${this.path} ${this.args.join(' ')}`;\n  }\n};\n\n// Platform type for OS detection\ntype Platform = 'darwin' | 'win32' | 'linux' | string;\n\n// Define the nodeAPI interface that's injected by the preload script\ndeclare global {\n  interface Window {\n    nodeAPI?: {\n      platform: Platform;\n      homedir: string;\n      env: {\n        HOME?: string;\n        USERPROFILE?: string;\n        LOCALAPPDATA?: string;\n      };\n      pathSep: string;\n    };\n  }\n}\n\n// Get platform info from nodeAPI bridge or fallback to browser detection\nfunction getPlatform(): Platform {\n  if (typeof window !== 'undefined' && window.nodeAPI) {\n    return window.nodeAPI.platform;\n  }\n  \n  // Fallback to browser detection if nodeAPI is not available\n  if (typeof navigator !== 'undefined') {\n    const userAgent = navigator.userAgent;\n    if (userAgent.includes('Win')) return 'win32';\n    if (userAgent.includes('Mac')) return 'darwin';\n    if (userAgent.includes('Linux')) return 'linux';\n  }\n  \n  return 'unknown';\n}\n\n// Get home directory from nodeAPI bridge\nfunction getHomeDir(): string {\n  if (typeof window !== 'undefined' && window.nodeAPI) {\n    return window.nodeAPI.homedir || '';\n  }\n  return '';\n}\n\n// Get environment variables from nodeAPI bridge\nfunction getEnvVar(name: string): string {\n  if (typeof window !== 'undefined' && window.nodeAPI && window.nodeAPI.env) {\n    // Use type assertion to bypass TypeScript's index signature check\n    return (window.nodeAPI.env as Record<string, string | undefined>)[name] || '';\n  }\n  return '';\n}\n\n// Default Chrome paths by platform\nconst CHROME_PATHS: Record<Platform, string[]> = {\n  darwin: [\n    '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n    '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',\n    '/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',\n    '/Applications/Chromium.app/Contents/MacOS/Chromium',\n  ],\n  win32: [\n    'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n    'C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n    `${getEnvVar('LOCALAPPDATA')}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n    'C:\\\\Program Files\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe', // Edge as fallback\n  ],\n  linux: [\n    '/usr/bin/google-chrome',\n    '/usr/bin/google-chrome-stable',\n    '/opt/google/chrome/chrome',\n    '/usr/bin/chromium-browser',\n    '/usr/bin/chromium',\n    '/snap/bin/chromium',\n  ]\n};\n\n// Default user data directories by platform\nconst USER_DATA_DIRS: Record<Platform, (homeDir: string) => string> = {\n  darwin: (homeDir: string) => `${homeDir}/Library/Application Support/Google/Chrome`,\n  win32: (homeDir: string) => `${homeDir}\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\User Data`,\n  linux: (homeDir: string) => `${homeDir}/.config/google-chrome`\n};\n\n// Determine Chrome path based on OS\nfunction getChromePath(): string {\n  // Get platform from nodeAPI\n  const currentPlatform = getPlatform();\n  console.log('Detected platform:', currentPlatform);\n  \n  const paths = CHROME_PATHS[currentPlatform] || CHROME_PATHS.linux;\n  console.log('Potential Chrome paths:', paths);\n  \n  // Try direct check in Node environment\n  if (typeof window === 'undefined' || typeof require !== 'undefined') {\n    try {\n      // Import fs dynamically to avoid linter errors\n      // eslint-disable-next-line @typescript-eslint/no-var-requires\n      const fs = require('fs');\n      console.log('Checking if Chrome paths exist using Node.js fs module');\n      for (const path of paths) {\n        try {\n          if (fs.existsSync(path)) {\n            console.log('Found Chrome at:', path);\n            return path;\n          }\n        } catch (e) {\n          console.warn(`Error checking Chrome path ${path}:`, e);\n        }\n      }\n    } catch (e) {\n      console.warn('Could not use fs module to check paths:', e);\n    }\n  }\n  \n  // In browser context or if no valid path found, return the first path for the platform\n  console.log('Using default Chrome path for platform:', paths[0]);\n  return paths[0];\n}\n\n// Get default user data directory based on OS\nfunction getUserDataDir(): string {\n  const homeDir = getHomeDir() || getEnvVar('HOME') || getEnvVar('USERPROFILE') || '';\n  const currentPlatform = getPlatform();\n  \n  const dirFn = USER_DATA_DIRS[currentPlatform] || USER_DATA_DIRS.linux;\n  return dirFn(homeDir);\n}\n\n// Basic Chrome command class - actual initialization happens in main.ts\nclass ChromeCommand {\n  private _path = '';\n  private _args = [\n    '--remote-debugging-port=9222',\n    '--window-position=0,0',\n    '--disable-web-security'\n  ];\n\n  get path(): string {\n    return this._path;\n  }\n\n  set path(value: string) {\n    this._path = value;\n  }\n\n  get args(): string[] {\n    return this._args;\n  }\n\n  set args(value: string[]) {\n    this._args = value;\n  }\n\n  get display(): string {\n    return `${this.path} ${this.args.join(' ')}`;\n  }\n}\n\nexport const chromeCommand = new ChromeCommand();\n"
  },
  {
    "path": "src/index.css",
    "content": "body {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,\n    Arial, sans-serif;\n  margin: auto;\n  max-width: 38rem;\n  padding: 2rem;\n}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { app, BrowserWindow, ipcMain, screen } from 'electron';\nimport path from 'node:path';\nimport { spawn, ChildProcess } from 'child_process';\nimport started from 'electron-squirrel-startup';\nimport { pythonCommand, chromeCommand } from './config';\nimport fs from 'fs';\nimport os from 'os';\nimport http from 'http';\n\n// Handle creating/removing shortcuts on Windows when installing/uninstalling.\nif (started) {\n  app.quit();\n}\n\nlet pyProcess: ChildProcess | null = null;\nlet chromeProcess: ChildProcess | null = null;\n\n// Clear any potentially existing IPC handlers\nfunction clearIPCHandlers() {\n  ipcMain.removeAllListeners('restart-python');\n  ipcMain.removeAllListeners('restart-chrome');\n}\n\n// Create required directories\nfunction createRequiredDirectories() {\n  const baseDir = path.join(os.homedir(), 'Downloads', 'browser-use');\n  const dirs = ['recordings', 'traces', 'history'];\n  \n  dirs.forEach(dir => {\n    const dirPath = path.join(baseDir, dir);\n    if (!fs.existsSync(dirPath)) {\n      fs.mkdirSync(dirPath, { recursive: true });\n    }\n  });\n}\n\n// Load default settings from JSON\nfunction loadDefaultSettings() {\n  const settingsPath = path.join(app.getAppPath(), 'lib/web-ui/tmp/webui_settings/31ccfe5a-ef3b-4064-836c-6910ab3a3281.json');\n  try {\n    if (fs.existsSync(settingsPath)) {\n      const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));\n      return settings;\n    }\n  } catch (e) {\n    console.error('Error loading default settings:', e);\n  }\n  return null;\n}\n\nconst createWindow = () => {\n  // Make sure we clean up any existing handlers first\n  clearIPCHandlers();\n  \n  // Get the primary display dimensions\n  const primaryDisplay = screen.getPrimaryDisplay();\n  const { width, height } = primaryDisplay.workAreaSize;\n  \n  // Calculate half width for positioning\n  const halfWidth = Math.floor(width / 2);\n  \n  // Create the browser window positioned on left half\n  const mainWindow = new BrowserWindow({\n    width: halfWidth,\n    height: height,\n    x: 0,\n    y: 0,\n    frame: false, // Remove window frame\n    titleBarStyle: 'hidden', // Hide title bar on macOS\n    backgroundColor: '#000000', // Set black background\n    webPreferences: {\n      preload: path.join(__dirname, 'preload.js'),\n      contextIsolation: true,\n      nodeIntegration: false,\n      webviewTag: true, // Enable webview tag\n      sandbox: false, // Disable sandbox for preload script\n    },\n  });\n\n  // Set up dark mode\n  mainWindow.webContents.on('did-finish-load', () => {\n    mainWindow.webContents.insertCSS(`\n      body {\n        background-color: #000000;\n        color: #ffffff;\n      }\n      * {\n        color-scheme: dark;\n      }\n    `);\n  });\n  \n  // Clean up IPC handlers when window is closed\n  mainWindow.on('closed', () => {\n    clearIPCHandlers();\n  });\n  \n  // Set up IPC handlers for restarting processes\n  ipcMain.on('restart-python', () => {\n    startPyProcess(mainWindow);\n  });\n  \n  ipcMain.on('restart-chrome', () => {\n    startChromeProcess(mainWindow);\n  });\n\n  // and load the index.html of the app.\n  if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {\n    mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);\n  } else {\n    mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));\n  }\n\n  // Open the DevTools.\n  mainWindow.webContents.openDevTools();\n\n  // Load default settings and pass them to the web UI\n  const defaultSettings = loadDefaultSettings();\n  if (defaultSettings) {\n    mainWindow.webContents.on('did-finish-load', () => {\n      mainWindow.webContents.send('load-default-settings', defaultSettings);\n    });\n  }\n\n  // Start the Python process\n  startPyProcess(mainWindow);\n  \n  // Wait a bit before starting Chrome to ensure Python is running first\n  setTimeout(() => {\n    startChromeProcess(mainWindow);\n  }, 1000);\n};\n\nfunction startPyProcess(mainWindow: BrowserWindow) {\n  // Find Chrome path first - needed for environment variables\n  const chromePath = findBestChromeExecutable();\n  if (!chromePath) {\n    console.error('Could not find Chrome for Python process environment');\n  }\n  \n  // Ensure user data directory exists\n  const userDataDir = ensureChromeUserDataDir();\n  \n  // Working directory for the subprocess\n  const options = {\n    cwd: path.join(app.getAppPath(), pythonCommand.workingDir),\n    shell: false,\n    env: {\n      ...process.env,\n      BROWSER_USE_DESKTOP_APP: 'true',\n      CHROME_PATH: chromePath || '',\n      CHROME_CDP: 'http://localhost:9222',\n      CHROME_USER_DATA: userDataDir\n    }\n  };\n\n  // Clear existing process if it exists\n  if (pyProcess) {\n    try {\n      // Remove all listeners first to prevent callback after destroy\n      pyProcess.stdout.removeAllListeners();\n      pyProcess.stderr.removeAllListeners();\n      pyProcess.removeAllListeners();\n      \n      pyProcess.kill();\n    } catch (e) {\n      console.error('Error killing Python process:', e);\n    }\n    pyProcess = null;\n  }\n\n  // Spawn the Python process\n  pyProcess = spawn(pythonCommand.path, pythonCommand.args, options);\n  \n  // Send process output to the renderer\n  pyProcess.stdout.on('data', (data) => {\n    const output = data.toString();\n    if (!mainWindow.isDestroyed()) {\n      mainWindow.webContents.send('py-output', { type: 'stdout', data: output });\n    }\n  });\n  \n  pyProcess.stderr.on('data', (data) => {\n    const output = data.toString();\n    if (!mainWindow.isDestroyed()) {\n      mainWindow.webContents.send('py-output', { type: 'stderr', data: output });\n    }\n  });\n  \n  pyProcess.on('error', (error) => {\n    if (!mainWindow.isDestroyed()) {\n      mainWindow.webContents.send('py-output', { \n        type: 'error', \n        data: `Failed to start subprocess: ${error.message}` \n      });\n    }\n  });\n  \n  pyProcess.on('close', (code) => {\n    if (!mainWindow.isDestroyed()) {\n      mainWindow.webContents.send('py-output', { \n        type: 'info', \n        data: `Python process exited with code ${code}` \n      });\n    }\n    pyProcess = null;\n  });\n  \n  // Notify the renderer the process has started\n  if (!mainWindow.isDestroyed()) {\n    mainWindow.webContents.send('py-started');\n  }\n  \n  // We'll let the renderer detect when the server is ready from the stdout logs\n  // but still send the ready signal after a longer timeout as a fallback\n  setTimeout(() => {\n    if (!mainWindow.isDestroyed()) {\n      mainWindow.webContents.send('py-ready', 'http://127.0.0.1:7788');\n    }\n  }, 10000); // 10 seconds delay as fallback\n}\n\n// This method will be called when Electron has finished\n// initialization and is ready to create browser windows.\n// Some APIs can only be used after this event occurs.\napp.whenReady().then(() => {\n  createRequiredDirectories();\n  createWindow();\n\n  app.on('activate', function () {\n    // On macOS it's common to re-create a window in the app when the\n    // dock icon is clicked and there are no other windows open.\n    if (BrowserWindow.getAllWindows().length === 0) createWindow();\n  });\n});\n\n// Quit when all windows are closed, except on macOS. There, it's common\n// for applications and their menu bar to stay active until the user quits\n// explicitly with Cmd + Q.\napp.on('window-all-closed', () => {\n  if (process.platform !== 'darwin') {\n    app.quit();\n  }\n});\n\n// Kill the Python and Chrome processes before the app quits\napp.on('before-quit', (event) => {\n  // Prevent the default quit behavior so we can clean up first\n  event.preventDefault();\n  \n  // Counter to track cleanup completion\n  let cleanupCounter = 0;\n  const processesToCleanup = (pyProcess ? 1 : 0) + (chromeProcess ? 1 : 0);\n  \n  if (processesToCleanup === 0) {\n    // No processes to clean up, safe to quit immediately\n    app.exit(0);\n    return;\n  }\n  \n  // Set a safety timeout to force quit after 1 second if processes don't exit properly\n  const forceQuitTimeout = setTimeout(() => {\n    console.warn('Force quitting after timeout');\n    app.exit(0);\n  }, 1000);\n  \n  // Function to check if cleanup is done\n  const checkCleanup = () => {\n    cleanupCounter++;\n    if (cleanupCounter >= processesToCleanup) {\n      // All processes cleaned up, now safe to exit\n      clearTimeout(forceQuitTimeout); // Clear the force quit timeout\n      app.exit(0);\n    }\n  };\n  \n  // Clean up Python process\n  if (pyProcess) {\n    try {\n      // Kill the process immediately\n      pyProcess.kill('SIGKILL');\n      pyProcess = null;\n      checkCleanup();\n    } catch (e) {\n      console.error('Error killing Python process:', e);\n      pyProcess = null;\n      checkCleanup();\n    }\n  }\n  \n  // Clean up Chrome process\n  if (chromeProcess) {\n    try {\n      // Kill the process immediately\n      chromeProcess.kill('SIGKILL');\n      chromeProcess = null;\n      checkCleanup();\n    } catch (e) {\n      console.error('Error killing Chrome process:', e);\n      chromeProcess = null;\n      checkCleanup();\n    }\n  }\n});\n\nfunction startChromeProcess(mainWindow: BrowserWindow) {\n  // Clear existing process if it exists\n  if (chromeProcess) {\n    try {\n      // Remove all listeners first to prevent callback after destroy\n      chromeProcess.stdout.removeAllListeners();\n      chromeProcess.stderr.removeAllListeners();\n      chromeProcess.removeAllListeners();\n      \n      chromeProcess.kill();\n    } catch (e) {\n      console.error('Error killing Chrome process:', e);\n    }\n    chromeProcess = null;\n  }\n\n  // Find the best Chrome executable for the current OS\n  const chromePath = findBestChromeExecutable();\n  if (!chromePath) {\n    const errorMsg = 'Could not find a valid Chrome executable. Please install Google Chrome and try again.';\n    console.error(errorMsg);\n    mainWindow.webContents.send('chrome-output', { type: 'error', data: errorMsg });\n    return;\n  }\n  \n  // Update chromeCommand with the found path\n  chromeCommand.path = chromePath;\n  \n  // Ensure Chrome user data directory exists\n  const userDataDir = ensureChromeUserDataDir();\n  \n  // Get screen dimensions for window positioning\n  const primaryDisplay = screen.getPrimaryDisplay();\n  const { width, height } = primaryDisplay.workAreaSize;\n  const halfWidth = Math.floor(width / 2);\n  \n  // Compose Chrome arguments with the validated user data directory\n  const args = [\n    '--remote-debugging-port=9222',\n    `--window-position=${halfWidth},0`,\n    `--window-size=${halfWidth},${height}`,\n    '--install-autogenerated-theme=0,0,0', // Theme setting\n    '--disable-web-security',\n    `--user-data-dir=${userDataDir}`,\n    '--profile-directory=Default',\n    '--no-first-run',\n    '--no-default-browser-check'\n  ];\n  \n  // Add compatibility flags\n  args.push('--disable-features=TranslateUI');\n  args.push('--disable-extensions');\n  \n  // Add minimal necessary flags for stability based on platform\n  if (process.platform === 'linux') {\n    // Linux often needs these flags\n    args.push('--no-sandbox');\n    args.push('--disable-gpu');\n  }\n  \n  // Store the args in chromeCommand for other parts of the app\n  chromeCommand.args = args;\n  \n  // Log Chrome launch information\n  console.log('Launching Chrome with path:', chromePath);\n  console.log('Chrome arguments:', args);\n  \n  try {\n    // Spawn the Chrome process\n    chromeProcess = spawn(chromePath, args);\n    \n    if (!chromeProcess || !chromeProcess.pid) {\n      const errorMsg = `Failed to launch Chrome process - could not start the process`;\n      console.error(errorMsg);\n      mainWindow.webContents.send('chrome-output', { type: 'error', data: errorMsg });\n      return;\n    }\n    \n    console.log(`Chrome process started with PID: ${chromeProcess.pid}`);\n    \n    // Set up Chrome process event listeners\n    chromeProcess.stdout.on('data', (data) => {\n      const output = data.toString();\n      console.log('Chrome stdout:', output);\n      if (!mainWindow.isDestroyed()) {\n        mainWindow.webContents.send('chrome-output', { type: 'stdout', data: output });\n      }\n    });\n    \n    chromeProcess.stderr.on('data', (data) => {\n      const output = data.toString();\n      console.log('Chrome stderr:', output);\n      if (!mainWindow.isDestroyed()) {\n        mainWindow.webContents.send('chrome-output', { type: 'stderr', data: output });\n      }\n    });\n    \n    chromeProcess.on('error', (error) => {\n      console.error('Chrome process error:', error.message);\n      if (!mainWindow.isDestroyed()) {\n        mainWindow.webContents.send('chrome-output', { \n          type: 'error', \n          data: `Failed to start Chrome: ${error.message}` \n        });\n      }\n    });\n    \n    chromeProcess.on('close', (code) => {\n      console.log(`Chrome process exited with code ${code}`);\n      if (!mainWindow.isDestroyed()) {\n        mainWindow.webContents.send('chrome-output', { \n          type: 'info', \n          data: `Chrome process exited with code ${code}` \n        });\n      }\n      chromeProcess = null;\n    });\n    \n    // Notify the renderer the process has started\n    if (!mainWindow.isDestroyed()) {\n      mainWindow.webContents.send('chrome-started');\n    }\n    \n    // Verify Chrome is listening on debug port\n    setTimeout(() => {\n      verifyChromeLaunched(mainWindow);\n    }, 2000);\n  } catch (error) {\n    const errorMsg = `Unexpected error launching Chrome: ${error}`;\n    console.error(errorMsg);\n    if (!mainWindow.isDestroyed()) {\n      mainWindow.webContents.send('chrome-output', { type: 'error', data: errorMsg });\n    }\n  }\n}\n\n/**\n * Verifies that Chrome launched successfully and is accessible via debug port\n */\nfunction verifyChromeLaunched(mainWindow: BrowserWindow): void {\n  console.log('Verifying Chrome is accessible on debug port...');\n  \n  try {\n    // Try to connect to Chrome's debug port\n    const options = {\n      host: '127.0.0.1',\n      port: 9222,\n      path: '/json/version',\n      timeout: 2000\n    };\n    \n    const req = http.get(options, (res) => {\n      if (res.statusCode === 200) {\n        console.log('Chrome debug port connection successful');\n        \n        let data = '';\n        res.on('data', (chunk) => { data += chunk; });\n        res.on('end', () => {\n          try {\n            const info = JSON.parse(data);\n            console.log('Chrome debug info:', info);\n            \n            if (!mainWindow.isDestroyed()) {\n              mainWindow.webContents.send('chrome-output', { \n                type: 'info', \n                data: `Chrome ready - ${info.Browser || 'Chrome'} [${info.Protocol || 'CDP'}]` \n              });\n            }\n          } catch (e) {\n            console.error('Error parsing Chrome debug info:', e);\n          }\n        });\n      } else {\n        console.error(`Chrome debug port returned status code: ${res.statusCode}`);\n        if (!mainWindow.isDestroyed()) {\n          mainWindow.webContents.send('chrome-output', { \n            type: 'warning', \n            data: `Chrome may not be fully initialized (status: ${res.statusCode})` \n          });\n        }\n      }\n    });\n    \n    req.on('error', (e) => {\n      console.error('Error connecting to Chrome debug port:', e.message);\n      if (!mainWindow.isDestroyed()) {\n        mainWindow.webContents.send('chrome-output', { \n          type: 'warning', \n          data: `Chrome debug port not accessible: ${e.message}. The browser may still be starting.` \n        });\n      }\n    });\n    \n    req.end();\n  } catch (e) {\n    console.error('Error verifying Chrome launch:', e);\n  }\n}\n\n/**\n * Finds the best Chrome executable for the current OS\n * @returns The path to the Chrome executable or null if not found\n */\nfunction findBestChromeExecutable(): string | null {\n  // Define platform-specific paths in order of preference\n  const paths: string[] = [];\n  \n  if (process.platform === 'darwin') {\n    // macOS paths\n    paths.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome');\n    paths.push('/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta');\n    paths.push('/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev');\n    paths.push('/Applications/Chromium.app/Contents/MacOS/Chromium');\n    // Add fallback for homebrew installations\n    paths.push('/opt/homebrew/bin/chromium');\n  } else if (process.platform === 'win32') {\n    // Windows paths\n    paths.push('C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe');\n    paths.push('C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe');\n    \n    // Add LocalAppData path if available\n    const localAppData = process.env.LOCALAPPDATA;\n    if (localAppData) {\n      paths.push(`${localAppData}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`);\n    }\n    \n    // Microsoft Edge as fallback (Chromium-based)\n    paths.push('C:\\\\Program Files\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe');\n    paths.push('C:\\\\Program Files (x86)\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe');\n  } else {\n    // Linux paths\n    paths.push('/usr/bin/google-chrome');\n    paths.push('/usr/bin/google-chrome-stable');\n    paths.push('/opt/google/chrome/chrome');\n    paths.push('/usr/bin/chromium-browser');\n    paths.push('/usr/bin/chromium');\n    paths.push('/snap/bin/chromium');\n  }\n  \n  console.log('Checking for Chrome at these paths:', paths);\n  \n  // Find first existing path\n  for (const path of paths) {\n    if (fs.existsSync(path)) {\n      console.log(`Found Chrome executable at: ${path}`);\n      return path;\n    }\n  }\n  \n  console.error('No Chrome executable found in common locations');\n  return null;\n}\n\n/**\n * Ensures the Chrome user data directory exists\n * @returns The path to the Chrome user data directory\n */\nfunction ensureChromeUserDataDir(): string {\n  // Determine user data directory based on platform\n  let userDataDir: string;\n  const homeDir = os.homedir();\n  const appName = 'browser-use-desktop';\n  \n  if (process.platform === 'darwin') {\n    userDataDir = `${homeDir}/Library/Application Support/${appName}/ChromeProfile`;\n  } else if (process.platform === 'win32') {\n    userDataDir = `${homeDir}\\\\AppData\\\\Local\\\\${appName}\\\\ChromeProfile`;\n  } else {\n    userDataDir = `${homeDir}/.config/${appName}/ChromeProfile`;\n  }\n  \n  // Log the user data directory path\n  console.log(`Using Chrome user data directory: ${userDataDir}`);\n  \n  // Ensure the directory exists\n  try {\n    if (!fs.existsSync(userDataDir)) {\n      fs.mkdirSync(userDataDir, { recursive: true });\n      console.log(`Created Chrome user data directory: ${userDataDir}`);\n      \n      // Create first_run file to prevent first run experience\n      const firstRunPath = path.join(userDataDir, 'First Run');\n      fs.writeFileSync(firstRunPath, '');\n      console.log('Created First Run file to disable welcome screen');\n      \n      // Create an empty Preferences file with basic settings\n      const prefsPath = path.join(userDataDir, 'Default', 'Preferences');\n      \n      // Create Default directory if it doesn't exist\n      const defaultDir = path.join(userDataDir, 'Default');\n      if (!fs.existsSync(defaultDir)) {\n        fs.mkdirSync(defaultDir, { recursive: true });\n      }\n      \n      // Basic preferences to disable welcome page, etc.\n      const defaultPrefs = {\n        browser: {\n          custom_chrome_frame: false,\n          check_default_browser: false\n        },\n        profile: {\n          default_content_setting_values: {\n            notifications: 2 // Block notifications: 1=allow, 2=block, 3=ask\n          }\n        },\n        session: {\n          restore_on_startup: 5 // Don't restore anything: 5=open new tab\n        },\n        bookmark_bar: {\n          show_on_all_tabs: false\n        },\n        distribution: {\n          import_bookmarks: false,\n          import_history: false,\n          import_search_engine: false,\n          make_chrome_default: false,\n          show_welcome_page: false,\n          skip_first_run_ui: true\n        }\n      };\n      \n      fs.writeFileSync(prefsPath, JSON.stringify(defaultPrefs, null, 2));\n      console.log('Created default Chrome preferences file');\n    } else {\n      console.log(`Using existing Chrome user data directory: ${userDataDir}`);\n    }\n  } catch (err) {\n    console.error(`Error creating Chrome user data directory: ${err}`);\n    // Fallback to a temporary directory\n    userDataDir = path.join(os.tmpdir(), `${appName}-chrome-profile`);\n    fs.mkdirSync(userDataDir, { recursive: true });\n    console.log(`Using fallback Chrome user data directory: ${userDataDir}`);\n  }\n  \n  return userDataDir;\n}\n\n// In this file you can include the rest of your app's specific main process\n// code. You can also put them in separate files and import them here.\n"
  },
  {
    "path": "src/preload.ts",
    "content": "// See the Electron documentation for details on how to use preload scripts:\n// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts\n\nimport { contextBridge, ipcRenderer } from 'electron';\nimport * as os from 'os';\nimport * as fs from 'fs';\n\n// Define types for the bridges\ntype PyOutputCallback = (data: { type: string; data: string }) => void;\ntype PyStartedCallback = () => void;\ntype PyReadyCallback = (url: string) => void;\ntype ChromeOutputCallback = (data: { type: string; data: string }) => void;\ntype ChromeStartedCallback = () => void;\ntype SettingsCallback = (settings: Record<string, any>) => void;\n\n// Expose protected methods that allow the renderer process to use\n// the ipcRenderer without exposing the entire object\ncontextBridge.exposeInMainWorld('pyBridge', {\n  // Listen for messages from the main process\n  onPyOutput: (callback: PyOutputCallback) => {\n    ipcRenderer.on('py-output', (_, data) => callback(data));\n  },\n  \n  onPyStarted: (callback: PyStartedCallback) => {\n    ipcRenderer.on('py-started', () => callback());\n  },\n  \n  onPyReady: (callback: PyReadyCallback) => {\n    ipcRenderer.on('py-ready', (_, url) => callback(url));\n  },\n  \n  // Restart the Python process\n  restartPython: () => {\n    ipcRenderer.send('restart-python');\n  },\n  \n  // Clean up event listeners\n  removeAllListeners: () => {\n    ipcRenderer.removeAllListeners('py-output');\n    ipcRenderer.removeAllListeners('py-started');\n    ipcRenderer.removeAllListeners('py-ready');\n  }\n});\n\n// Expose Chrome bridge for communicating Chrome process information\ncontextBridge.exposeInMainWorld('chromeBridge', {\n  // Listen for messages from the main process\n  onChromeOutput: (callback: ChromeOutputCallback) => {\n    ipcRenderer.on('chrome-output', (_, data) => callback(data));\n  },\n  \n  onChromeStarted: (callback: ChromeStartedCallback) => {\n    ipcRenderer.on('chrome-started', () => callback());\n  },\n  \n  // Restart the Chrome process\n  restartChrome: () => {\n    ipcRenderer.send('restart-chrome');\n  },\n  \n  // Clean up event listeners\n  removeAllListeners: () => {\n    ipcRenderer.removeAllListeners('chrome-output');\n    ipcRenderer.removeAllListeners('chrome-started');\n  }\n});\n\n// Expose settings bridge for handling default settings\ncontextBridge.exposeInMainWorld('settingsBridge', {\n  // Listen for default settings from the main process\n  onDefaultSettings: (callback: SettingsCallback) => {\n    ipcRenderer.on('load-default-settings', (_, settings) => callback(settings));\n  },\n  \n  // Clean up event listeners\n  removeAllListeners: () => {\n    ipcRenderer.removeAllListeners('load-default-settings');\n  }\n});\n\n// Expose Node.js platform-specific APIs needed by the renderer\ncontextBridge.exposeInMainWorld('nodeAPI', {\n  platform: os.platform(),\n  homedir: os.homedir(),\n  env: {\n    HOME: process.env.HOME,\n    USERPROFILE: process.env.USERPROFILE,\n    LOCALAPPDATA: process.env.LOCALAPPDATA\n  },\n  pathSep: process.platform === 'win32' ? '\\\\' : '/'\n});\n\n"
  },
  {
    "path": "src/renderer.ts",
    "content": "/**\n * This file will automatically be loaded by vite and run in the \"renderer\" context.\n * To learn more about the differences between the \"main\" and the \"renderer\" context in\n * Electron, visit:\n *\n * https://electronjs.org/docs/tutorial/process-model\n */\n\nimport './index.css';\nimport { pythonCommand, chromeCommand } from './config';\n\n// Define interface for Python bridge\ninterface PyBridge {\n  onPyOutput: (callback: (data: { type: string; data: string }) => void) => void;\n  onPyStarted: (callback: () => void) => void;\n  onPyReady: (callback: (url: string) => void) => void;\n  restartPython: () => void;\n  removeAllListeners: () => void;\n}\n\n// Define interface for Chrome bridge\ninterface ChromeBridge {\n  onChromeOutput: (callback: (data: { type: string; data: string }) => void) => void;\n  onChromeStarted: (callback: () => void) => void;\n  restartChrome: () => void;\n  removeAllListeners: () => void;\n}\n\n// Access the exposed API from preload script\ndeclare global {\n  interface Window {\n    pyBridge: PyBridge;\n    chromeBridge: ChromeBridge;\n    settingsBridge: {\n      onDefaultSettings: (callback: (settings: any) => void) => void;\n    };\n  }\n}\n\n// DOM Elements\nconst consoleOutput = document.getElementById('console-output') as HTMLDivElement;\nconst consoleContent = document.getElementById('console-content') as HTMLDivElement;\nconst chromeConsoleContent = document.getElementById('chrome-console-content') as HTMLDivElement;\nconst loadingProgress = document.getElementById('loading-progress') as HTMLDivElement;\nconst loadingContainer = document.getElementById('loading-container') as HTMLDivElement;\nconst webviewContainer = document.getElementById('webview-container') as HTMLDivElement;\nconst webview = document.getElementById('webview') as HTMLElement;\nconst launchButton = document.getElementById('launch-button') as HTMLButtonElement;\nconst controls = document.getElementById('controls') as HTMLDivElement;\nconst toggleConsoleButton = document.getElementById('toggle-console') as HTMLButtonElement;\nconst closeConsoleButton = document.getElementById('close-console') as HTMLSpanElement;\nconst tabPython = document.getElementById('tab-python') as HTMLButtonElement;\nconst tabChrome = document.getElementById('tab-chrome') as HTMLButtonElement;\nconst restartPythonButton = document.getElementById('restart-python') as HTMLButtonElement;\nconst restartChromeButton = document.getElementById('restart-chrome') as HTMLButtonElement;\nconst pythonCommandDisplay = document.getElementById('python-command') as HTMLDivElement;\nconst chromeCommandDisplay = document.getElementById('chrome-command') as HTMLDivElement;\nconst actionRows = document.querySelectorAll('.action-row') as NodeListOf<HTMLDivElement>;\n\n// Variables to track loading\nlet progressValue = 0;\nlet progressInterval: number | null = null;\nlet serverUrl = '';\n\n// Function to append output to the console\nfunction appendToConsole(message: string, type: string, target: HTMLElement = consoleContent): void {\n  const element = document.createElement('div');\n  element.className = type;\n  element.textContent = message;\n  target.appendChild(element);\n  target.scrollTop = target.scrollHeight;\n}\n\n// Function to simulate loading progress\nfunction simulateProgress(): void {\n  progressInterval = window.setInterval(() => {\n    if (progressValue < 90) {\n      progressValue += Math.random() * 10;\n      loadingProgress.style.width = `${progressValue}%`;\n    }\n  }, 300);\n}\n\n// Function to complete the loading progress\nfunction completeProgress(): void {\n  clearInterval(progressInterval!);\n  progressValue = 100;\n  loadingProgress.style.width = '100%';\n  \n  // Hide loading container after a short delay\n  setTimeout(() => {\n    loadingContainer.style.display = 'none';\n    // Don't show the launch button if we're auto-loading the UI\n    if (!serverUrl) {\n      launchButton.style.display = 'block';\n    }\n  }, 500);\n}\n\n// Toggle console visibility\nfunction toggleConsole(): void {\n  // Use classList to toggle visibility\n  const isConsoleVisible = consoleOutput.classList.contains('visible');\n  \n  if (isConsoleVisible) {\n    consoleOutput.classList.remove('visible');\n    toggleConsoleButton.textContent = 'Show Console';\n  } else {\n    consoleOutput.classList.add('visible');\n    toggleConsoleButton.textContent = 'Hide Console';\n    // Scroll to the latest output\n    const activeTab = tabPython.classList.contains('active') ? consoleContent : chromeConsoleContent;\n    activeTab.scrollTop = activeTab.scrollHeight;\n  }\n}\n\n// Function to switch between console tabs\nfunction switchConsoleTab(tab: 'python' | 'chrome'): void {\n  if (tab === 'python') {\n    tabPython.classList.add('active');\n    tabChrome.classList.remove('active');\n    consoleContent.style.display = 'block';\n    chromeConsoleContent.style.display = 'none';\n    consoleContent.scrollTop = consoleContent.scrollHeight;\n    // Show Python action row, hide Chrome action row\n    actionRows[0].style.display = 'flex';\n    actionRows[1].style.display = 'none';\n  } else {\n    tabPython.classList.remove('active');\n    tabChrome.classList.add('active');\n    consoleContent.style.display = 'none';\n    chromeConsoleContent.style.display = 'block';\n    chromeConsoleContent.scrollTop = chromeConsoleContent.scrollHeight;\n    // Hide Python action row, show Chrome action row\n    actionRows[0].style.display = 'none';\n    actionRows[1].style.display = 'flex';\n  }\n}\n\n// Function to load the web UI\nfunction loadWebUI(url: string): void {\n  // Force the correct type for webview element\n  const webviewElement = document.querySelector('webview') as Electron.WebviewTag;\n  if (webviewElement) {\n    webviewElement.src = url;\n    \n    // Handle webview events\n    webviewElement.addEventListener('dom-ready', () => {\n      console.log('WebView DOM ready');\n    });\n    \n    webviewElement.addEventListener('did-start-loading', () => {\n      console.log('WebView started loading');\n    });\n    \n    webviewElement.addEventListener('did-finish-load', () => {\n      console.log('WebView finished loading');\n      // Remove loading class when webview is loaded\n      document.body.classList.remove('loading');\n    });\n    \n    webviewElement.addEventListener('did-fail-load', (e) => {\n      console.error('WebView failed to load:', e);\n      // Show error and console if webview fails to load\n      appendToConsole(`Failed to load webview: ${JSON.stringify(e)}`, 'error');\n      consoleOutput.classList.add('visible');\n      toggleConsoleButton.textContent = 'Hide Console';\n      // Scroll to the error\n      consoleContent.scrollTop = consoleContent.scrollHeight;\n    });\n  }\n  \n  // Show the webview and controls, hide other elements\n  webviewContainer.style.display = 'block';\n  controls.style.display = 'block';\n  loadingContainer.style.display = 'none';\n  launchButton.style.display = 'none';\n  \n  // Keep console visible until the UI fully loads, then hide it with a delay\n  // This is handled in the event listeners in the init function\n}\n\n// Initialize the app\nfunction init(): void {\n  // Start simulating progress\n  simulateProgress();\n  \n  // Show console automatically during loading\n  consoleOutput.classList.add('visible');\n  \n  // Set the command displays\n  pythonCommandDisplay.textContent = pythonCommand.display;\n  chromeCommandDisplay.textContent = chromeCommand.display;\n  \n  // Listen for Python process output\n  window.pyBridge.onPyOutput((data) => {\n    appendToConsole(data.data, data.type);\n    \n    // Automatically detect when server is ready from the output\n    if (data.data.includes('Server is ready') || data.data.includes('Running on http://')) {\n      if (!serverUrl) {\n        serverUrl = 'http://127.0.0.1:7788';\n        appendToConsole(`Detected server is ready at: ${serverUrl}`, 'info');\n        completeProgress();\n        loadWebUI(serverUrl);\n        \n        // Hide console after server is ready and web UI is loaded\n        setTimeout(() => {\n          consoleOutput.classList.remove('visible');\n          toggleConsoleButton.textContent = 'Show Console';\n        }, 1000); // Small delay to allow users to see the \"Server is ready\" message\n      }\n    }\n  });\n  \n  // Listen for Python process started event\n  window.pyBridge.onPyStarted(() => {\n    appendToConsole('Python process started', 'info');\n  });\n  \n  // Listen for Python ready event\n  window.pyBridge.onPyReady((url) => {\n    serverUrl = url;\n    appendToConsole(`Server is ready at: ${url}`, 'info');\n    completeProgress();\n    // Automatically load the web UI\n    loadWebUI(url);\n    \n    // Hide console after server is ready and web UI is loaded\n    setTimeout(() => {\n      consoleOutput.classList.remove('visible');\n      toggleConsoleButton.textContent = 'Show Console';\n    }, 1000); // Small delay to allow users to see the \"Server is ready\" message\n  });\n  \n  // Listen for Chrome process output\n  window.chromeBridge.onChromeOutput((data) => {\n    appendToConsole(data.data, data.type, chromeConsoleContent);\n  });\n  \n  // Listen for Chrome process started event\n  window.chromeBridge.onChromeStarted(() => {\n    appendToConsole('Chrome process started', 'info', chromeConsoleContent);\n  });\n  \n  // Setup tab switching\n  tabPython.addEventListener('click', () => {\n    switchConsoleTab('python');\n    restartPythonButton.style.display = 'block';\n    restartChromeButton.style.display = 'none';\n  });\n  \n  tabChrome.addEventListener('click', () => {\n    switchConsoleTab('chrome');\n    restartPythonButton.style.display = 'none';\n    restartChromeButton.style.display = 'block';\n  });\n  \n  // Setup restart buttons\n  restartPythonButton.addEventListener('click', () => {\n    // Clear console\n    consoleContent.innerHTML = '';\n    \n    // Add message about restart\n    appendToConsole('Restarting Python process...', 'info');\n    \n    // Restart the process\n    window.pyBridge.restartPython();\n  });\n  \n  restartChromeButton.addEventListener('click', () => {\n    // Clear console\n    chromeConsoleContent.innerHTML = '';\n    \n    // Add message about restart\n    appendToConsole('Restarting Chrome process...', 'info', chromeConsoleContent);\n    \n    // Restart the process\n    window.chromeBridge.restartChrome();\n  });\n  \n  // Setup launch button click handler\n  launchButton.addEventListener('click', () => {\n    loadWebUI(serverUrl);\n  });\n  \n  // Setup toggle console button\n  toggleConsoleButton.addEventListener('click', toggleConsole);\n  \n  // Setup close console button\n  closeConsoleButton.addEventListener('click', () => {\n    consoleOutput.classList.remove('visible');\n    toggleConsoleButton.textContent = 'Show Console';\n  });\n}\n\n// Initialize the app when the DOM is loaded\ndocument.addEventListener('DOMContentLoaded', init);\n\n// Clean up event listeners when window is closed\nwindow.addEventListener('beforeunload', () => {\n  window.pyBridge.removeAllListeners();\n  window.chromeBridge.removeAllListeners();\n  if (progressInterval) {\n    clearInterval(progressInterval);\n  }\n});\n"
  },
  {
    "path": "src/types/electron-squirrel-startup.d.ts",
    "content": "declare module 'electron-squirrel-startup' {\n  const started: boolean;\n  export default started;\n} \n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"commonjs\",\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"noImplicitAny\": true,\n    \"sourceMap\": true,\n    \"baseUrl\": \".\",\n    \"outDir\": \"dist\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true\n  }\n}\n"
  },
  {
    "path": "vite.main.config.ts",
    "content": "import { defineConfig } from 'vite';\n\n// https://vitejs.dev/config\nexport default defineConfig({});\n"
  },
  {
    "path": "vite.preload.config.ts",
    "content": "import { defineConfig } from 'vite';\n\n// https://vitejs.dev/config\nexport default defineConfig({});\n"
  },
  {
    "path": "vite.renderer.config.ts",
    "content": "import { defineConfig } from 'vite';\n\n// https://vitejs.dev/config\nexport default defineConfig({});\n"
  }
]