Full Code of browser-use/desktop for AI

main 600a8b3c88e2 cached
17 files
53.5 KB
13.7k tokens
40 symbols
1 requests
Download .txt
Repository: browser-use/desktop
Branch: main
Commit: 600a8b3c88e2
Files: 17
Total size: 53.5 KB

Directory structure:
gitextract_ja6d5fvp/

├── .eslintrc.json
├── .gitignore
├── README.md
├── forge.config.ts
├── forge.env.d.ts
├── index.html
├── package.json
├── src/
│   ├── config.ts
│   ├── index.css
│   ├── main.ts
│   ├── preload.ts
│   ├── renderer.ts
│   └── types/
│       └── electron-squirrel-startup.d.ts
├── tsconfig.json
├── vite.main.config.ts
├── vite.preload.config.ts
└── vite.renderer.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.json
================================================
{
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:import/recommended",
    "plugin:import/electron",
    "plugin:import/typescript"
  ],
  "parser": "@typescript-eslint/parser"
}


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock
.DS_Store

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# Webpack
.webpack/

# Vite
.vite/

# Electron-Forge
out/


================================================
FILE: README.md
================================================

<img src="./assets/desktop.png" alt="Browser Use Desktop App" width="full"/>

<br/>

[![GitHub stars](https://img.shields.io/github/stars/browser-use/desktop?style=social)](https://github.com/browser-use/desktop/stargazers)
[![Discord](https://img.shields.io/discord/1303749220842340412?color=7289DA&label=Discord&logo=discord&logoColor=white)](https://link.browser-use.com/discord)
[![Documentation](https://img.shields.io/badge/Documentation-📕-blue)](https://docs.browser-use.com)
[![Browser-Use](https://img.shields.io/twitter/follow/browser_use?style=social)](https://x.com/browser_use)

This 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).

**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.

**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.

**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.

<img width="100%" alt="Screenshot of Browser Use Desktop running on macOS" src="https://github.com/user-attachments/assets/94e9fec1-7b17-453e-af6d-2e35f54b46ab" />


## Get Started

```bash
git clone https://github.com/browser-use/desktop
cd desktop

npm install
vite dev
```

## Download

- Download for macOS *(Coming soon...)*
- Download for Windows *(Coming soon...)*
- Download for Linux *(Coming soon...)*


================================================
FILE: forge.config.ts
================================================
import type { ForgeConfig } from '@electron-forge/shared-types';
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
import { VitePlugin } from '@electron-forge/plugin-vite';
import { FusesPlugin } from '@electron-forge/plugin-fuses';
import { FuseV1Options, FuseVersion } from '@electron/fuses';

const config: ForgeConfig = {
  packagerConfig: {
    asar: true,
  },
  rebuildConfig: {},
  makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],
  plugins: [
    new VitePlugin({
      // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
      // If you are familiar with Vite configuration, it will look really familiar.
      build: [
        {
          // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
          entry: 'src/main.ts',
          config: 'vite.main.config.ts',
          target: 'main',
        },
        {
          entry: 'src/preload.ts',
          config: 'vite.preload.config.ts',
          target: 'preload',
        },
      ],
      renderer: [
        {
          name: 'main_window',
          config: 'vite.renderer.config.ts',
        },
      ],
    }),
    // Fuses are used to enable/disable various Electron functionality
    // at package time, before code signing the application
    new FusesPlugin({
      version: FuseVersion.V1,
      [FuseV1Options.RunAsNode]: false,
      [FuseV1Options.EnableCookieEncryption]: true,
      [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
      [FuseV1Options.EnableNodeCliInspectArguments]: false,
      [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
      [FuseV1Options.OnlyLoadAppFromAsar]: true,
    }),
  ],
};

export default config;


================================================
FILE: forge.env.d.ts
================================================
/// <reference types="@electron-forge/plugin-vite/forge-vite-env" />


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Browser-Use Desktop</title>
    <style>
      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
        margin: 0;
        padding: 20px;
        background-color: #f5f5f5;
        color: #333;
      }
      h1 {
        margin-bottom: 20px;
        color: #2c3e50;
      }
      main {
        display: flex;
        flex-direction: column;
        gap: 20px;
      }
      .console-output {
        background-color: #263238;
        color: #eeffff;
        padding: 0;
        border-radius: 5px 5px 0 0;
        height: 300px;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        z-index: 15;
        transform: translateY(100%);
        transition: transform 0.3s ease;
        box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
        display: flex;
        flex-direction: column;
      }
      
      .console-output.visible {
        transform: translateY(0);
      }
      
      /* Adjust layout for loading state */
      body.loading .console-output.visible {
        height: 250px;
      }
      
      .console-header {
        background-color: #1e272c;
        padding: 8px 15px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        border-bottom: 1px solid #37474F;
      }
      
      .console-header h3 {
        margin: 0;
        font-size: 14px;
        font-weight: normal;
        color: #B0BEC5;
      }
      
      .console-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      
      .console-tabs {
        display: flex;
        gap: 8px;
      }
      
      .console-tab {
        background-color: #37474F;
        border: none;
        color: #B0BEC5;
        padding: 5px 10px;
        border-radius: 4px;
        cursor: pointer;
        font-size: 12px;
      }
      
      .console-tab.active {
        background-color: #546E7A;
        color: #ECEFF1;
      }
      
      .console-tab:hover {
        background-color: #455A64;
      }
      
      #console-actions {
        background-color: #1e272c;
        padding: 8px 15px;
        display: flex;
        border-bottom: 1px solid #37474F;
      }
      
      .restart-btn {
        background-color: #455A64;
        color: #ECEFF1;
        border: none;
        border-radius: 4px;
        padding: 6px 10px;
        font-size: 12px;
        cursor: pointer;
        margin-right: 10px;
      }
      
      .restart-btn:hover {
        background-color: #546E7A;
      }
      
      .action-row {
        display: flex;
        align-items: center;
        width: 100%;
      }
      
      .command-display {
        margin-left: 15px;
        font-family: monospace;
        color: #81C784;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        flex: 1;
        font-size: 12px;
      }
      
      .console-content {
        overflow-y: auto;
        padding: 15px;
        font-family: monospace;
        white-space: pre-wrap;
        flex: 1;
        height: 100%;
      }
      
      .command-line {
        color: #81C784;
        font-weight: bold;
        padding: 5px 0;
        border-bottom: 1px solid #37474F;
        margin-bottom: 10px;
      }
      
      .close-btn {
        font-size: 24px;
        color: #B0BEC5;
        cursor: pointer;
        user-select: none;
        line-height: 1;
      }
      
      .close-btn:hover {
        color: #ECEFF1;
      }
      
      #console-content {
        overflow-y: auto;
        padding: 15px;
        font-family: monospace;
        white-space: pre-wrap;
        flex: 1;
      }
      .console-output .stderr {
        color: #ff5252;
      }
      .console-output .info {
        color: #69f0ae;
      }
      .console-output .error {
        color: #ff7043;
      }
      .loading-container {
        margin: 20px 0;
      }
      .loading-bar {
        height: 20px;
        background-color: #ddd;
        border-radius: 10px;
        overflow: hidden;
      }
      .loading-progress {
        height: 100%;
        background-color: #4caf50;
        width: 0%;
        transition: width 0.3s ease;
      }
      .webview-container {
        display: none;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        width: 100%;
        height: 100%;
        z-index: 10;
      }
      
      .controls {
        position: fixed;
        bottom: 10px;
        right: 10px;
        z-index: 20;
        background-color: rgba(0,0,0,0.6);
        border-radius: 5px;
        padding: 8px;
        display: none;
        box-shadow: 0 2px 10px rgba(0,0,0,0.3);
        transition: opacity 0.3s ease;
      }
      
      .controls:hover {
        opacity: 1;
      }
      
      .controls button {
        background-color: #2196f3;
        color: white;
        border: none;
        border-radius: 4px;
        padding: 10px 15px;
        cursor: pointer;
        font-weight: bold;
        transition: background-color 0.2s ease;
      }
      
      .controls button:hover {
        background-color: #0d8aee;
      }
      webview {
        width: 100%;
        height: 100%;
        border: none;
      }
      .launch-button {
        display: none;
        padding: 10px 20px;
        background-color: #2196f3;
        color: white;
        border: none;
        border-radius: 4px;
        font-size: 16px;
        cursor: pointer;
        margin-top: 10px;
      }
      .launch-button:hover {
        background-color: #1976d2;
      }
    </style>
  </head>
  <body class="loading">
    <h1>Browser-Use Desktop</h1>
    <main>
      <div id="loading-container" class="loading-container">
        <h3>Starting Python web server...</h3>
        <div class="loading-bar">
          <div id="loading-progress" class="loading-progress"></div>
        </div>
      </div>
      
      <div id="console-output" class="console-output">
        <div class="console-header">
          <h3>Console Output</h3>
          <div class="console-tabs">
            <button id="tab-python" class="console-tab active">Python</button>
            <button id="tab-chrome" class="console-tab">Chrome</button>
          </div>
          <span id="close-console" class="close-btn">×</span>
        </div>
        <div id="console-actions">
          <div class="action-row">
            <button id="restart-python" class="restart-btn">Restart Python</button>
            <div id="python-command" class="command-display"></div>
          </div>
          <div class="action-row" style="display: none;">
            <button id="restart-chrome" class="restart-btn">Restart Chrome</button>
            <div id="chrome-command" class="command-display"></div>
          </div>
        </div>
        <div id="console-content"></div>
        <div id="chrome-console-content" class="console-content" style="display: none;"></div>
      </div>
      
      <button id="launch-button" class="launch-button">Open Web UI</button>
      
      <div id="webview-container" class="webview-container">
        <webview id="webview" src="about:blank" webpreferences="contextIsolation=yes" allowpopups></webview>
      </div>
      
      <div id="controls" class="controls">
        <button id="toggle-console">Show Console</button>
      </div>
    </main>
    
    <script type="module" src="/src/renderer.ts"></script>
  </body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "browser-use-desktop",
  "productName": "browser-use-desktop",
  "version": "1.0.0",
  "description": "My Electron application description",
  "main": ".vite/build/main.js",
  "scripts": {
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make",
    "publish": "electron-forge publish",
    "lint": "eslint --ext .ts,.tsx ."
  },
  "keywords": [],
  "author": {
    "name": "Nick Sweeting",
    "email": "git@sweeting.me"
  },
  "license": "MIT",
  "devDependencies": {
    "@electron-forge/cli": "^7.8.0",
    "@electron-forge/maker-deb": "^7.8.0",
    "@electron-forge/maker-rpm": "^7.8.0",
    "@electron-forge/maker-squirrel": "^7.8.0",
    "@electron-forge/maker-zip": "^7.8.0",
    "@electron-forge/plugin-auto-unpack-natives": "^7.8.0",
    "@electron-forge/plugin-fuses": "^7.8.0",
    "@electron-forge/plugin-vite": "^7.8.0",
    "@electron/fuses": "^1.8.0",
    "@typescript-eslint/eslint-plugin": "^5.62.0",
    "@typescript-eslint/parser": "^5.62.0",
    "electron": "35.1.2",
    "eslint": "^8.57.1",
    "eslint-plugin-import": "^2.31.0",
    "ts-node": "^10.9.2",
    "typescript": "~4.5.4",
    "vite": "^5.4.15"
  },
  "dependencies": {
    "electron-squirrel-startup": "^1.0.1"
  }
}


================================================
FILE: src/config.ts
================================================
// Configuration for processes
export const pythonCommand = {
  path: './.venv/bin/python',
  args: ['webui.py', '--ip', '127.0.0.1', '--port', '7788'],
  workingDir: 'lib/web-ui',
  // Full command string for display
  get display(): string {
    return `${this.path} ${this.args.join(' ')}`;
  }
};

// Platform type for OS detection
type Platform = 'darwin' | 'win32' | 'linux' | string;

// Define the nodeAPI interface that's injected by the preload script
declare global {
  interface Window {
    nodeAPI?: {
      platform: Platform;
      homedir: string;
      env: {
        HOME?: string;
        USERPROFILE?: string;
        LOCALAPPDATA?: string;
      };
      pathSep: string;
    };
  }
}

// Get platform info from nodeAPI bridge or fallback to browser detection
function getPlatform(): Platform {
  if (typeof window !== 'undefined' && window.nodeAPI) {
    return window.nodeAPI.platform;
  }
  
  // Fallback to browser detection if nodeAPI is not available
  if (typeof navigator !== 'undefined') {
    const userAgent = navigator.userAgent;
    if (userAgent.includes('Win')) return 'win32';
    if (userAgent.includes('Mac')) return 'darwin';
    if (userAgent.includes('Linux')) return 'linux';
  }
  
  return 'unknown';
}

// Get home directory from nodeAPI bridge
function getHomeDir(): string {
  if (typeof window !== 'undefined' && window.nodeAPI) {
    return window.nodeAPI.homedir || '';
  }
  return '';
}

// Get environment variables from nodeAPI bridge
function getEnvVar(name: string): string {
  if (typeof window !== 'undefined' && window.nodeAPI && window.nodeAPI.env) {
    // Use type assertion to bypass TypeScript's index signature check
    return (window.nodeAPI.env as Record<string, string | undefined>)[name] || '';
  }
  return '';
}

// Default Chrome paths by platform
const CHROME_PATHS: Record<Platform, string[]> = {
  darwin: [
    '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
    '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
    '/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',
    '/Applications/Chromium.app/Contents/MacOS/Chromium',
  ],
  win32: [
    'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
    'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
    `${getEnvVar('LOCALAPPDATA')}\\Google\\Chrome\\Application\\chrome.exe`,
    'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe', // Edge as fallback
  ],
  linux: [
    '/usr/bin/google-chrome',
    '/usr/bin/google-chrome-stable',
    '/opt/google/chrome/chrome',
    '/usr/bin/chromium-browser',
    '/usr/bin/chromium',
    '/snap/bin/chromium',
  ]
};

// Default user data directories by platform
const USER_DATA_DIRS: Record<Platform, (homeDir: string) => string> = {
  darwin: (homeDir: string) => `${homeDir}/Library/Application Support/Google/Chrome`,
  win32: (homeDir: string) => `${homeDir}\\AppData\\Local\\Google\\Chrome\\User Data`,
  linux: (homeDir: string) => `${homeDir}/.config/google-chrome`
};

// Determine Chrome path based on OS
function getChromePath(): string {
  // Get platform from nodeAPI
  const currentPlatform = getPlatform();
  console.log('Detected platform:', currentPlatform);
  
  const paths = CHROME_PATHS[currentPlatform] || CHROME_PATHS.linux;
  console.log('Potential Chrome paths:', paths);
  
  // Try direct check in Node environment
  if (typeof window === 'undefined' || typeof require !== 'undefined') {
    try {
      // Import fs dynamically to avoid linter errors
      // eslint-disable-next-line @typescript-eslint/no-var-requires
      const fs = require('fs');
      console.log('Checking if Chrome paths exist using Node.js fs module');
      for (const path of paths) {
        try {
          if (fs.existsSync(path)) {
            console.log('Found Chrome at:', path);
            return path;
          }
        } catch (e) {
          console.warn(`Error checking Chrome path ${path}:`, e);
        }
      }
    } catch (e) {
      console.warn('Could not use fs module to check paths:', e);
    }
  }
  
  // In browser context or if no valid path found, return the first path for the platform
  console.log('Using default Chrome path for platform:', paths[0]);
  return paths[0];
}

// Get default user data directory based on OS
function getUserDataDir(): string {
  const homeDir = getHomeDir() || getEnvVar('HOME') || getEnvVar('USERPROFILE') || '';
  const currentPlatform = getPlatform();
  
  const dirFn = USER_DATA_DIRS[currentPlatform] || USER_DATA_DIRS.linux;
  return dirFn(homeDir);
}

// Basic Chrome command class - actual initialization happens in main.ts
class ChromeCommand {
  private _path = '';
  private _args = [
    '--remote-debugging-port=9222',
    '--window-position=0,0',
    '--disable-web-security'
  ];

  get path(): string {
    return this._path;
  }

  set path(value: string) {
    this._path = value;
  }

  get args(): string[] {
    return this._args;
  }

  set args(value: string[]) {
    this._args = value;
  }

  get display(): string {
    return `${this.path} ${this.args.join(' ')}`;
  }
}

export const chromeCommand = new ChromeCommand();


================================================
FILE: src/index.css
================================================
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
    Arial, sans-serif;
  margin: auto;
  max-width: 38rem;
  padding: 2rem;
}


================================================
FILE: src/main.ts
================================================
import { app, BrowserWindow, ipcMain, screen } from 'electron';
import path from 'node:path';
import { spawn, ChildProcess } from 'child_process';
import started from 'electron-squirrel-startup';
import { pythonCommand, chromeCommand } from './config';
import fs from 'fs';
import os from 'os';
import http from 'http';

// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (started) {
  app.quit();
}

let pyProcess: ChildProcess | null = null;
let chromeProcess: ChildProcess | null = null;

// Clear any potentially existing IPC handlers
function clearIPCHandlers() {
  ipcMain.removeAllListeners('restart-python');
  ipcMain.removeAllListeners('restart-chrome');
}

// Create required directories
function createRequiredDirectories() {
  const baseDir = path.join(os.homedir(), 'Downloads', 'browser-use');
  const dirs = ['recordings', 'traces', 'history'];
  
  dirs.forEach(dir => {
    const dirPath = path.join(baseDir, dir);
    if (!fs.existsSync(dirPath)) {
      fs.mkdirSync(dirPath, { recursive: true });
    }
  });
}

// Load default settings from JSON
function loadDefaultSettings() {
  const settingsPath = path.join(app.getAppPath(), 'lib/web-ui/tmp/webui_settings/31ccfe5a-ef3b-4064-836c-6910ab3a3281.json');
  try {
    if (fs.existsSync(settingsPath)) {
      const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
      return settings;
    }
  } catch (e) {
    console.error('Error loading default settings:', e);
  }
  return null;
}

const createWindow = () => {
  // Make sure we clean up any existing handlers first
  clearIPCHandlers();
  
  // Get the primary display dimensions
  const primaryDisplay = screen.getPrimaryDisplay();
  const { width, height } = primaryDisplay.workAreaSize;
  
  // Calculate half width for positioning
  const halfWidth = Math.floor(width / 2);
  
  // Create the browser window positioned on left half
  const mainWindow = new BrowserWindow({
    width: halfWidth,
    height: height,
    x: 0,
    y: 0,
    frame: false, // Remove window frame
    titleBarStyle: 'hidden', // Hide title bar on macOS
    backgroundColor: '#000000', // Set black background
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      webviewTag: true, // Enable webview tag
      sandbox: false, // Disable sandbox for preload script
    },
  });

  // Set up dark mode
  mainWindow.webContents.on('did-finish-load', () => {
    mainWindow.webContents.insertCSS(`
      body {
        background-color: #000000;
        color: #ffffff;
      }
      * {
        color-scheme: dark;
      }
    `);
  });
  
  // Clean up IPC handlers when window is closed
  mainWindow.on('closed', () => {
    clearIPCHandlers();
  });
  
  // Set up IPC handlers for restarting processes
  ipcMain.on('restart-python', () => {
    startPyProcess(mainWindow);
  });
  
  ipcMain.on('restart-chrome', () => {
    startChromeProcess(mainWindow);
  });

  // and load the index.html of the app.
  if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
    mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
  } else {
    mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));
  }

  // Open the DevTools.
  mainWindow.webContents.openDevTools();

  // Load default settings and pass them to the web UI
  const defaultSettings = loadDefaultSettings();
  if (defaultSettings) {
    mainWindow.webContents.on('did-finish-load', () => {
      mainWindow.webContents.send('load-default-settings', defaultSettings);
    });
  }

  // Start the Python process
  startPyProcess(mainWindow);
  
  // Wait a bit before starting Chrome to ensure Python is running first
  setTimeout(() => {
    startChromeProcess(mainWindow);
  }, 1000);
};

