From fd88dd96a925fc5cb947931bc645c305f71f0833 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sat, 30 May 2026 19:24:27 -0500 Subject: [PATCH] feat(install): add --include-desktop stage + PowerShell-style flags to install.sh Brings install.sh to parity with install.ps1's bootstrap surface so the shared Rust/Tauri bootstrapper (apps/bootstrap-installer) can drive a macOS/Linux install the same way it drives Windows. - Accept the PowerShell-style aliases the bootstrapper emits to both installers: -Commit / -Branch (alongside existing -Manifest / -Stage / -Json / -NonInteractive). - Add --include-desktop / -IncludeDesktop. When set, the manifest gains a 'desktop' stage (immediately before 'complete'), and a new install_desktop runs a root workspace `npm install` + `npm run pack` (electron-builder --dir, signing auto-discovery disabled) to produce release/mac*/Hermes.app -- mirroring install.ps1's Install-Desktop / Stage-Desktop. - The flag is opt-in, exactly like Windows: the signed bootstrap installer passes it; the Electron app's own first-launch bootstrap and the CLI one-liner omit it (building the desktop from inside the running app would clobber it). --- scripts/install.sh | 92 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 6d51c565869..0006a1e543f 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -78,6 +78,7 @@ MANIFEST_MODE=false STAGE_NAME="" JSON_OUTPUT=false NON_INTERACTIVE=false +INCLUDE_DESKTOP=false # Detect non-interactive mode (e.g. curl | bash) # When stdin is not a terminal, read -p will fail with EOF, @@ -103,11 +104,11 @@ while [[ $# -gt 0 ]]; do SKIP_BROWSER=true shift ;; - --branch) + --branch|-Branch) BRANCH="$2" shift 2 ;; - --commit) + --commit|-Commit) INSTALL_COMMIT="$2" shift 2 ;; @@ -127,6 +128,10 @@ while [[ $# -gt 0 ]]; do NON_INTERACTIVE=true shift ;; + --include-desktop|-IncludeDesktop) + INCLUDE_DESKTOP=true + shift + ;; --dir) INSTALL_DIR="$2" INSTALL_DIR_EXPLICIT=true @@ -159,6 +164,7 @@ while [[ $# -gt 0 ]]; do echo " --stage NAME Run one desktop bootstrap stage" echo " --json Print a JSON result frame for --stage" echo " --non-interactive Skip stages that require user input" + echo " --include-desktop Also build the desktop app (apps/desktop -> Hermes.app)" echo " --dir PATH Installation directory" echo " default (non-root): ~/.hermes/hermes-agent" echo " default (root, Linux): /usr/local/lib/hermes-agent" @@ -228,9 +234,17 @@ json_escape() { } emit_manifest() { - cat <<'JSON' -{"protocol_version":1,"stages":[{"name":"prerequisites","title":"System prerequisites","category":"runtime","needs_user_input":false},{"name":"repository","title":"Download Hermes Agent","category":"runtime","needs_user_input":false},{"name":"venv","title":"Create Python virtual environment","category":"runtime","needs_user_input":false},{"name":"python-deps","title":"Install Python dependencies","category":"runtime","needs_user_input":false},{"name":"node-deps","title":"Install browser-tool dependencies","category":"runtime","needs_user_input":false},{"name":"path","title":"Install hermes command","category":"runtime","needs_user_input":false},{"name":"config","title":"Prepare config and skills","category":"configuration","needs_user_input":false},{"name":"setup","title":"Configure API keys and settings","category":"configuration","needs_user_input":true},{"name":"gateway","title":"Configure gateway service","category":"configuration","needs_user_input":true},{"name":"complete","title":"Finish install","category":"runtime","needs_user_input":false}]} -JSON + # Stage-Desktop is included only with --include-desktop, mirroring + # install.ps1: the signed bootstrap installer (Hermes-Setup) passes it so + # a GUI install ends up with a launchable app; the Electron app's own + # first-launch bootstrap and the CLI one-liner omit it (building the + # desktop from inside the already-running app would clobber it). + local desktop_stage="" + if [ "$INCLUDE_DESKTOP" = true ]; then + desktop_stage='{"name":"desktop","title":"Build desktop app","category":"runtime","needs_user_input":false},' + fi + printf '%s' '{"protocol_version":1,"stages":[{"name":"prerequisites","title":"System prerequisites","category":"runtime","needs_user_input":false},{"name":"repository","title":"Download Hermes Agent","category":"runtime","needs_user_input":false},{"name":"venv","title":"Create Python virtual environment","category":"runtime","needs_user_input":false},{"name":"python-deps","title":"Install Python dependencies","category":"runtime","needs_user_input":false},{"name":"node-deps","title":"Install browser-tool dependencies","category":"runtime","needs_user_input":false},{"name":"path","title":"Install hermes command","category":"runtime","needs_user_input":false},{"name":"config","title":"Prepare config and skills","category":"configuration","needs_user_input":false},{"name":"setup","title":"Configure API keys and settings","category":"configuration","needs_user_input":true},{"name":"gateway","title":"Configure gateway service","category":"configuration","needs_user_input":true},'"$desktop_stage"'{"name":"complete","title":"Finish install","category":"runtime","needs_user_input":false}]}' + printf '\n' } stage_needs_user_input() { @@ -2162,6 +2176,64 @@ postinstall_mode() { fi } +# Build apps/desktop into a launchable Hermes.app. Mirrors install.ps1's +# Install-Desktop: a root-level npm install so the apps/* workspace resolves +# the desktop's own deps (Electron ~150MB), then `npm run pack` +# (electron-builder --dir) which emits release/mac*/Hermes.app. Only invoked +# via the 'desktop' stage / --include-desktop, which the Electron app's own +# first-launch bootstrap never requests (it must not rebuild itself). +install_desktop() { + local desktop_dir="$INSTALL_DIR/apps/desktop" + + if ! command -v npm >/dev/null 2>&1; then + log_warn "Skipping desktop build (Node.js / npm not on PATH)" + return 0 + fi + if [ ! -f "$desktop_dir/package.json" ]; then + log_warn "Skipping desktop build (apps/desktop not present in checkout)" + return 0 + fi + + # 1. Root workspace install so apps/desktop's deps (Electron, Vite, + # node-pty prebuilds) resolve. The browser-tools install runs in the + # repo-root package workspace, which does not pull apps/* deps. + log_info "Installing desktop workspace dependencies (includes Electron ~150MB, 1-3min)..." + ( cd "$INSTALL_DIR" && npm install ) || { + log_error "Desktop workspace npm install failed" + return 1 + } + log_success "Desktop workspace dependencies installed" + + # 2. Build. `npm run pack` = tsc + vite build + electron-builder --dir, + # producing an unpacked release/mac*/Hermes.app. We disable signing + # auto-discovery so electron-builder falls back to an ad-hoc signature + # instead of grabbing an unrelated Developer ID from the keychain; a + # real signed/notarized .dmg needs Apple credentials and is a separate + # release concern. + log_info "Building desktop app (this takes 1-3 minutes)..." + ( cd "$desktop_dir" && CSC_IDENTITY_AUTO_DISCOVERY=false npm run pack ) || { + log_error "Desktop app build failed" + log_info "Run manually: cd $desktop_dir && npm run pack" + return 1 + } + + local app="" + local cand + for cand in \ + "$desktop_dir/release/mac-arm64/Hermes.app" \ + "$desktop_dir/release/mac/Hermes.app"; do + if [ -d "$cand" ]; then + app="$cand" + break + fi + done + if [ -z "$app" ]; then + log_error "Desktop build completed but no Hermes.app was found under $desktop_dir/release/" + return 1 + fi + log_success "Desktop app built: $app" +} + # Each --stage runs in its own process, so (unlike the monolithic main() where # clone_repo cd's once and later steps inherit it) a stage that operates on the # checkout must cd into it explicitly. Without this, install_deps/setup_path run @@ -2245,6 +2317,12 @@ run_stage_body() { require_install_dir maybe_start_gateway ;; + desktop) + detect_os + resolve_install_layout + require_install_dir + install_desktop + ;; complete) detect_os resolve_install_layout @@ -2316,6 +2394,10 @@ main() { run_setup_wizard maybe_start_gateway + if [ "$INCLUDE_DESKTOP" = true ]; then + install_desktop + fi + print_success echo "git" > "$HERMES_HOME/.install_method"