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
JavaScript
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();