function startPyProcess(mainWindow: BrowserWindow) {
  // Find Chrome path first - needed for environment variables
  const chromePath = findBestChromeExecutable();
  if (!chromePath) {
    console.error('Could not find Chrome for Python process environment');
  }
  
  // Ensure user data directory exists
  const userDataDir = ensureChromeUserDataDir();
  
  // Working directory for the subprocess
  const options = {
    cwd: path.join(app.getAppPath(), pythonCommand.workingDir),
    shell: false,
    env: {
      ...process.env,
      BROWSER_USE_DESKTOP_APP: 'true',
      CHROME_PATH: chromePath || '',
      CHROME_CDP: 'http://localhost:9222',
      CHROME_USER_DATA: userDataDir
    }
  };

  // Clear existing process if it exists
  if (pyProcess) {
    try {
      // Remove all listeners first to prevent callback after destroy
      pyProcess.stdout.removeAllListeners();
      pyProcess.stderr.removeAllListeners();
      pyProcess.removeAllListeners();
      
      pyProcess.kill();
    } catch (e) {
      console.error('Error killing Python process:', e);
    }
    pyProcess = null;
  }

  // Spawn the Python process
  pyProcess = spawn(pythonCommand.path, pythonCommand.args, options);
  
  // Send process output to the renderer
  pyProcess.stdout.on('data', (data) => {
    const output = data.toString();
    if (!mainWindow.isDestroyed()) {
      mainWindow.webContents.send('py-output', { type: 'stdout', data: output });
    }
  });
  
  pyProcess.stderr.on('data', (data) => {
    const output = data.toString();
    if (!mainWindow.isDestroyed()) {
      mainWindow.webContents.send('py-output', { type: 'stderr', data: output });
    }
  });
  
  pyProcess.on('error', (error) => {
    if (!mainWindow.isDestroyed()) {
      mainWindow.webContents.send('py-output', { 
        type: 'error', 
        data: `Failed to start subprocess: ${error.message}` 
      });
    }
  });
  
  pyProcess.on('close', (code) => {
    if (!mainWindow.isDestroyed()) {
      mainWindow.webContents.send('py-output', { 
        type: 'info', 
        data: `Python process exited with code ${code}` 
      });
    }
    pyProcess = null;
  });
  
  // Notify the renderer the process has started
  if (!mainWindow.isDestroyed()) {
    mainWindow.webContents.send('py-started');
  }
  
  // We'll let the renderer detect when the server is ready from the stdout logs
  // but still send the ready signal after a longer timeout as a fallback
  setTimeout(() => {
    if (!mainWindow.isDestroyed()) {
      mainWindow.webContents.send('py-ready', 'http://127.0.0.1:7788');
    }
  }, 10000); // 10 seconds delay as fallback
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
  createRequiredDirectories();
  createWindow();

  app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Kill the Python and Chrome processes before the app quits
app.on('before-quit', (event) => {
  // Prevent the default quit behavior so we can clean up first
  event.preventDefault();
  
  // Counter to track cleanup completion
  let cleanupCounter = 0;
  const processesToCleanup = (pyProcess ? 1 : 0) + (chromeProcess ? 1 : 0);
  
  if (processesToCleanup === 0) {
    // No processes to clean up, safe to quit immediately
    app.exit(0);
    return;
  }
  
  // Set a safety timeout to force quit after 1 second if processes don't exit properly
  const forceQuitTimeout = setTimeout(() => {
    console.warn('Force quitting after timeout');
    app.exit(0);
  }, 1000);
  
  // Function to check if cleanup is done
  const checkCleanup = () => {
    cleanupCounter++;
    if (cleanupCounter >= processesToCleanup) {
      // All processes cleaned up, now safe to exit
      clearTimeout(forceQuitTimeout); // Clear the force quit timeout
      app.exit(0);
    }
  };
  
  // Clean up Python process
  if (pyProcess) {
    try {
      // Kill the process immediately
      pyProcess.kill('SIGKILL');
      pyProcess = null;
      checkCleanup();
    } catch (e) {
      console.error('Error killing Python process:', e);
      pyProcess = null;
      checkCleanup();
    }
  }
  
  // Clean up Chrome process
  if (chromeProcess) {
    try {
      // Kill the process immediately
      chromeProcess.kill('SIGKILL');
      chromeProcess = null;
      checkCleanup();
    } catch (e) {
      console.error('Error killing Chrome process:', e);
      chromeProcess = null;
      checkCleanup();
    }
  }
});

function startChromeProcess(mainWindow: BrowserWindow) {
  // Clear existing process if it exists
  if (chromeProcess) {
    try {
      // Remove all listeners first to prevent callback after destroy
      chromeProcess.stdout.removeAllListeners();
      chromeProcess.stderr.removeAllListeners();
      chromeProcess.removeAllListeners();
      
      chromeProcess.kill();
    } catch (e) {
      console.error('Error killing Chrome process:', e);
    }
    chromeProcess = null;
  }

  // Find the best Chrome executable for the current OS
  const chromePath = findBestChromeExecutable();
  if (!chromePath) {
    const errorMsg = 'Could not find a valid Chrome executable. Please install Google Chrome and try again.';
    console.error(errorMsg);
    mainWindow.webContents.send('chrome-output', { type: 'error', data: errorMsg });
    return;
  }
  
  // Update chromeCommand with the found path
  chromeCommand.path = chromePath;
  
  // Ensure Chrome user data directory exists
  const userDataDir = ensureChromeUserDataDir();
  
  // Get screen dimensions for window positioning
  const primaryDisplay = screen.getPrimaryDisplay();
  const { width, height } = primaryDisplay.workAreaSize;
  const halfWidth = Math.floor(width / 2);
  
  // Compose Chrome arguments with the validated user data directory
  const args = [
    '--remote-debugging-port=9222',
    `--window-position=${halfWidth},0`,
    `--window-size=${halfWidth},${height}`,
    '--install-autogenerated-theme=0,0,0', // Theme setting
    '--disable-web-security',
    `--user-data-dir=${userDataDir}`,
    '--profile-directory=Default',
    '--no-first-run',
    '--no-default-browser-check'
  ];
  
  // Add compatibility flags
  args.push('--disable-features=TranslateUI');
  args.push('--disable-extensions');
  
  // Add minimal necessary flags for stability based on platform
  if (process.platform === 'linux') {
    // Linux often needs these flags
    args.push('--no-sandbox');
    args.push('--disable-gpu');
  }
  
  // Store the args in chromeCommand for other parts of the app
  chromeCommand.args = args;
  
  // Log Chrome launch information
  console.log('Launching Chrome with path:', chromePath);
  console.log('Chrome arguments:', args);
  
  try {
    // Spawn the Chrome process
    chromeProcess = spawn(chromePath, args);
    
    if (!chromeProcess || !chromeProcess.pid) {
      const errorMsg = `Failed to launch Chrome process - could not start the process`;
      console.error(errorMsg);
      mainWindow.webContents.send('chrome-output', { type: 'error', data: errorMsg });
      return;
    }
    
    console.log(`Chrome process started with PID: ${chromeProcess.pid}`);
    
    // Set up Chrome process event listeners
    chromeProcess.stdout.on('data', (data) => {
      const output = data.toString();
      console.log('Chrome stdout:', output);
      if (!mainWindow.isDestroyed()) {
        mainWindow.webContents.send('chrome-output', { type: 'stdout', data: output });
      }
    });
    
    chromeProcess.stderr.on('data', (data) => {
      const output = data.toString();
      console.log('Chrome stderr:', output);
      if (!mainWindow.isDestroyed()) {
        mainWindow.webContents.send('chrome-output', { type: 'stderr', data: output });
      }
    });
    
    chromeProcess.on('error', (error) => {
      console.error('Chrome process error:', error.message);
      if (!mainWindow.isDestroyed()) {
        mainWindow.webContents.send('chrome-output', { 
          type: 'error', 
          data: `Failed to start Chrome: ${error.message}` 
        });
      }
    });
    
    chromeProcess.on('close', (code) => {
      console.log(`Chrome process exited with code ${code}`);
      if (!mainWindow.isDestroyed()) {
        mainWindow.webContents.send('chrome-output', { 
          type: 'info', 
          data: `Chrome process exited with code ${code}` 
        });
      }
      chromeProcess = null;
    });
    
    // Notify the renderer the process has started
    if (!mainWindow.isDestroyed()) {
      mainWindow.webContents.send('chrome-started');
    }
    
    // Verify Chrome is listening on debug port
    setTimeout(() => {
      verifyChromeLaunched(mainWindow);
    }, 2000);
  } catch (error) {
    const errorMsg = `Unexpected error launching Chrome: ${error}`;
    console.error(errorMsg);
    if (!mainWindow.isDestroyed()) {
      mainWindow.webContents.send('chrome-output', { type: 'error', data: errorMsg });
    }
  }
}

/**
 * Verifies that Chrome launched successfully and is accessible via debug port
 */
function verifyChromeLaunched(mainWindow: BrowserWindow): void {
  console.log('Verifying Chrome is accessible on debug port...');
  
  try {
    // Try to connect to Chrome's debug port
    const options = {
      host: '127.0.0.1',
      port: 9222,
      path: '/json/version',
      timeout: 2000
    };
    
    const req = http.get(options, (res) => {
      if (res.statusCode === 200) {
        console.log('Chrome debug port connection successful');
        
        let data = '';
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          try {
            const info = JSON.parse(data);
            console.log('Chrome debug info:', info);
            
            if (!mainWindow.isDestroyed()) {
              mainWindow.webContents.send('chrome-output', { 
                type: 'info', 
                data: `Chrome ready - ${info.Browser || 'Chrome'} [${info.Protocol || 'CDP'}]` 
              });
            }
          } catch (e) {
            console.error('Error parsing Chrome debug info:', e);
          }
        });
      } else {
        console.error(`Chrome debug port returned status code: ${res.statusCode}`);
        if (!mainWindow.isDestroyed()) {
          mainWindow.webContents.send('chrome-output', { 
            type: 'warning', 
            data: `Chrome may not be fully initialized (status: ${res.statusCode})` 
          });
        }
      }
    });
    
    req.on('error', (e) => {
      console.error('Error connecting to Chrome debug port:', e.message);
      if (!mainWindow.isDestroyed()) {
        mainWindow.webContents.send('chrome-output', { 
          type: 'warning', 
          data: `Chrome debug port not accessible: ${e.message}. The browser may still be starting.` 
        });
      }
    });
    
    req.end();
  } catch (e) {
    console.error('Error verifying Chrome launch:', e);
  }
}

