diff --git a/src/seedpass/cli/__init__.py b/src/seedpass/cli/__init__.py index efbd3003..4a00a2ae 100644 --- a/src/seedpass/cli/__init__.py +++ b/src/seedpass/cli/__init__.py @@ -154,11 +154,21 @@ def _launch_legacy_tui(*, fingerprint: Optional[str]) -> typer.Exit: return typer.Exit(legacy_main(fingerprint=fingerprint)) +def _stdin_is_tty() -> bool: + return sys.stdin.isatty() + + def _launch_tui3_or_exit( *, fingerprint: Optional[str], services: Optional[dict[str, object]] = None, ) -> None: + if not _stdin_is_tty(): + typer.echo( + "seedpass tui3: requires an interactive terminal (stdin is not a TTY).", + err=True, + ) + raise typer.Exit(1) kwargs: dict[str, object] = {"fingerprint": fingerprint} if services is not None: kwargs.update( diff --git a/src/tests/test_typer_cli.py b/src/tests/test_typer_cli.py index 2f082613..535d9125 100644 --- a/src/tests/test_typer_cli.py +++ b/src/tests/test_typer_cli.py @@ -1021,6 +1021,7 @@ def test_root_tui3_forward_fingerprint(monkeypatch): called["factory"] = kwargs.get("entry_service_factory") return True + monkeypatch.setattr(cli, "_stdin_is_tty", lambda: True) monkeypatch.setattr(cli, "launch_tui3", fake_launch) result = runner.invoke(app, ["--fingerprint", "abc"]) assert result.exit_code == 0 @@ -1170,6 +1171,7 @@ def test_tui2_fallback_legacy_strips_subcommand_argv(monkeypatch): def test_root_tui3_unavailable_returns_error(monkeypatch): + monkeypatch.setattr(cli, "_stdin_is_tty", lambda: True) monkeypatch.setattr(cli, "launch_tui3", lambda **_: False) result = runner.invoke(app, ["--fingerprint", "abc"]) assert result.exit_code == 1 @@ -1179,6 +1181,7 @@ def test_root_tui3_unavailable_returns_error(monkeypatch): def test_root_tui3_does_not_preflight_entry_service(monkeypatch): + monkeypatch.setattr(cli, "_stdin_is_tty", lambda: True) monkeypatch.setattr( cli, "_get_entry_service", @@ -1189,6 +1192,14 @@ def test_root_tui3_does_not_preflight_entry_service(monkeypatch): assert result.exit_code == 0 +def test_tui3_non_interactive_stdin_exits_with_error(monkeypatch): + """tui3 must exit cleanly with an error message when stdin is not a TTY.""" + monkeypatch.setattr(cli, "_stdin_is_tty", lambda: False) + result = runner.invoke(app, ["tui3"]) + assert result.exit_code == 1 + assert "not a TTY" in result.stderr + + def test_tui2_preflight_failure_without_fallback(monkeypatch): monkeypatch.setattr( cli,