UNPKG

@darbotlabs/darbot-windows-mcp

Version:

🪟 Darbot Windows MCP - Professional Windows desktop automation server for AI agents via Model Context Protocol (MCP). Control Windows like a human with 15 powerful tools. Includes Python dependencies, UV, and VS Code configuration.

402 lines (335 loc) • 15 kB
#!/usr/bin/env node const fs = require('fs-extra'); const path = require('path'); const { spawn } = require('child_process'); const chalk = require('chalk'); const ora = require('ora'); const inquirer = require('inquirer'); class DarbotSetup { constructor() { this.packageDir = path.dirname(__dirname); this.workspaceDir = process.cwd(); } async run() { console.log(chalk.blue.bold('🪟 Darbot Windows MCP Setup Wizard')); console.log(chalk.gray(' Desktop automation for AI agents')); console.log(''); // Check system requirements await this.checkSystemRequirements(); // Ask user preferences const preferences = await this.getUserPreferences(); // Install dependencies await this.installDependencies(preferences); // Update workspace directory from user input this.workspaceDir = preferences.workspaceDir; // Configure VS Code if (preferences.configureVSCode) { await this.configureVSCode(preferences); } // Configure Claude Desktop if (preferences.configureClaude) { await this.configureClaude(); } console.log(chalk.green.bold('\nšŸŽ‰ Setup completed successfully!')); console.log(chalk.cyan('\nFiles created:')); if (preferences.configureVSCode) { console.log(chalk.gray(` • ${path.join(this.workspaceDir, '.vscode', 'mcp.json')}`)); console.log(chalk.gray(` • ${path.join(this.workspaceDir, '.vscode', 'settings.json')}`)); } console.log(chalk.cyan('\nNext steps:')); console.log('• Restart VS Code if you configured it'); console.log('• Restart Claude Desktop if you configured it'); console.log('• Run "darbot-windows-mcp --help" for usage information'); } async checkSystemRequirements() { const spinner = ora('Checking system requirements...').start(); try { // Check Python const pythonResult = await this.runCommand('python', ['--version']); const pythonVersion = pythonResult.stdout.trim(); if (!pythonVersion.includes('3.12') && !pythonVersion.includes('3.13') && !pythonVersion.includes('3.14') && !pythonVersion.includes('3.15')) { spinner.fail('Python 3.12+ is required'); console.log(chalk.red('Please install Python 3.12 or higher')); process.exit(1); } spinner.succeed(`Found ${pythonVersion}`); } catch (error) { spinner.fail('Python not found'); console.log(chalk.red('Please install Python 3.12+ and ensure it\'s in your PATH')); process.exit(1); } } async getUserPreferences() { console.log(chalk.cyan(`Current workspace: ${this.workspaceDir}`)); console.log(''); return await inquirer.prompt([ { type: 'input', name: 'workspaceDir', message: 'VS Code workspace directory (press Enter to use current):', default: this.workspaceDir, validate: (input) => { if (!fs.existsSync(input)) { return 'Directory does not exist'; } return true; } }, { type: 'list', name: 'installMethod', message: 'Choose installation method:', choices: [ { name: 'UV (Recommended - faster, modern)', value: 'uv' }, { name: 'Standard Python (pip + venv)', value: 'python' } ], default: 'uv' }, { type: 'confirm', name: 'configureVSCode', message: 'Configure VS Code MCP integration?', default: true }, { type: 'confirm', name: 'configureClaude', message: 'Configure Claude Desktop integration?', default: true } ]); } async installDependencies(preferences) { if (preferences.installMethod === 'uv') { await this.installWithUV(); } else { await this.installWithPython(); } } async installWithUV() { let spinner = ora('Installing UV...').start(); try { // Check if UV is already installed await this.runCommand('uv', ['--version']); spinner.succeed('UV already installed'); } catch (error) { try { await this.runCommand('python', ['-m', 'pip', 'install', 'uv']); spinner.succeed('UV installed successfully'); } catch (installError) { spinner.fail('Failed to install UV'); throw installError; } } spinner = ora('Installing Python dependencies with UV...').start(); try { // Set UV_LINK_MODE to copy to avoid hardlink issues on OneDrive const env = { ...process.env, UV_LINK_MODE: 'copy' }; await this.runCommand('uv', ['sync'], { cwd: this.packageDir, env }); spinner.succeed('Dependencies installed successfully'); } catch (error) { spinner.fail('Failed to install dependencies'); throw error; } } async installWithPython() { const spinner = ora('Installing Python dependencies...').start(); try { // Create virtual environment in package directory if it doesn't exist const venvPath = path.join(this.packageDir, 'venv'); if (!fs.existsSync(venvPath)) { await this.runCommand('python', ['-m', 'venv', 'venv'], { cwd: this.packageDir }); } // Install requirements const requirementsPath = path.join(this.packageDir, 'requirements.txt'); const pythonPath = path.join(venvPath, 'Scripts', 'python.exe'); await this.runCommand(pythonPath, ['-m', 'pip', 'install', '-r', requirementsPath]); spinner.succeed('Dependencies installed successfully'); } catch (error) { spinner.fail('Failed to install dependencies'); throw error; } } async configureVSCode(preferences) { const spinner = ora(`Configuring VS Code in ${this.workspaceDir}...`).start(); try { const vscodeDir = path.join(this.workspaceDir, '.vscode'); await fs.ensureDir(vscodeDir); // Create mcp.json using template const mcpConfig = await this.createMCPConfigFromTemplate(preferences); const mcpPath = path.join(vscodeDir, 'mcp.json'); await fs.writeJSON(mcpPath, mcpConfig, { spaces: 2 }); // Create/update settings.json using template const settingsPath = path.join(vscodeDir, 'settings.json'); let settings = {}; if (fs.existsSync(settingsPath)) { settings = await fs.readJSON(settingsPath); } const settingsConfig = await this.createSettingsConfigFromTemplate(preferences); settings['mcp.servers'] = settings['mcp.servers'] || {}; settings['mcp.servers']['darbot-windows-mcp'] = settingsConfig['mcp.servers']['darbot-windows-mcp']; await fs.writeJSON(settingsPath, settings, { spaces: 2 }); spinner.succeed(`VS Code configured successfully`); console.log(chalk.green(` āœ“ Created: ${mcpPath}`)); console.log(chalk.green(` āœ“ Updated: ${settingsPath}`)); } catch (error) { spinner.fail('Failed to configure VS Code'); throw error; } } async configureClaude() { const spinner = ora('Configuring Claude Desktop...').start(); try { const claudeConfigDir = path.join(process.env.APPDATA, 'Claude'); const claudeConfigPath = path.join(claudeConfigDir, 'claude_desktop_config.json'); await fs.ensureDir(claudeConfigDir); let config = {}; if (fs.existsSync(claudeConfigPath)) { config = await fs.readJSON(claudeConfigPath); } config.mcpServers = config.mcpServers || {}; config.mcpServers['darbot-windows-mcp'] = { command: 'darbot-windows-mcp', args: [] }; await fs.writeJSON(claudeConfigPath, config, { spaces: 2 }); spinner.succeed('Claude Desktop configured successfully'); } catch (error) { spinner.fail('Failed to configure Claude Desktop'); console.log(chalk.yellow('You may need to configure Claude Desktop manually')); } } async createMCPConfigFromTemplate(preferences) { const templatePath = path.join(this.packageDir, 'templates', 'mcp.json.template'); const template = await fs.readFile(templatePath, 'utf8'); let command, args; if (preferences.installMethod === 'uv') { command = 'uv'; args = [ '--directory', this.packageDir, 'run', 'python', path.join(this.packageDir, 'main.py') ]; } else { const venvPython = path.join(this.packageDir, 'venv', 'Scripts', 'python.exe'); command = venvPython; args = [path.join(this.packageDir, 'main.py')]; } // Create proper JSON array string const argsJsonArray = JSON.stringify(args, null, 8).replace(/\n {8}/g, '\n '); const argsString = argsJsonArray.slice(1, -1); // Remove outer brackets const configContent = template .replace('"{{COMMAND}}"', JSON.stringify(command)) .replace('{{ARGS}}', argsString); return JSON.parse(configContent); } async createSettingsConfigFromTemplate(preferences) { const templatePath = path.join(this.packageDir, 'templates', 'settings.json.template'); const template = await fs.readFile(templatePath, 'utf8'); let command, args; if (preferences.installMethod === 'uv') { command = 'uv'; args = [ '--directory', this.packageDir, 'run', 'python', path.join(this.packageDir, 'main.py') ]; } else { const venvPython = path.join(this.packageDir, 'venv', 'Scripts', 'python.exe'); command = venvPython; args = [path.join(this.packageDir, 'main.py')]; } // Create proper JSON array string const argsJsonArray = JSON.stringify(args, null, 8).replace(/\n {8}/g, '\n '); const argsString = argsJsonArray.slice(1, -1); // Remove outer brackets const configContent = template .replace('"{{COMMAND}}"', JSON.stringify(command)) .replace('{{ARGS}}', argsString); return JSON.parse(configContent); } createMCPConfig(preferences) { const config = { servers: { 'darbot-windows-mcp': { type: 'stdio' } }, inputs: [] }; if (preferences.installMethod === 'uv') { config.servers['darbot-windows-mcp'].command = 'uv'; config.servers['darbot-windows-mcp'].args = [ '--directory', this.packageDir, 'run', 'python', path.join(this.packageDir, 'main.py') ]; } else { const venvPython = path.join(this.packageDir, 'venv', 'Scripts', 'python.exe'); config.servers['darbot-windows-mcp'].command = venvPython; config.servers['darbot-windows-mcp'].args = [path.join(this.packageDir, 'main.py')]; config.servers['darbot-windows-mcp'].cwd = this.packageDir; } return config; } createSettingsConfig(preferences) { const config = { env: {} }; if (preferences.installMethod === 'uv') { config.command = 'uv'; config.args = [ '--directory', this.packageDir, 'run', 'python', path.join(this.packageDir, 'main.py') ]; } else { const venvPython = path.join(this.packageDir, 'venv', 'Scripts', 'python.exe'); config.command = venvPython; config.args = [path.join(this.packageDir, 'main.py')]; config.cwd = this.packageDir; } return config; } runCommand(command, args = [], options = {}) { return new Promise((resolve, reject) => { const processOptions = { stdio: 'pipe', env: process.env, ...options }; const childProcess = spawn(command, args, processOptions); let stdout = ''; let stderr = ''; childProcess.stdout?.on('data', (data) => { stdout += data.toString(); }); childProcess.stderr?.on('data', (data) => { stderr += data.toString(); }); childProcess.on('close', (code) => { if (code === 0) { resolve({ stdout, stderr }); } else { reject(new Error(`Command failed with code ${code}: ${stderr}`)); } }); childProcess.on('error', reject); }); } } // Run setup if called directly if (require.main === module) { const setup = new DarbotSetup(); setup.run().catch((error) => { console.error(chalk.red('āŒ Setup failed:'), error.message); process.exit(1); }); }