/**
 * Finds the best Chrome executable for the current OS
 * @returns The path to the Chrome executable or null if not found
 */
function findBestChromeExecutable(): string | null {
  // Define platform-specific paths in order of preference
  const paths: string[] = [];
  
  if (process.platform === 'darwin') {
    // macOS paths
    paths.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome');
    paths.push('/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta');
    paths.push('/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev');
    paths.push('/Applications/Chromium.app/Contents/MacOS/Chromium');
    // Add fallback for homebrew installations
    paths.push('/opt/homebrew/bin/chromium');
  } else if (process.platform === 'win32') {
    // Windows paths
    paths.push('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe');
    paths.push('C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe');
    
    // Add LocalAppData path if available
    const localAppData = process.env.LOCALAPPDATA;
    if (localAppData) {
      paths.push(`${localAppData}\\Google\\Chrome\\Application\\chrome.exe`);
    }
    
    // Microsoft Edge as fallback (Chromium-based)
    paths.push('C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe');
    paths.push('C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe');
  } else {
    // Linux paths
    paths.push('/usr/bin/google-chrome');
    paths.push('/usr/bin/google-chrome-stable');
    paths.push('/opt/google/chrome/chrome');
    paths.push('/usr/bin/chromium-browser');
    paths.push('/usr/bin/chromium');
    paths.push('/snap/bin/chromium');
  }
  
  console.log('Checking for Chrome at these paths:', paths);
  
  // Find first existing path
  for (const path of paths) {
    if (fs.existsSync(path)) {
      console.log(`Found Chrome executable at: ${path}`);
      return path;
    }
  }
  
  console.error('No Chrome executable found in common locations');
  return null;
}

