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.

192 lines (157 loc) 5.7 kB
const fs = require('fs').promises; const path = require('path'); const os = require('os'); const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); class HealthChecker { constructor() { this.configDir = path.join(os.homedir(), '.terminal-mirror'); this.envFile = path.join(process.cwd(), '.env'); } async run() { console.log('🏥 Terminal Mirror Health Check'); console.log('─'.repeat(40)); console.log(''); const checks = [ { name: 'Node.js Version', check: () => this.checkNodeVersion() }, { name: 'Configuration Files', check: () => this.checkConfigFiles() }, { name: 'Environment Variables', check: () => this.checkEnvironment() }, { name: 'Network Connectivity', check: () => this.checkNetwork() }, { name: 'Dependencies', check: () => this.checkDependencies() }, { name: 'Port Availability', check: () => this.checkPort() }, { name: 'Google OAuth Setup', check: () => this.checkOAuth() }, { name: 'Permissions', check: () => this.checkPermissions() } ]; let allPassed = true; for (const check of checks) { try { const result = await check.check(); console.log(`✅ ${check.name}: ${result}`); } catch (error) { console.log(`❌ ${check.name}: ${error.message}`); allPassed = false; } } console.log(''); if (allPassed) { console.log('🎉 All health checks passed! Terminal Mirror should work correctly.'); } else { console.log('⚠️ Some health checks failed. Please address the issues above.'); console.log(''); console.log('Common solutions:'); console.log('• Run "terminal-mirror setup" to reconfigure'); console.log('• Check your Google OAuth credentials'); console.log('• Ensure the port is not in use by another application'); console.log('• Verify your internet connection'); } } async checkNodeVersion() { const version = process.version; const major = parseInt(version.split('.')[0].substring(1)); if (major < 14) { throw new Error(`Node.js ${major} is too old. Requires Node.js 14 or newer.`); } return `${version} (supported)`; } async checkConfigFiles() { const files = [ { path: this.envFile, name: '.env' }, { path: path.join(this.configDir, 'config.json'), name: 'user config' } ]; let found = 0; for (const file of files) { try { await fs.access(file.path); found++; } catch (error) { // File doesn't exist } } if (found === 0) { throw new Error('No configuration files found. Run "terminal-mirror setup".'); } return `${found}/${files.length} files found`; } async checkEnvironment() { 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) { throw new Error(`Missing variables: ${missing.join(', ')}`); } return 'All required variables set'; } async checkNetwork() { try { // Test DNS resolution await execAsync('nslookup google.com'); return 'Internet connectivity OK'; } catch (error) { throw new Error('Cannot resolve DNS. Check internet connection.'); } } async checkDependencies() { const packageJsonPath = path.join(__dirname, '..', 'package.json'); try { const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); const dependencies = Object.keys(packageJson.dependencies || {}); // Check if node_modules exists const nodeModulesPath = path.join(__dirname, '..', 'node_modules'); await fs.access(nodeModulesPath); return `${dependencies.length} dependencies installed`; } catch (error) { throw new Error('Dependencies not installed. Run "npm install".'); } } async checkPort() { require('dotenv').config({ path: this.envFile }); const port = process.env.PORT || 3000; try { const { stdout } = await execAsync(`lsof -i :${port}`); if (stdout.trim()) { throw new Error(`Port ${port} is already in use`); } } catch (error) { if (error.message.includes('already in use')) { throw error; } // lsof command failed (probably port is free) } return `Port ${port} is available`; } async checkOAuth() { require('dotenv').config({ path: this.envFile }); const clientId = process.env.GOOGLE_CLIENT_ID; const clientSecret = process.env.GOOGLE_CLIENT_SECRET; if (!clientId || !clientSecret) { throw new Error('OAuth credentials not configured'); } // Basic format validation if (!clientId.includes('.googleusercontent.com')) { throw new Error('Client ID format appears invalid'); } if (clientSecret.length < 20) { throw new Error('Client secret appears too short'); } return 'OAuth credentials format looks correct'; } async checkPermissions() { // Check if we can write to config directory try { await fs.mkdir(this.configDir, { recursive: true }); const testFile = path.join(this.configDir, 'test-permissions.tmp'); await fs.writeFile(testFile, 'test'); await fs.unlink(testFile); return 'File system permissions OK'; } catch (error) { throw new Error('Cannot write to config directory: ' + error.message); } } } module.exports = new HealthChecker();