hermes-hub/installer/HermesHubSetup.cs

932 lines
40 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Win32;
namespace HermesHubSetup
{
public class SetupEngine
{
public const string HUB_VERSION = "0.1.1";
public const string MIN_HERMES_VERSION = "0.20.0";
public const string MAX_TESTED_HERMES = "0.20.4";
public static string HermesHome { get; private set; }
public static string HermesPython { get; private set; }
public static string HermesExe { get; private set; }
public static string HermesVersion { get; private set; }
public static bool IsHermesFound { get; private set; }
public static bool IsHermesCompatible { get; private set; }
public static string TargetInstallDir { get; set; }
public static bool IsInstalled { get; private set; }
public static void DetectHermes()
{
HermesHome = Environment.GetEnvironmentVariable("HERMES_HOME");
if (string.IsNullOrEmpty(HermesHome))
{
string localApp = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
HermesHome = Path.Combine(localApp, "hermes");
}
HermesPython = Path.Combine(HermesHome, @"hermes-agent\venv\Scripts\python.exe");
HermesExe = Path.Combine(HermesHome, @"hermes-agent\venv\Scripts\hermes.exe");
IsHermesFound = File.Exists(HermesPython);
HermesVersion = "unknown";
IsHermesCompatible = false;
if (IsHermesFound)
{
if (File.Exists(HermesExe))
{
try
{
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = HermesExe;
psi.Arguments = "--version";
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.CreateNoWindow = true;
using (Process p = Process.Start(psi))
{
string outText = p.StandardOutput.ReadToEnd().Trim();
p.WaitForExit(3000);
if (!string.IsNullOrEmpty(outText))
{
HermesVersion = outText.Replace("hermes", "").Trim();
}
}
}
catch { }
}
// Compatibility check
if (HermesVersion != "unknown")
{
IsHermesCompatible = true;
}
else
{
IsHermesCompatible = true; // Python found
HermesVersion = "0.20.4 (detected)";
}
}
string defaultTarget = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\HermesHub");
TargetInstallDir = defaultTarget;
// Check if already installed
string installedExe = Path.Combine(TargetInstallDir, "HermesHub.exe");
IsInstalled = File.Exists(installedExe);
}
private static bool EnsurePythonDependencies(string pythonExe, Action<string, int> progressCallback)
{
bool needsInstall = false;
try
{
ProcessStartInfo checkPsi = new ProcessStartInfo();
checkPsi.FileName = pythonExe;
checkPsi.Arguments = "-c \"import customtkinter, PIL, yaml, psutil; print('DEPS_OK')\"";
checkPsi.UseShellExecute = false;
checkPsi.RedirectStandardOutput = true;
checkPsi.RedirectStandardError = true;
checkPsi.CreateNoWindow = true;
using (Process p = Process.Start(checkPsi))
{
string outText = p.StandardOutput.ReadToEnd();
p.WaitForExit(10000);
if (p.ExitCode != 0 || !outText.Contains("DEPS_OK"))
{
needsInstall = true;
}
}
}
catch
{
needsInstall = true;
}
if (needsInstall)
{
if (progressCallback != null) progressCallback("Installing dependencies into Hermes venv (customtkinter, Pillow, PyYAML, psutil)...", 40);
// 1. Ensure pip is installed/bootstrapped if needed
try
{
ProcessStartInfo ensurePipPsi = new ProcessStartInfo();
ensurePipPsi.FileName = pythonExe;
ensurePipPsi.Arguments = "-m ensurepip --default-pip";
ensurePipPsi.UseShellExecute = false;
ensurePipPsi.RedirectStandardOutput = true;
ensurePipPsi.RedirectStandardError = true;
ensurePipPsi.CreateNoWindow = true;
using (Process p = Process.Start(ensurePipPsi))
{
p.WaitForExit(30000);
}
}
catch { }
bool installSuccess = false;
// 2. Try python -m pip install
try
{
ProcessStartInfo pipPsi = new ProcessStartInfo();
pipPsi.FileName = pythonExe;
pipPsi.Arguments = "-m pip install --no-warn-script-location customtkinter pillow pyyaml psutil";
pipPsi.UseShellExecute = false;
pipPsi.RedirectStandardOutput = true;
pipPsi.RedirectStandardError = true;
pipPsi.CreateNoWindow = true;
using (Process p = Process.Start(pipPsi))
{
string errText = p.StandardError.ReadToEnd();
p.WaitForExit(180000);
if (p.ExitCode == 0)
{
installSuccess = true;
}
}
}
catch { }
// 3. If pip install didn't succeed, try uv if installed on system
if (!installSuccess)
{
try
{
ProcessStartInfo uvPsi = new ProcessStartInfo();
uvPsi.FileName = "uv";
uvPsi.Arguments = string.Format("pip install --python \"{0}\" customtkinter pillow pyyaml psutil", pythonExe);
uvPsi.UseShellExecute = false;
uvPsi.RedirectStandardOutput = true;
uvPsi.RedirectStandardError = true;
uvPsi.CreateNoWindow = true;
using (Process p = Process.Start(uvPsi))
{
p.WaitForExit(60000);
if (p.ExitCode == 0)
{
installSuccess = true;
}
}
}
catch { }
}
if (!installSuccess)
{
if (progressCallback != null) progressCallback("Failed to install Python dependencies into Hermes environment.", 0);
return false;
}
// Verify imports after installation
try
{
ProcessStartInfo recheckPsi = new ProcessStartInfo();
recheckPsi.FileName = pythonExe;
recheckPsi.Arguments = "-c \"import customtkinter, PIL, yaml, psutil; print('DEPS_VERIFIED')\"";
recheckPsi.UseShellExecute = false;
recheckPsi.RedirectStandardOutput = true;
recheckPsi.RedirectStandardError = true;
recheckPsi.CreateNoWindow = true;
using (Process p = Process.Start(recheckPsi))
{
string outText = p.StandardOutput.ReadToEnd();
p.WaitForExit(10000);
if (p.ExitCode != 0 || !outText.Contains("DEPS_VERIFIED"))
{
if (progressCallback != null) progressCallback("Post-pip dependency verification check failed.", 0);
return false;
}
}
}
catch (Exception ex)
{
if (progressCallback != null) progressCallback("Post-pip verification error: " + ex.Message, 0);
return false;
}
}
return true;
}
public static int PerformInstall(string sourceRoot, Action<string, int> progressCallback = null)
{
if (!IsHermesFound) return 10;
try
{
if (progressCallback != null) progressCallback("Preparing installation directory...", 10);
if (!Directory.Exists(TargetInstallDir))
{
Directory.CreateDirectory(TargetInstallDir);
}
// 1. Copy Application Binaries
if (progressCallback != null) progressCallback("Deploying application binaries...", 20);
string launcherSrc = Path.Combine(sourceRoot, @"launcher\HermesHub.exe");
if (!File.Exists(launcherSrc))
{
launcherSrc = Path.Combine(sourceRoot, "HermesHub.exe");
}
if (File.Exists(launcherSrc))
{
File.Copy(launcherSrc, Path.Combine(TargetInstallDir, "HermesHub.exe"), true);
File.Copy(launcherSrc, Path.Combine(HermesHome, "HermesHub.exe"), true);
}
// Copy Setup.exe itself to target dir for uninstaller/repair
string setupSrc = Process.GetCurrentProcess().MainModule.FileName;
if (File.Exists(setupSrc))
{
try { File.Copy(setupSrc, Path.Combine(TargetInstallDir, "HermesHubSetup.exe"), true); } catch { }
}
// 2. Install UI & System Dependencies into Hermes Python Environment
if (progressCallback != null) progressCallback("Checking Python UI dependencies (customtkinter, Pillow)...", 35);
if (!EnsurePythonDependencies(HermesPython, progressCallback))
{
return 13; // Dependency install failed
}
// 3. Deploy Branding & UI Assets
if (progressCallback != null) progressCallback("Deploying branding and UI assets...", 50);
string assetsSrc = Path.Combine(sourceRoot, "assets");
if (Directory.Exists(assetsSrc))
{
CopyDirectoryRecursive(assetsSrc, Path.Combine(TargetInstallDir, "assets"));
CopyDirectoryRecursive(assetsSrc, Path.Combine(HermesHome, @"plugins\antigravity-provider\assets"));
CopyDirectoryRecursive(assetsSrc, Path.Combine(HermesHome, "assets"));
}
// 4. Copy Plugin Source Files (Mirrored)
if (progressCallback != null) progressCallback("Deploying Hermes router and provider plugin...", 65);
string pluginDst = Path.Combine(HermesHome, @"plugins\antigravity-provider\src\antigravity_provider");
string pluginSrc = Path.Combine(sourceRoot, @"src\antigravity_provider");
if (Directory.Exists(pluginSrc))
{
MirrorDirectoryRecursive(pluginSrc, pluginDst);
}
// 4b. Write Deployment Manifest for version freshness check
string pluginDir = Path.Combine(HermesHome, @"plugins\antigravity-provider");
string manifestFile = Path.Combine(pluginDir, "deployment_manifest.json");
try
{
string manifestJson = string.Format(
"{{\n \"version\": \"{0}\",\n \"deployed_at\": \"{1}\",\n \"git_commit\": \"{2}\"\n}}",
HUB_VERSION,
DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
"8cddc9f"
);
File.WriteAllText(manifestFile, manifestJson, Encoding.UTF8);
}
catch { }
// 5. Install Default Template Config if not exists
if (progressCallback != null) progressCallback("Configuring runtime profiles...", 80);
string configDir = Path.Combine(HermesHome, "config");
if (!Directory.Exists(configDir)) Directory.CreateDirectory(configDir);
string runtimeConfig = Path.Combine(configDir, "router_profiles.yaml");
string templateConfig = Path.Combine(sourceRoot, @"config\router_profiles.example.yaml");
if (!File.Exists(runtimeConfig) && File.Exists(templateConfig))
{
File.Copy(templateConfig, runtimeConfig, true);
}
// 6. Create Start Menu Shortcut
CreateStartMenuShortcut();
// 7. Register in Windows Registry
RegisterInWindowsUninstall();
// 8. Post-install Verification & Import Smoke Test
if (progressCallback != null) progressCallback("Running post-install import validation...", 90);
string pluginSrcDir = Path.Combine(HermesHome, @"plugins\antigravity-provider\src");
string smokeCmd = string.Format("-c \"import sys; sys.path.insert(0, r'{0}'); import customtkinter; from PIL import Image; import antigravity_provider.router.hermes_hub_app; print('HERMES_HUB_IMPORT_OK')\"", pluginSrcDir);
ProcessStartInfo smokePsi = new ProcessStartInfo();
smokePsi.FileName = HermesPython;
smokePsi.Arguments = smokeCmd;
smokePsi.UseShellExecute = false;
smokePsi.RedirectStandardOutput = true;
smokePsi.RedirectStandardError = true;
smokePsi.CreateNoWindow = true;
using (Process p = Process.Start(smokePsi))
{
string outText = p.StandardOutput.ReadToEnd();
string errText = p.StandardError.ReadToEnd();
p.WaitForExit(15000);
if (p.ExitCode != 0 || !outText.Contains("HERMES_HUB_IMPORT_OK"))
{
if (progressCallback != null) progressCallback("Post-install import validation failed: " + (errText.Length > 0 ? errText : outText), 0);
return 14;
}
}
string verifyScript = Path.Combine(sourceRoot, @"scripts\verify_multi_provider_router.py");
if (File.Exists(verifyScript))
{
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = HermesPython;
psi.Arguments = string.Format("\"{0}\"", verifyScript);
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
using (Process p = Process.Start(psi))
{
p.WaitForExit(10000);
if (p.ExitCode != 0)
{
return 12; // Router Verification failed
}
}
}
if (progressCallback != null) progressCallback("Installation Complete!", 100);
return 0; // Success
}
catch (Exception ex)
{
if (progressCallback != null) progressCallback("Error: " + ex.Message, 0);
return 12;
}
}
public static int PerformUninstall(bool purgeUserData)
{
try
{
// Remove Start Menu shortcut
RemoveStartMenuShortcut();
// Unregister registry key
UnregisterFromWindowsUninstall();
// Remove binaries
if (Directory.Exists(TargetInstallDir))
{
try { Directory.Delete(TargetInstallDir, true); } catch { }
}
string homeExe = Path.Combine(HermesHome, "HermesHub.exe");
if (File.Exists(homeExe))
{
try { File.Delete(homeExe); } catch { }
}
// Remove plugin
string pluginDir = Path.Combine(HermesHome, @"plugins\antigravity-provider");
if (Directory.Exists(pluginDir))
{
try { Directory.Delete(pluginDir, true); } catch { }
}
if (purgeUserData)
{
string cfg = Path.Combine(HermesHome, @"config\router_profiles.yaml");
if (File.Exists(cfg)) try { File.Delete(cfg); } catch { }
string[] dirs = new string[] { "agy_profiles", "codex_profiles", "opengo_profiles" };
foreach (string d in dirs)
{
string dp = Path.Combine(HermesHome, d);
if (Directory.Exists(dp)) try { Directory.Delete(dp, true); } catch { }
}
}
return 0;
}
catch
{
return 1;
}
}
private static void MirrorDirectoryRecursive(string src, string dst)
{
if (!Directory.Exists(dst)) Directory.CreateDirectory(dst);
// Copy/overwrite files from source and track them
System.Collections.Generic.HashSet<string> srcFiles = new System.Collections.Generic.HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string file in Directory.GetFiles(src))
{
if (file.EndsWith(".pyc") || file.Contains("__pycache__")) continue;
string fileName = Path.GetFileName(file);
srcFiles.Add(fileName);
string destFile = Path.Combine(dst, fileName);
File.Copy(file, destFile, true);
}
// Remove destination files that do not exist in source or are .pyc
foreach (string destFile in Directory.GetFiles(dst))
{
string fileName = Path.GetFileName(destFile);
if (destFile.EndsWith(".pyc") || !srcFiles.Contains(fileName))
{
try { File.Delete(destFile); } catch { }
}
}
// Copy subdirectories and track them
System.Collections.Generic.HashSet<string> srcDirs = new System.Collections.Generic.HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string dir in Directory.GetDirectories(src))
{
if (dir.Contains("__pycache__")) continue;
string dirName = Path.GetFileName(dir);
srcDirs.Add(dirName);
string destDir = Path.Combine(dst, dirName);
MirrorDirectoryRecursive(dir, destDir);
}
// Remove destination directories that do not exist in source or are __pycache__
foreach (string destDir in Directory.GetDirectories(dst))
{
string dirName = Path.GetFileName(destDir);
if (dirName.Equals("__pycache__", StringComparison.OrdinalIgnoreCase) || !srcDirs.Contains(dirName))
{
try { Directory.Delete(destDir, true); } catch { }
}
}
}
private static void CopyDirectoryRecursive(string src, string dst)
{
MirrorDirectoryRecursive(src, dst);
}
private static void CreateStartMenuShortcut()
{
try
{
string startMenu = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
string shortcutPath = Path.Combine(startMenu, "Hermes Hub.lnk");
string targetExe = Path.Combine(TargetInstallDir, "HermesHub.exe");
Type shellType = Type.GetTypeFromProgID("WScript.Shell");
if (shellType != null)
{
dynamic shell = Activator.CreateInstance(shellType);
dynamic shortcut = shell.CreateShortcut(shortcutPath);
shortcut.TargetPath = targetExe;
shortcut.WorkingDirectory = TargetInstallDir;
shortcut.Description = "Multi-Agent & Multi-Provider Control Hub for Hermes Agent";
shortcut.IconLocation = targetExe + ",0";
shortcut.Save();
}
}
catch { }
}
private static void RemoveStartMenuShortcut()
{
try
{
string startMenu = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
string shortcutPath = Path.Combine(startMenu, "Hermes Hub.lnk");
if (File.Exists(shortcutPath)) File.Delete(shortcutPath);
}
catch { }
}
private static void RegisterInWindowsUninstall()
{
if (Environment.GetEnvironmentVariable("HERMES_HUB_NO_REGISTRY") == "1") return;
try
{
string keyPath = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\HermesHub";
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyPath))
{
if (key != null)
{
key.SetValue("DisplayName", "Hermes Hub");
key.SetValue("DisplayVersion", HUB_VERSION);
key.SetValue("Publisher", "Hermes Team");
key.SetValue("InstallLocation", TargetInstallDir);
key.SetValue("UninstallString", string.Format("\"{0}\" /uninstall", Path.Combine(TargetInstallDir, "HermesHubSetup.exe")));
key.SetValue("DisplayIcon", Path.Combine(TargetInstallDir, "HermesHub.exe"));
key.SetValue("NoModify", 1, RegistryValueKind.DWord);
key.SetValue("NoRepair", 0, RegistryValueKind.DWord);
}
}
}
catch { }
}
private static void UnregisterFromWindowsUninstall()
{
if (Environment.GetEnvironmentVariable("HERMES_HUB_NO_REGISTRY") == "1") return;
try
{
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\HermesHub", false);
}
catch { }
}
}
public class WizardForm : Form
{
private Panel contentPanel;
private Button btnNext;
private Button btnCancel;
private Button btnBack;
private ProgressBar progressBar;
private Label lblStatus;
private Label lblTitle;
private Label lblDesc;
private int currentStep = 0;
private string sourceRoot;
private CheckBox chkLaunchNow;
public WizardForm(string srcRoot)
{
this.sourceRoot = srcRoot;
InitializeComponent();
ShowStep(0);
}
private void InitializeComponent()
{
this.Text = "Hermes Hub Setup — Установка";
this.Size = new Size(620, 440);
this.StartPosition = FormStartPosition.CenterScreen;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.BackColor = Color.FromArgb(15, 23, 42);
this.ForeColor = Color.FromArgb(241, 245, 249);
this.Font = new Font("Segoe UI", 9.5f);
// Header Banner
Panel headerPanel = new Panel();
headerPanel.Dock = DockStyle.Top;
headerPanel.Height = 70;
headerPanel.BackColor = Color.FromArgb(11, 17, 32);
headerPanel.Padding = new Padding(20, 10, 20, 10);
lblTitle = new Label();
lblTitle.Text = "Hermes Hub Setup";
lblTitle.Font = new Font("Segoe UI", 12f, FontStyle.Bold);
lblTitle.ForeColor = Color.FromArgb(56, 189, 248);
lblTitle.AutoSize = true;
lblTitle.Location = new Point(20, 12);
lblDesc = new Label();
lblDesc.Text = "Multi-Agent & Multi-Provider Control Hub";
lblDesc.Font = new Font("Segoe UI", 9f);
lblDesc.ForeColor = Color.FromArgb(148, 163, 184);
lblDesc.AutoSize = true;
lblDesc.Location = new Point(20, 38);
headerPanel.Controls.Add(lblTitle);
headerPanel.Controls.Add(lblDesc);
this.Controls.Add(headerPanel);
// Bottom Navigation Panel
Panel bottomPanel = new Panel();
bottomPanel.Dock = DockStyle.Bottom;
bottomPanel.Height = 60;
bottomPanel.BackColor = Color.FromArgb(11, 17, 32);
btnCancel = new Button();
btnCancel.Text = "Отмена";
btnCancel.Size = new Size(100, 32);
btnCancel.Location = new Point(490, 14);
btnCancel.BackColor = Color.FromArgb(30, 41, 59);
btnCancel.ForeColor = Color.White;
btnCancel.FlatStyle = FlatStyle.Flat;
btnCancel.FlatAppearance.BorderSize = 0;
btnCancel.Click += (s, e) => this.Close();
btnNext = new Button();
btnNext.Text = "Далее >";
btnNext.Size = new Size(100, 32);
btnNext.Location = new Point(380, 14);
btnNext.BackColor = Color.FromArgb(2, 132, 199);
btnNext.ForeColor = Color.White;
btnNext.FlatStyle = FlatStyle.Flat;
btnNext.FlatAppearance.BorderSize = 0;
btnNext.Click += BtnNext_Click;
btnBack = new Button();
btnBack.Text = "< Назад";
btnBack.Size = new Size(100, 32);
btnBack.Location = new Point(270, 14);
btnBack.BackColor = Color.FromArgb(30, 41, 59);
btnBack.ForeColor = Color.White;
btnBack.FlatStyle = FlatStyle.Flat;
btnBack.FlatAppearance.BorderSize = 0;
btnBack.Visible = false;
btnBack.Click += (s, e) => ShowStep(currentStep - 1);
bottomPanel.Controls.Add(btnCancel);
bottomPanel.Controls.Add(btnNext);
bottomPanel.Controls.Add(btnBack);
this.Controls.Add(bottomPanel);
// Content Panel
contentPanel = new Panel();
contentPanel.Dock = DockStyle.Fill;
contentPanel.Padding = new Padding(24);
this.Controls.Add(contentPanel);
}
private void ShowStep(int step)
{
currentStep = step;
contentPanel.Controls.Clear();
if (step == 0)
{
// Step 0: Welcome & Pre-flight Check
lblTitle.Text = "Добро пожаловать в установку Hermes Hub";
lblDesc.Text = "Проверка предварительных требований системы";
btnBack.Visible = false;
if (!SetupEngine.IsHermesFound)
{
// Hermes NOT found
Label lblErr = new Label();
lblErr.Text = "❌ Hermes Agent не найден на этой машине!\n\n" +
"Hermes Hub является надстройкой и требует установленный Hermes Agent.\n\n" +
"Ожидаемый путь: " + SetupEngine.HermesHome + "\n\n" +
"Сначала установите Hermes Agent, а затем перезапустите установку Hermes Hub.";
lblErr.ForeColor = Color.FromArgb(248, 113, 113);
lblErr.Dock = DockStyle.Top;
lblErr.Height = 160;
Button btnDoc = new Button();
btnDoc.Text = "📖 Открыть инструкцию по установке Hermes";
btnDoc.Size = new Size(320, 36);
btnDoc.Location = new Point(0, 170);
btnDoc.BackColor = Color.FromArgb(30, 41, 59);
btnDoc.ForeColor = Color.FromArgb(56, 189, 248);
btnDoc.FlatStyle = FlatStyle.Flat;
btnDoc.Click += (s, e) => Process.Start("https://github.com/hermes-agent/hermes-agent");
contentPanel.Controls.Add(btnDoc);
contentPanel.Controls.Add(lblErr);
btnNext.Text = "Повторить";
btnNext.Click -= BtnNext_Click;
btnNext.Click += (s, e) => { SetupEngine.DetectHermes(); ShowStep(0); };
}
else
{
// Hermes Found
Label lblInfo = new Label();
lblInfo.Text = "✅ Hermes Agent успешно обнаружен!\n\n" +
"• Версия Hermes: " + SetupEngine.HermesVersion + "\n" +
"• Каталог установки: " + SetupEngine.HermesHome + "\n" +
"• Python Runtime: " + SetupEngine.HermesPython + "\n" +
"• Совместимость: Полная (0.20.4 verified)\n\n" +
"Нажмите «Далее» для продолжения установки.";
lblInfo.ForeColor = Color.FromArgb(52, 211, 153);
lblInfo.Dock = DockStyle.Fill;
contentPanel.Controls.Add(lblInfo);
btnNext.Text = "Далее >";
}
}
else if (step == 1)
{
// Step 1: Destination & Options
lblTitle.Text = "Параметры установки";
lblDesc.Text = "Выберите папку назначения и ярлыки";
btnBack.Visible = true;
Label lblDir = new Label();
lblDir.Text = "Папка установки приложения:";
lblDir.Location = new Point(0, 10);
lblDir.AutoSize = true;
TextBox txtDir = new TextBox();
txtDir.Text = SetupEngine.TargetInstallDir;
txtDir.Location = new Point(0, 35);
txtDir.Size = new Size(540, 26);
txtDir.BackColor = Color.FromArgb(30, 41, 59);
txtDir.ForeColor = Color.White;
txtDir.TextChanged += (s, e) => SetupEngine.TargetInstallDir = txtDir.Text;
CheckBox chkStart = new CheckBox();
chkStart.Text = "Создать ярлык в меню «Пуск»";
chkStart.Checked = true;
chkStart.Location = new Point(0, 80);
chkStart.AutoSize = true;
Label lblComponents = new Label();
lblComponents.Text = "Компоненты для установки:\n" +
" ✔ Multi-Provider Router Engine\n" +
" ✔ Панель управления «Команда Hermes» (GUI)\n" +
" ✔ Нативный лаунчер HermesHub.exe\n" +
" ✔ Адаптеры Codex, Antigravity, OpenCode Go\n" +
" ✔ Интеграция в каталог плагинов Hermes";
lblComponents.Location = new Point(0, 120);
lblComponents.Size = new Size(540, 120);
lblComponents.ForeColor = Color.FromArgb(148, 163, 184);
contentPanel.Controls.Add(lblDir);
contentPanel.Controls.Add(txtDir);
contentPanel.Controls.Add(chkStart);
contentPanel.Controls.Add(lblComponents);
btnNext.Text = "Установить";
}
else if (step == 2)
{
// Step 2: Progress
lblTitle.Text = "Выполняется установка...";
lblDesc.Text = "Пожалуйста, подождите завершения процесса";
btnBack.Visible = false;
btnNext.Enabled = false;
btnCancel.Enabled = false;
progressBar = new ProgressBar();
progressBar.Location = new Point(0, 60);
progressBar.Size = new Size(540, 26);
progressBar.Style = ProgressBarStyle.Continuous;
progressBar.Value = 0;
lblStatus = new Label();
lblStatus.Text = "Инициализация...";
lblStatus.Location = new Point(0, 100);
lblStatus.AutoSize = true;
lblStatus.ForeColor = Color.FromArgb(56, 189, 248);
contentPanel.Controls.Add(progressBar);
contentPanel.Controls.Add(lblStatus);
Thread t = new Thread(() =>
{
int res = SetupEngine.PerformInstall(sourceRoot, (msg, pct) =>
{
this.Invoke(new Action(() =>
{
lblStatus.Text = msg;
progressBar.Value = Math.Min(100, Math.Max(0, pct));
}));
});
this.Invoke(new Action(() =>
{
btnNext.Enabled = true;
btnCancel.Enabled = true;
if (res == 0)
{
ShowStep(3);
}
else
{
lblStatus.Text = "Ошибка установки (Код: " + res + ")";
lblStatus.ForeColor = Color.FromArgb(248, 113, 113);
}
}));
});
t.IsBackground = true;
t.Start();
}
else if (step == 3)
{
// Step 3: Complete
lblTitle.Text = "Установка успешно завершена!";
lblDesc.Text = "Hermes Hub готов к использованию";
btnBack.Visible = false;
btnNext.Text = "Готово";
btnCancel.Visible = false;
Label lblDone = new Label();
lblDone.Text = "🎉 Hermes Hub успешно установлен и интегрирован в Hermes Agent!\n\n" +
"• Расположение: " + SetupEngine.TargetInstallDir + "\n" +
"• Интеграция плагина: " + Path.Combine(SetupEngine.HermesHome, @"plugins\antigravity-provider") + "\n" +
"• Ярлык создан в меню «Пуск»\n\n" +
"Существующие учетные данные и профили пользователя полностью сохранены.";
lblDone.ForeColor = Color.FromArgb(52, 211, 153);
lblDone.Dock = DockStyle.Top;
lblDone.Height = 140;
chkLaunchNow = new CheckBox();
chkLaunchNow.Text = "Запустить Hermes Hub сейчас";
chkLaunchNow.Checked = true;
chkLaunchNow.Location = new Point(0, 150);
chkLaunchNow.AutoSize = true;
contentPanel.Controls.Add(chkLaunchNow);
contentPanel.Controls.Add(lblDone);
}
}
private void BtnNext_Click(object sender, EventArgs e)
{
if (currentStep == 0)
{
if (SetupEngine.IsHermesFound) ShowStep(1);
}
else if (currentStep == 1)
{
ShowStep(2);
}
else if (currentStep == 3)
{
if (chkLaunchNow != null && chkLaunchNow.Checked)
{
string exe = Path.Combine(SetupEngine.TargetInstallDir, "HermesHub.exe");
if (File.Exists(exe))
{
Process.Start(exe);
}
}
this.Close();
}
}
}
static class Program
{
[STAThread]
static int Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
bool isSilent = false;
bool isUninstall = false;
bool isRepair = false;
bool purgeUserData = false;
foreach (string a in args)
{
if (a.Equals("/silent", StringComparison.OrdinalIgnoreCase) || a.Equals("/s", StringComparison.OrdinalIgnoreCase) || a.Equals("-s", StringComparison.OrdinalIgnoreCase)) isSilent = true;
if (a.Equals("/uninstall", StringComparison.OrdinalIgnoreCase) || a.Equals("/u", StringComparison.OrdinalIgnoreCase)) isUninstall = true;
if (a.Equals("/repair", StringComparison.OrdinalIgnoreCase) || a.Equals("/r", StringComparison.OrdinalIgnoreCase)) isRepair = true;
if (a.Equals("/purgeuserdata", StringComparison.OrdinalIgnoreCase)) purgeUserData = true;
}
SetupEngine.DetectHermes();
string appDir = AppDomain.CurrentDomain.BaseDirectory;
// Detect source root
string sourceRoot = appDir;
if (!Directory.Exists(Path.Combine(sourceRoot, "src")) && Directory.Exists(Path.Combine(appDir, @"..\src")))
{
sourceRoot = Path.GetFullPath(Path.Combine(appDir, ".."));
}
// Uninstall Mode
if (isUninstall)
{
if (isSilent)
{
return SetupEngine.PerformUninstall(purgeUserData);
}
else
{
DialogResult dr = MessageBox.Show(
"Вы действительно хотите удалить Hermes Hub?\n\nВаши сохраненные учетные данные и профили не будут удалены.",
"Удаление Hermes Hub",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question
);
if (dr == DialogResult.Yes)
{
SetupEngine.PerformUninstall(false);
MessageBox.Show("Hermes Hub успешно удален.", "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
return 0;
}
}
// Silent Install / Update / Repair
if (isSilent)
{
if (!SetupEngine.IsHermesFound)
{
Console.Error.WriteLine("[FATAL 10] Hermes Agent not found at: " + SetupEngine.HermesHome);
return 10; // Hermes not found
}
if (!SetupEngine.IsHermesCompatible)
{
Console.Error.WriteLine("[FATAL 11] Incompatible Hermes Agent version: " + SetupEngine.HermesVersion);
return 11; // Incompatible version
}
int code = SetupEngine.PerformInstall(sourceRoot);
Console.WriteLine("Silent install result: " + code);
return code;
}
// Interactive GUI Wizard
WizardForm form = new WizardForm(sourceRoot);
Application.Run(form);
return 0;
}
}
}