/**
 * Ensures the Chrome user data directory exists
 * @returns The path to the Chrome user data directory
 */
function ensureChromeUserDataDir(): string {
  // Determine user data directory based on platform
  let userDataDir: string;
  const homeDir = os.homedir();
  const appName = 'browser-use-desktop';
  
  if (process.platform === 'darwin') {
    userDataDir = `${homeDir}/Library/Application Support/${appName}/ChromeProfile`;
  } else if (process.platform === 'win32') {
    userDataDir = `${homeDir}\\AppData\\Local\\${appName}\\ChromeProfile`;
  } else {
    userDataDir = `${homeDir}/.config/${appName}/ChromeProfile`;
  }
  
  // Log the user data directory path
  console.log(`Using Chrome user data directory: ${userDataDir}`);
  
  // Ensure the directory exists
  try {
    if (!fs.existsSync(userDataDir)) {
      fs.mkdirSync(userDataDir, { recursive: true });
      console.log(`Created Chrome user data directory: ${userDataDir}`);
      
      // Create first_run file to prevent first run experience
      const firstRunPath = path.join(userDataDir, 'First Run');
      fs.writeFileSync(firstRunPath, '');
      console.log('Created First Run file to disable welcome screen');
      
      // Create an empty Preferences file with basic settings
      const prefsPath = path.join(userDataDir, 'Default', 'Preferences');
      
      // Create Default directory if it doesn't exist
      const defaultDir = path.join(userDataDir, 'Default');
      if (!fs.existsSync(defaultDir)) {
        fs.mkdirSync(defaultDir, { recursive: true });
      }
      
      // Basic preferences to disable welcome page, etc.
      const defaultPrefs = {
        browser: {
          custom_chrome_frame: false,
          check_default_browser: false
        },
        profile: {
          default_content_setting_values: {
            notifications: 2 // Block notifications: 1=allow, 2=block, 3=ask
          }
        },
        session: {
          restore_on_startup: 5 // Don't restore anything: 5=open new tab
        },
        bookmark_bar: {
          show_on_all_tabs: false
        },
        distribution: {
          import_bookmarks: false,
          import_history: false,
          import_search_engine: false,
          make_chrome_default: false,
          show_welcome_page: false,
          skip_first_run_ui: true
        }
      };
      
      fs.writeFileSync(prefsPath, JSON.stringify(defaultPrefs, null, 2));
      console.log('Created default Chrome preferences file');
    } else {
      console.log(`Using existing Chrome user data directory: ${userDataDir}`);
    }
  } catch (err) {
    console.error(`Error creating Chrome user data directory: ${err}`);
    // Fallback to a temporary directory
    userDataDir = path.join(os.tmpdir(), `${appName}-chrome-profile`);
    fs.mkdirSync(userDataDir, { recursive: true });
    console.log(`Using fallback Chrome user data directory: ${userDataDir}`);
  }
  
  return userDataDir;
}

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.


