UNPKG

shell-mirror

Version:

Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.

222 lines (189 loc) 6.64 kB
const inquirer = require('inquirer'); const fs = require('fs').promises; const path = require('path'); const os = require('os'); const crypto = require('crypto'); class SetupWizard { constructor() { this.configDir = path.join(os.homedir(), '.terminal-mirror'); this.configFile = path.join(this.configDir, 'config.json'); this.envFile = path.join(process.cwd(), '.env'); } async run(options = {}) { console.log('🚀 Welcome to Terminal Mirror Setup!'); console.log(''); console.log('This wizard will help you configure Google OAuth and get Terminal Mirror running.'); console.log(''); // Check if already configured if (!options.force && await this.isConfigured()) { const { proceed } = await inquirer.prompt([{ type: 'confirm', name: 'proceed', message: 'Terminal Mirror is already configured. Reconfigure?', default: false }]); if (!proceed) { console.log('Setup cancelled.'); return; } } console.log('📋 First, you\'ll need Google OAuth credentials.'); console.log(''); console.log('1. Go to: https://console.cloud.google.com/'); console.log('2. Create a new project or select existing one'); console.log('3. Enable the Google People API'); console.log('4. Create OAuth 2.0 credentials (Web application)'); console.log('5. Add authorized origins and redirect URIs'); console.log(''); const { ready } = await inquirer.prompt([{ type: 'confirm', name: 'ready', message: 'Have you completed the Google Cloud Console setup?', default: false }]); if (!ready) { console.log(''); console.log('Please complete the Google Cloud Console setup first.'); console.log('Detailed instructions: https://github.com/yourusername/terminal-mirror#setup'); return; } // Collect configuration const config = await this.collectConfig(); // Save configuration await this.saveConfig(config); // Run verification await this.verifySetup(config); console.log(''); console.log('✅ Setup completed successfully!'); console.log(''); console.log('Next steps:'); console.log('1. Run: terminal-mirror start'); console.log(`2. Open: ${config.baseUrl}`); console.log('3. Log in with your Google account'); console.log(''); } async collectConfig() { const questions = [ { type: 'input', name: 'clientId', message: 'Google Client ID:', validate: (input) => input.length > 0 || 'Client ID is required' }, { type: 'password', name: 'clientSecret', message: 'Google Client Secret:', validate: (input) => input.length > 0 || 'Client Secret is required' }, { type: 'list', name: 'environment', message: 'Environment:', choices: ['Development (localhost)', 'Production (custom domain)'], default: 'Development (localhost)' } ]; const answers = await inquirer.prompt(questions); let baseUrl, port = '3000'; if (answers.environment === 'Development (localhost)') { const portQuestion = await inquirer.prompt([{ type: 'input', name: 'port', message: 'Port number:', default: '3000', validate: (input) => { const port = parseInt(input); return (port > 0 && port < 65536) || 'Please enter a valid port number'; } }]); port = portQuestion.port; baseUrl = `http://localhost:${port}`; } else { const domainQuestion = await inquirer.prompt([{ type: 'input', name: 'domain', message: 'Your domain (e.g., example.com):', validate: (input) => input.length > 0 || 'Domain is required' }]); baseUrl = `https://${domainQuestion.domain}`; } // Generate secure session secret const sessionSecret = crypto.randomBytes(32).toString('hex'); return { clientId: answers.clientId, clientSecret: answers.clientSecret, baseUrl, port, sessionSecret, environment: answers.environment }; } async saveConfig(config) { // Ensure config directory exists await fs.mkdir(this.configDir, { recursive: true }); // Save to user config const userConfig = { baseUrl: config.baseUrl, port: config.port, environment: config.environment, setupDate: new Date().toISOString() }; await fs.writeFile(this.configFile, JSON.stringify(userConfig, null, 2)); // Create .env file const envContent = `# Terminal Mirror Configuration # Generated by setup wizard on ${new Date().toISOString()} BASE_URL=${config.baseUrl} PORT=${config.port} HOST=0.0.0.0 GOOGLE_CLIENT_ID=${config.clientId} GOOGLE_CLIENT_SECRET=${config.clientSecret} SESSION_SECRET=${config.sessionSecret} NODE_ENV=${config.environment === 'Production (custom domain)' ? 'production' : 'development'} `; await fs.writeFile(this.envFile, envContent); console.log(`✅ Configuration saved to ${this.envFile}`); } async verifySetup(config) { console.log(''); console.log('🔍 Verifying setup...'); // Check if all required files exist const checks = [ { name: 'Configuration file', path: this.configFile }, { name: 'Environment file', path: this.envFile } ]; for (const check of checks) { try { await fs.access(check.path); console.log(`✅ ${check.name} exists`); } catch (error) { console.log(`❌ ${check.name} missing`); throw new Error(`Setup verification failed: ${check.name} not found`); } } // Validate environment variables require('dotenv').config({ path: this.envFile }); const required = ['BASE_URL', 'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET', 'SESSION_SECRET']; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { console.log(`❌ Missing environment variables: ${missing.join(', ')}`); throw new Error('Environment validation failed'); } console.log('✅ Environment variables configured'); // Test OAuth configuration format if (!config.clientId.includes('.googleusercontent.com')) { console.log('⚠️ Warning: Client ID format looks unusual'); } console.log('✅ Setup verification completed'); } async isConfigured() { try { await fs.access(this.configFile); await fs.access(this.envFile); return true; } catch { return false; } } } module.exports = new SetupWizard();