import { spawn } from 'node:child_process'; import { join } from 'node:path'; import { getResourcesDir } from '../utils/paths'; export type HermesInstallState = 'unknown' | 'not_installed' | 'installed' | 'wsl_missing' | 'error'; export type HermesRunState = 'stopped' | 'running' | 'error'; export interface HermesStatus { installState: HermesInstallState; runState: HermesRunState; version?: string; error?: string; } function runPowerShell(scriptPath: string): Promise<{ success: boolean; stdout: string; stderr: string; exitCode: number | null }> { return new Promise((resolve) => { const child = spawn('powershell.exe', [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ], { windowsHide: true }); let stdout = ''; let stderr = ''; child.stdout.on('data', (d) => { stdout += String(d); }); child.stderr.on('data', (d) => { stderr += String(d); }); child.on('close', (code) => { resolve({ success: code === 0, stdout, stderr, exitCode: code }); }); }); } function runWsl(args: string[]): Promise<{ success: boolean; stdout: string; stderr: string; exitCode: number | null }> { return new Promise((resolve) => { const child = spawn('wsl.exe', args, { windowsHide: true }); let stdout = ''; let stderr = ''; child.stdout.on('data', (d) => { stdout += String(d); }); child.stderr.on('data', (d) => { stderr += String(d); }); child.on('close', (code) => resolve({ success: code === 0, stdout, stderr, exitCode: code })); }); } export class HermesManager { private status: HermesStatus = { installState: 'unknown', runState: 'stopped' }; getStatus(): HermesStatus { return { ...this.status }; } async refreshStatus(): Promise { if (process.platform !== 'win32') { this.status = { installState: 'error', runState: 'stopped', error: 'Hermes Windows integration requires WSL2' }; return this.getStatus(); } const wslCheck = await runWsl(['-e', 'bash', '-lc', 'echo ok']); if (!wslCheck.success) { this.status = { installState: 'wsl_missing', runState: 'stopped', error: wslCheck.stderr.trim() || 'WSL2 not available' }; return this.getStatus(); } const version = await runWsl(['-e', 'bash', '-lc', 'command -v hermes >/dev/null 2>&1 && hermes --version || hermes version || true']); const raw = (version.stdout || version.stderr).trim(); if (!raw) { this.status = { installState: 'not_installed', runState: 'stopped' }; return this.getStatus(); } this.status = { installState: 'installed', runState: this.status.runState, version: raw }; return this.getStatus(); } async install(): Promise<{ success: boolean; stdout: string; stderr: string; exitCode: number | null }> { if (process.platform !== 'win32') { return { success: false, stdout: '', stderr: 'Windows only', exitCode: 1 }; } const scriptPath = join(getResourcesDir(), 'hermes', 'install-hermes.ps1'); const result = await runPowerShell(scriptPath); await this.refreshStatus(); return result; } async startGateway(): Promise<{ success: boolean; stdout: string; stderr: string; exitCode: number | null }> { if (process.platform !== 'win32') { return { success: false, stdout: '', stderr: 'Windows only', exitCode: 1 }; } const scriptPath = join(getResourcesDir(), 'hermes', 'start-hermes.ps1'); const result = await runPowerShell(scriptPath); this.status = { ...this.status, runState: result.success ? 'running' : 'error', error: result.success ? undefined : result.stderr.trim() }; return result; } }