================================================
FILE: src/preload.ts
================================================
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

import { contextBridge, ipcRenderer } from 'electron';
import * as os from 'os';
import * as fs from 'fs';

// Define types for the bridges
type PyOutputCallback = (data: { type: string; data: string }) => void;
type PyStartedCallback = () => void;
type PyReadyCallback = (url: string) => void;
type ChromeOutputCallback = (data: { type: string; data: string }) => void;
type ChromeStartedCallback = () => void;
type SettingsCallback = (settings: Record<string, any>) => void;

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('pyBridge', {
  // Listen for messages from the main process
  onPyOutput: (callback: PyOutputCallback) => {
    ipcRenderer.on('py-output', (_, data) => callback(data));
  },
  
  onPyStarted: (callback: PyStartedCallback) => {
    ipcRenderer.on('py-started', () => callback());
  },
  
  onPyReady: (callback: PyReadyCallback) => {
    ipcRenderer.on('py-ready', (_, url) => callback(url));
  },
  
  // Restart the Python process
  restartPython: () => {
    ipcRenderer.send('restart-python');
  },
  
  // Clean up event listeners
  removeAllListeners: () => {
    ipcRenderer.removeAllListeners('py-output');
    ipcRenderer.removeAllListeners('py-started');
    ipcRenderer.removeAllListeners('py-ready');
  }
});

