From 0a079f73212d43f6f5493a7f8d87f0deb82bdbfc Mon Sep 17 00:00:00 2001 From: emozilla Date: Thu, 28 May 2026 02:42:33 -0400 Subject: [PATCH] fix(installer): pass -IncludeDesktop to manifest, surface launch errors, alias hermes desktop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs found in the first VM end-to-end test: 1. install.ps1 -Manifest was called WITHOUT -IncludeDesktop, so the manifest came back with the 14-stage list (no desktop stage), the UI showed '14 steps' and Stage-Desktop never ran. Pass the flag to both the manifest fetch and the per-stage runs — install.ps1 gates the desktop stage's inclusion on the flag. 2. The Success screen's Launch button silently swallowed the Tauri error when no Hermes.exe existed (e.g. Stage-Desktop was skipped). Wire the error through to inline UI with an alert callout, so the user gets actionable text ('Hermes.exe missing, run hermes desktop from a terminal') instead of an unresponsive button. 3. The Success screen tells users to run 'hermes desktop' from a terminal but the CLI only accepted 'hermes gui' — invalid choice for 'desktop'. Rename the subcommand canonically to 'desktop' with 'gui' as a backwards-compatible alias. Update the _SUBCOMMANDS sets used by session-flag arg parsing + logging-mode probe so both names route to the same logic. --- apps/bootstrap-installer/.gitignore | 6 +++ .../src-tauri/src/bootstrap.rs | 23 +++++++++- .../src/routes/success.tsx | 43 +++++++++++++++++-- hermes_cli/main.py | 18 +++++--- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/apps/bootstrap-installer/.gitignore b/apps/bootstrap-installer/.gitignore index 97f17a951be..bc961ce5a39 100644 --- a/apps/bootstrap-installer/.gitignore +++ b/apps/bootstrap-installer/.gitignore @@ -7,6 +7,12 @@ /dist-ssr/ *.local +# TypeScript build info + tsc emit (we don't ship .js for the +# vite.config.ts; Vite reads it directly via ts-node-style loader). +*.tsbuildinfo +vite.config.d.ts +vite.config.js + # Tauri generated artifacts (regenerated on each build) /src-tauri/gen/schemas/ diff --git a/apps/bootstrap-installer/src-tauri/src/bootstrap.rs b/apps/bootstrap-installer/src-tauri/src/bootstrap.rs index 787afe5d959..c6bd1a6b810 100644 --- a/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +++ b/apps/bootstrap-installer/src-tauri/src/bootstrap.rs @@ -158,14 +158,24 @@ pub async fn get_bootstrap_status( /// Spawn the locally-built Hermes desktop binary, then close the installer /// window. Caller resolves the binary path from `install_root`. +/// +/// Returns Err with a human-readable message if the binary doesn't exist +/// (e.g. when Stage-Desktop was skipped) so the frontend can present +/// actionable failure UI rather than silently doing nothing. #[tauri::command] pub async fn launch_hermes_desktop( app: AppHandle, install_root: String, ) -> Result<(), String> { let install_root = PathBuf::from(install_root); - let exe_path = resolve_hermes_desktop_exe(&install_root) - .ok_or_else(|| "Could not locate a built Hermes desktop binary".to_string())?; + let exe_path = resolve_hermes_desktop_exe(&install_root).ok_or_else(|| { + format!( + "Couldn't find a built Hermes desktop at {}. The desktop build step \ + may have been skipped or failed. Run `hermes desktop` from a \ + terminal to build and launch it.", + install_root.join("apps").join("desktop").join("release").display() + ) + })?; tracing::info!(?exe_path, "launching Hermes desktop"); @@ -285,9 +295,18 @@ async fn run_bootstrap( )); // 2. Fetch manifest + // + // -IncludeDesktop MUST be passed to the manifest call too — install.ps1 + // gates the desktop stage inclusion on this flag, so without it here + // the manifest comes back missing the desktop stage and we never run + // it. The per-stage call below also passes -IncludeDesktop to keep + // the contracts identical. let manifest_args = build_pin_args(&script); let mut manifest_args_full = vec!["-Manifest".to_string()]; manifest_args_full.extend(manifest_args.clone()); + if args.include_desktop { + manifest_args_full.push("-IncludeDesktop".to_string()); + } let manifest_result = run_install_script( &app, diff --git a/apps/bootstrap-installer/src/routes/success.tsx b/apps/bootstrap-installer/src/routes/success.tsx index 0a8480b7b13..3b0c17d5050 100644 --- a/apps/bootstrap-installer/src/routes/success.tsx +++ b/apps/bootstrap-installer/src/routes/success.tsx @@ -1,16 +1,37 @@ +import { useState } from 'react' import { type CSSProperties } from 'react' import { Button } from '../components/button' import { launchHermesDesktop } from '../store' -import { Rocket } from 'lucide-react' +import { Rocket, AlertCircle } from 'lucide-react' /* * Success screen. HERMES AGENT wordmark stays as the visual anchor * (same Collapse Bold treatment as Welcome + the desktop chat intro), * with a status line below. * - * No install-path footer — same rationale as Welcome. + * Launching the desktop can fail (e.g. Stage-Desktop was skipped and + * Hermes.exe doesn't exist). We catch the Tauri error and surface it + * inline rather than silently doing nothing — the previous version + * had `onClick={() => void launchHermesDesktop()}` which swallowed + * the rejection and left the user staring at an unresponsive button. */ export default function Success() { + const [error, setError] = useState(null) + const [launching, setLaunching] = useState(false) + + async function handleLaunch() { + setError(null) + setLaunching(true) + try { + await launchHermesDesktop() + // On success the installer exits — control never returns here. + } catch (e) { + const msg = e instanceof Error ? e.message : String(e) + setError(msg) + setLaunching(false) + } + } + return (
@@ -40,13 +61,27 @@ export default function Success() {
+ + {error && ( +
+ +
+
Couldn’t launch the desktop app
+
{error}
+
+
+ )}
) } diff --git a/hermes_cli/main.py b/hermes_cli/main.py index b3670d122fd..88b9e91566c 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -350,7 +350,7 @@ try: mode=( "gui" if next((arg for arg in sys.argv[1:] if not arg.startswith("-")), "") - in {"dashboard", "gui"} + in {"dashboard", "gui", "desktop"} else "cli" ) ) @@ -10172,6 +10172,7 @@ def _coalesce_session_name_args(argv: list) -> list: "uninstall", "profile", "dashboard", + "desktop", "gui", "honcho", "claw", @@ -11043,7 +11044,7 @@ _BUILTIN_SUBCOMMANDS = frozenset( "computer-use", "config", "cron", "curator", "dashboard", "debug", "doctor", "dump", "fallback", "gateway", "hooks", "import", "insights", - "gui", "kanban", "login", "logout", "logs", "lsp", "mcp", "memory", "migrate", + "gui", "desktop", "kanban", "login", "logout", "logs", "lsp", "mcp", "memory", "migrate", "model", "pairing", "plugins", "portal", "postinstall", "profile", "proxy", "send", "sessions", "setup", "skills", "slack", "status", "tools", "uninstall", "update", @@ -14136,11 +14137,18 @@ Examples: dashboard_parser.set_defaults(func=cmd_dashboard) # ========================================================================= - # gui command + # desktop (a.k.a. gui) command + # + # The canonical name is "desktop"; "gui" is kept as a deprecated alias + # for one release. The Hermes-Setup.exe success screen tells users to + # run `hermes desktop` from a terminal, so the canonical name needs + # to be the one that appears in --help (argparse promotes the primary + # name; aliases stay hidden). # ========================================================================= gui_parser = subparsers.add_parser( - "gui", - help="Build and launch the native desktop GUI", + "desktop", + aliases=["gui"], + help="Build and launch the native desktop app", description=( "Launch the Hermes Electron desktop app. By default this installs " "workspace Node dependencies, builds the current OS's unpacked "