diff --git a/hermes_cli/main.py b/hermes_cli/main.py index ef6a176a2..918733325 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -1650,6 +1650,22 @@ def _find_bundled_tui(hermes_cli_dir: Path | None = None) -> Path | None: return bundled if bundled.is_file() else None +def _exit_missing_tui_workspace(tui_dir: Path) -> "NoReturn": + """Abort TUI launch with a recovery hint when the workspace checkout is missing.""" + print( + "Error: the TUI workspace is missing from this Hermes checkout.\n" + f"Expected directory: {tui_dir}\n" + "This usually means `hermes update` left tracked ui-tui files deleted.\n" + "Recovery:\n" + " 1. From the Hermes checkout, run `git restore -- ui-tui`\n" + " 2. Run `npm install --silent --no-fund --no-audit --progress=false`\n" + " 3. Retry `hermes --tui`\n" + "If the checkout is still inconsistent, run `hermes update --force`.", + file=sys.stderr, + ) + sys.exit(1) + + def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]: """TUI: --dev → tsx src; else node dist (HERMES_TUI_DIR prebuilt or esbuild).""" _ensure_tui_node() @@ -1683,6 +1699,9 @@ def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]: ) sys.exit(1) + if not ext_dir and not tui_dir.is_dir(): + _exit_missing_tui_workspace(tui_dir) + # 1. Prebuilt bundle (nix / packaged release): just run it. if not tui_dev: if ext_dir: diff --git a/tests/hermes_cli/test_tui_npm_install.py b/tests/hermes_cli/test_tui_npm_install.py index b2f58fefa..08a2200fa 100644 --- a/tests/hermes_cli/test_tui_npm_install.py +++ b/tests/hermes_cli/test_tui_npm_install.py @@ -327,6 +327,27 @@ def test_make_tui_argv_decodes_dev_prebuild_with_utf8_replace( _assert_utf8_replace_capture(calls[0][1]) +def test_make_tui_argv_exits_with_recovery_hint_when_workspace_missing( + tmp_path: Path, main_mod, monkeypatch, capsys +) -> None: + monkeypatch.delenv("HERMES_TUI_DIR", raising=False) + monkeypatch.setattr(main_mod, "_ensure_tui_node", lambda: None) + + def fail_which(_name: str) -> str: + raise AssertionError("node/npm lookup must not run when ui-tui is missing") + + monkeypatch.setattr(main_mod.shutil, "which", fail_which) + + with pytest.raises(SystemExit) as exc: + main_mod._make_tui_argv(tmp_path / "ui-tui", tui_dev=False) + + assert exc.value.code == 1 + err = capsys.readouterr().err + assert "TUI workspace is missing" in err + assert "git restore -- ui-tui" in err + assert "hermes update --force" in err + + # ── _workspace_root helper ──────────────────────────────────────────