// Expose Chrome bridge for communicating Chrome process information
contextBridge.exposeInMainWorld('chromeBridge', {
  // Listen for messages from the main process
  onChromeOutput: (callback: ChromeOutputCallback) => {
    ipcRenderer.on('chrome-output', (_, data) => callback(data));
  },
  
  onChromeStarted: (callback: ChromeStartedCallback) => {
    ipcRenderer.on('chrome-started', () => callback());
  },
  
  // Restart the Chrome process
  restartChrome: () => {
    ipcRenderer.send('restart-chrome');
  },
  
  // Clean up event listeners
  removeAllListeners: () => {
    ipcRenderer.removeAllListeners('chrome-output');
    ipcRenderer.removeAllListeners('chrome-started');
  }
});

// Expose settings bridge for handling default settings
contextBridge.exposeInMainWorld('settingsBridge', {
  // Listen for default settings from the main process
  onDefaultSettings: (callback: SettingsCallback) => {
    ipcRenderer.on('load-default-settings', (_, settings) => callback(settings));
  },
  
  // Clean up event listeners
  removeAllListeners: () => {
    ipcRenderer.removeAllListeners('load-default-settings');
  }
});

// Expose Node.js platform-specific APIs needed by the renderer
contextBridge.exposeInMainWorld('nodeAPI', {
  platform: os.platform(),
  homedir: os.homedir(),
  env: {
    HOME: process.env.HOME,
    USERPROFILE: process.env.USERPROFILE,
    LOCALAPPDATA: process.env.LOCALAPPDATA
  },
  pathSep: process.platform === 'win32' ? '\\' : '/'
});



================================================
FILE: src/renderer.ts
================================================
/**
 * This file will automatically be loaded by vite and run in the "renderer" context.
 * To learn more about the differences between the "main" and the "renderer" context in
 * Electron, visit:
 *
 * https://electronjs.org/docs/tutorial/process-model
 */

import './index.css';
import { pythonCommand, chromeCommand } from './config';

// Define interface for Python bridge
interface PyBridge {
  onPyOutput: (callback: (data: { type: string; data: string }) => void) => void;
  onPyStarted: (callback: () => void) => void;
  onPyReady: (callback: (url: string) => void) => void;
  restartPython: () => void;
  removeAllListeners: () => void;
}

// Define interface for Chrome bridge
interface ChromeBridge {
  onChromeOutput: (callback: (data: { type: string; data: string }) => void) => void;
  onChromeStarted: (callback: () => void) => void;
  restartChrome: () => void;
  removeAllListeners: () => void;
}

// Access the exposed API from preload script
declare global {
  interface Window {
    pyBridge: PyBridge;
    chromeBridge: ChromeBridge;
    settingsBridge: {
      onDefaultSettings: (callback: (settings: any) => void) => void;
    };
  }
}

// DOM Elements
const consoleOutput = document.getElementById('console-output') as HTMLDivElement;
const consoleContent = document.getElementById('console-content') as HTMLDivElement;
const chromeConsoleContent = document.getElementById('chrome-console-content') as HTMLDivElement;
const loadingProgress = document.getElementById('loading-progress') as HTMLDivElement;
const loadingContainer = document.getElementById('loading-container') as HTMLDivElement;
const webviewContainer = document.getElementById('webview-container') as HTMLDivElement;
const webview = document.getElementById('webview') as HTMLElement;
const launchButton = document.getElementById('launch-button') as HTMLButtonElement;
const controls = document.getElementById('controls') as HTMLDivElement;
const toggleConsoleButton = document.getElementById('toggle-console') as HTMLButtonElement;
const closeConsoleButton = document.getElementById('close-console') as HTMLSpanElement;
const tabPython = document.getElementById('tab-python') as HTMLButtonElement;
const tabChrome = document.getElementById('tab-chrome') as HTMLButtonElement;
const restartPythonButton = document.getElementById('restart-python') as HTMLButtonElement;
const restartChromeButton = document.getElementById('restart-chrome') as HTMLButtonElement;
const pythonCommandDisplay = document.getElementById('python-command') as HTMLDivElement;
const chromeCommandDisplay = document.getElementById('chrome-command') as HTMLDivElement;
const actionRows = document.querySelectorAll('.action-row') as NodeListOf<HTMLDivElement>;

// Variables to track loading
let progressValue = 0;
let progressInterval: number | null = null;
let serverUrl = '';

// Function to append output to the console
function appendToConsole(message: string, type: string, target: HTMLElement = consoleContent): void {
  const element = document.createElement('div');
  element.className = type;
  element.textContent = message;
  target.appendChild(element);
  target.scrollTop = target.scrollHeight;
}

// Function to simulate loading progress
function simulateProgress(): void {
  progressInterval = window.setInterval(() => {
    if (progressValue < 90) {
      progressValue += Math.random() * 10;
      loadingProgress.style.width = `${progressValue}%`;
    }
  }, 300);
}

// Function to complete the loading progress
function completeProgress(): void {
  clearInterval(progressInterval!);
  progressValue = 100;
  loadingProgress.style.width = '100%';
  
  // Hide loading container after a short delay
  setTimeout(() => {
    loadingContainer.style.display = 'none';
    // Don't show the launch button if we're auto-loading the UI
    if (!serverUrl) {
      launchButton.style.display = 'block';
    }
  }, 500);
}

// Toggle console visibility
function toggleConsole(): void {
  // Use classList to toggle visibility
  const isConsoleVisible = consoleOutput.classList.contains('visible');
  
  if (isConsoleVisible) {
    consoleOutput.classList.remove('visible');
    toggleConsoleButton.textContent = 'Show Console';
  } else {
    consoleOutput.classList.add('visible');
    toggleConsoleButton.textContent = 'Hide Console';
    // Scroll to the latest output
    const activeTab = tabPython.classList.contains('active') ? consoleContent : chromeConsoleContent;
    activeTab.scrollTop = activeTab.scrollHeight;
  }
}

