98 lines
3.6 KiB
TypeScript
98 lines
3.6 KiB
TypeScript
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<HermesStatus> {
|
|
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;
|
|
}
|
|
}
|
|
|