// Function to switch between console tabs
function switchConsoleTab(tab: 'python' | 'chrome'): void {
  if (tab === 'python') {
    tabPython.classList.add('active');
    tabChrome.classList.remove('active');
    consoleContent.style.display = 'block';
    chromeConsoleContent.style.display = 'none';
    consoleContent.scrollTop = consoleContent.scrollHeight;
    // Show Python action row, hide Chrome action row
    actionRows[0].style.display = 'flex';
    actionRows[1].style.display = 'none';
  } else {
    tabPython.classList.remove('active');
    tabChrome.classList.add('active');
    consoleContent.style.display = 'none';
    chromeConsoleContent.style.display = 'block';
    chromeConsoleContent.scrollTop = chromeConsoleContent.scrollHeight;
    // Hide Python action row, show Chrome action row
    actionRows[0].style.display = 'none';
    actionRows[1].style.display = 'flex';
  }
}

// Function to load the web UI
function loadWebUI(url: string): void {
  // Force the correct type for webview element
  const webviewElement = document.querySelector('webview') as Electron.WebviewTag;
  if (webviewElement) {
    webviewElement.src = url;
    
    // Handle webview events
    webviewElement.addEventListener('dom-ready', () => {
      console.log('WebView DOM ready');
    });
    
    webviewElement.addEventListener('did-start-loading', () => {
      console.log('WebView started loading');
    });
    
    webviewElement.addEventListener('did-finish-load', () => {
      console.log('WebView finished loading');
      // Remove loading class when webview is loaded
      document.body.classList.remove('loading');
    });
    
    webviewElement.addEventListener('did-fail-load', (e) => {
      console.error('WebView failed to load:', e);
      // Show error and console if webview fails to load
      appendToConsole(`Failed to load webview: ${JSON.stringify(e)}`, 'error');
      consoleOutput.classList.add('visible');
      toggleConsoleButton.textContent = 'Hide Console';
      // Scroll to the error
      consoleContent.scrollTop = consoleContent.scrollHeight;
    });
  }
  
  // Show the webview and controls, hide other elements
  webviewContainer.style.display = 'block';
  controls.style.display = 'block';
  loadingContainer.style.display = 'none';
  launchButton.style.display = 'none';
  
  // Keep console visible until the UI fully loads, then hide it with a delay
  // This is handled in the event listeners in the init function
}

// Initialize the app
function init(): void {
  // Start simulating progress
  simulateProgress();
  
  // Show console automatically during loading
  consoleOutput.classList.add('visible');
  
  // Set the command displays
  pythonCommandDisplay.textContent = pythonCommand.display;
  chromeCommandDisplay.textContent = chromeCommand.display;
  
  // Listen for Python process output
  window.pyBridge.onPyOutput((data) => {
    appendToConsole(data.data, data.type);
    
    // Automatically detect when server is ready from the output
    if (data.data.includes('Server is ready') || data.data.includes('Running on http://')) {
      if (!serverUrl) {
        serverUrl = 'http://127.0.0.1:7788';
        appendToConsole(`Detected server is ready at: ${serverUrl}`, 'info');
        completeProgress();
        loadWebUI(serverUrl);
        
        // Hide console after server is ready and web UI is loaded
        setTimeout(() => {
          consoleOutput.classList.remove('visible');
          toggleConsoleButton.textContent = 'Show Console';
        }, 1000); // Small delay to allow users to see the "Server is ready" message
      }
    }
  });
  
  // Listen for Python process started event
  window.pyBridge.onPyStarted(() => {
    appendToConsole('Python process started', 'info');
  });
  
  // Listen for Python ready event
  window.pyBridge.onPyReady((url) => {
    serverUrl = url;
    appendToConsole(`Server is ready at: ${url}`, 'info');
    completeProgress();
    // Automatically load the web UI
    loadWebUI(url);
    
    // Hide console after server is ready and web UI is loaded
    setTimeout(() => {
      consoleOutput.classList.remove('visible');
      toggleConsoleButton.textContent = 'Show Console';
    }, 1000); // Small delay to allow users to see the "Server is ready" message
  });
  
  // Listen for Chrome process output
  window.chromeBridge.onChromeOutput((data) => {
    appendToConsole(data.data, data.type, chromeConsoleContent);
  });
  
  // Listen for Chrome process started event
  window.chromeBridge.onChromeStarted(() => {
    appendToConsole('Chrome process started', 'info', chromeConsoleContent);
  });
  
  // Setup tab switching
  tabPython.addEventListener('click', () => {
    switchConsoleTab('python');
    restartPythonButton.style.display = 'block';
    restartChromeButton.style.display = 'none';
  });
  
  tabChrome.addEventListener('click', () => {
    switchConsoleTab('chrome');
    restartPythonButton.style.display = 'none';
    restartChromeButton.style.display = 'block';
  });
  
  // Setup restart buttons
  restartPythonButton.addEventListener('click', () => {
    // Clear console
    consoleContent.innerHTML = '';
    
    // Add message about restart
    appendToConsole('Restarting Python process...', 'info');
    
    // Restart the process
    window.pyBridge.restartPython();
  });
  
  restartChromeButton.addEventListener('click', () => {
    // Clear console
    chromeConsoleContent.innerHTML = '';
    
    // Add message about restart
    appendToConsole('Restarting Chrome process...', 'info', chromeConsoleContent);
    
    // Restart the process
    window.chromeBridge.restartChrome();
  });
  
  // Setup launch button click handler
  launchButton.addEventListener('click', () => {
    loadWebUI(serverUrl);
  });
  
  // Setup toggle console button
  toggleConsoleButton.addEventListener('click', toggleConsole);
  
  // Setup close console button
  closeConsoleButton.addEventListener('click', () => {
    consoleOutput.classList.remove('visible');
    toggleConsoleButton.textContent = 'Show Console';
  });
}

// Initialize the app when the DOM is loaded
document.addEventListener('DOMContentLoaded', init);

// Clean up event listeners when window is closed
window.addEventListener('beforeunload', () => {
  window.pyBridge.removeAllListeners();
  window.chromeBridge.removeAllListeners();
  if (progressInterval) {
    clearInterval(progressInterval);
  }
});


================================================
FILE: src/types/electron-squirrel-startup.d.ts
================================================
declare module 'electron-squirrel-startup' {
  const started: boolean;
  export default started;
} 


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "commonjs",
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "noImplicitAny": true,
    "sourceMap": true,
    "baseUrl": ".",
    "outDir": "dist",
    "moduleResolution": "node",
    "resolveJsonModule": true
  }
}


================================================
FILE: vite.main.config.ts
================================================
import { defineConfig } from 'vite';

// https://vitejs.dev/config
export default defineConfig({});


================================================
FILE: vite.preload.config.ts
================================================
import { defineConfig } from 'vite';

// https://vitejs.dev/config
export default defineConfig({});


================================================
FILE: vite.renderer.config.ts
================================================
import { defineConfig } from 'vite';

// https://vitejs.dev/config
export default defineConfig({});
Download .txt
gitextract_ja6d5fvp/

├── .eslintrc.json
├── .gitignore
├── README.md
├── forge.config.ts
├── forge.env.d.ts
├── index.html
├── package.json
├── src/
│   ├── config.ts
│   ├── index.css
│   ├── main.ts
│   ├── preload.ts
│   ├── renderer.ts
│   └── types/
│       └── electron-squirrel-startup.d.ts
├── tsconfig.json
├── vite.main.config.ts
├── vite.preload.config.ts
└── vite.renderer.config.ts
Download .txt
SYMBOL INDEX (40 symbols across 4 files)

FILE: src/config.ts
  method display (line 7) | get display(): string {
  type Platform (line 13) | type Platform = 'darwin' | 'win32' | 'linux' | string;
  type Window (line 17) | interface Window {
  function getPlatform (line 32) | function getPlatform(): Platform {
  function getHomeDir (line 49) | function getHomeDir(): string {
  function getEnvVar (line 57) | function getEnvVar(name: string): string {
  constant CHROME_PATHS (line 66) | const CHROME_PATHS: Record<Platform, string[]> = {
  constant USER_DATA_DIRS (line 90) | const USER_DATA_DIRS: Record<Platform, (homeDir: string) => string> = {
  function getChromePath (line 97) | function getChromePath(): string {
  function getUserDataDir (line 133) | function getUserDataDir(): string {
  class ChromeCommand (line 142) | class ChromeCommand {
    method path (line 150) | get path(): string {
    method path (line 154) | set path(value: string) {
    method args (line 158) | get args(): string[] {
    method args (line 162) | set args(value: string[]) {
    method display (line 166) | get display(): string {

FILE: src/main.ts
  function clearIPCHandlers (line 19) | function clearIPCHandlers() {
  function createRequiredDirectories (line 25) | function createRequiredDirectories() {
  function loadDefaultSettings (line 38) | function loadDefaultSettings() {
  function startPyProcess (line 134) | function startPyProcess(mainWindow: BrowserWindow) {
  function startChromeProcess (line 306) | function startChromeProcess(mainWindow: BrowserWindow) {
  function verifyChromeLaunched (line 445) | function verifyChromeLaunched(mainWindow: BrowserWindow): void {
  function findBestChromeExecutable (line 509) | function findBestChromeExecutable(): string | null {
  function ensureChromeUserDataDir (line 563) | function ensureChromeUserDataDir(): string {

FILE: src/preload.ts
  type PyOutputCallback (line 9) | type PyOutputCallback = (data: { type: string; data: string }) => void;
  type PyStartedCallback (line 10) | type PyStartedCallback = () => void;
  type PyReadyCallback (line 11) | type PyReadyCallback = (url: string) => void;
  type ChromeOutputCallback (line 12) | type ChromeOutputCallback = (data: { type: string; data: string }) => void;
  type ChromeStartedCallback (line 13) | type ChromeStartedCallback = () => void;
  type SettingsCallback (line 14) | type SettingsCallback = (settings: Record<string, any>) => void;

FILE: src/renderer.ts
  type PyBridge (line 13) | interface PyBridge {
  type ChromeBridge (line 22) | interface ChromeBridge {
  type Window (line 31) | interface Window {
  function appendToConsole (line 66) | function appendToConsole(message: string, type: string, target: HTMLElem...
  function simulateProgress (line 75) | function simulateProgress(): void {
  function completeProgress (line 85) | function completeProgress(): void {
  function toggleConsole (line 101) | function toggleConsole(): void {
  function switchConsoleTab (line 118) | function switchConsoleTab(tab: 'python' | 'chrome'): void {
  function loadWebUI (line 141) | function loadWebUI(url: string): void {
  function init (line 184) | function init(): void {
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (58K chars).
[
  {
    "path": ".eslintrc.json",
    "chars": 352,
    "preview": "{\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"p"
  },
  {
    "path": ".gitignore",
    "chars": 1215,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs."
  },
  {
    "path": "README.md",
    "chars": 1866,
    "preview": "\n<img src=\"./assets/desktop.png\" alt=\"Browser Use Desktop App\" width=\"full\"/>\n\n<br/>\n\n[![GitHub stars](https://img.shiel"
  },
  {
    "path": "forge.config.ts",
    "chars": 1963,
    "preview": "import type { ForgeConfig } from '@electron-forge/shared-types';\nimport { MakerSquirrel } from '@electron-forge/maker-sq"
  },
  {
    "path": "forge.env.d.ts",
    "chars": 69,
    "preview": "/// <reference types=\"@electron-forge/plugin-vite/forge-vite-env\" />\n"
  },
  {
    "path": "index.html",
    "chars": 7552,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Browser-Use Desktop</title>\n    <style>\n      bo"
  },
  {
    "path": "package.json",
    "chars": 1270,
    "preview": "{\n  \"name\": \"browser-use-desktop\",\n  \"productName\": \"browser-use-desktop\",\n  \"version\": \"1.0.0\",\n  \"description\": \"My El"
  },
  {
    "path": "src/config.ts",
    "chars": 5193,
    "preview": "// Configuration for processes\nexport const pythonCommand = {\n  path: './.venv/bin/python',\n  args: ['webui.py', '--ip',"
  },
  {
    "path": "src/index.css",
    "chars": 166,
    "preview": "body {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,\n    Arial, sans-serif;\n  margin:"
  },
  {
    "path": "src/main.ts",
    "chars": 20445,
    "preview": "import { app, BrowserWindow, ipcMain, screen } from 'electron';\nimport path from 'node:path';\nimport { spawn, ChildProce"
  },
  {
    "path": "src/preload.ts",
    "chars": 2996,
    "preview": "// See the Electron documentation for details on how to use preload scripts:\n// https://www.electronjs.org/docs/latest/t"
  },
  {
    "path": "src/renderer.ts",
    "chars": 10949,
    "preview": "/**\n * This file will automatically be loaded by vite and run in the \"renderer\" context.\n * To learn more about the diff"
  },
  {
    "path": "src/types/electron-squirrel-startup.d.ts",
    "chars": 100,
    "preview": "declare module 'electron-squirrel-startup' {\n  const started: boolean;\n  export default started;\n} \n"
  },
  {
    "path": "tsconfig.json",
    "chars": 311,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"commonjs\",\n    \"allowJs\": true,\n    \"skipLibCheck\": true"
  },
  {
    "path": "vite.main.config.ts",
    "chars": 100,
    "preview": "import { defineConfig } from 'vite';\n\n// https://vitejs.dev/config\nexport default defineConfig({});\n"
  },
  {
    "path": "vite.preload.config.ts",
    "chars": 100,
    "preview": "import { defineConfig } from 'vite';\n\n// https://vitejs.dev/config\nexport default defineConfig({});\n"
  },
  {
    "path": "vite.renderer.config.ts",
    "chars": 100,
    "preview": "import { defineConfig } from 'vite';\n\n// https://vitejs.dev/config\nexport default defineConfig({});\n"
  }
]

About this extraction

This page contains the full source code of the browser-use/desktop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (53.5 KB), approximately 13.7k tokens, and a symbol index with 40 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!