From e3c117110a337d78cc30d55d790be22c3d64b04e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 1 Mar 2026 16:46:08 -0500 Subject: [PATCH] Switch default interactive CLI routing to TUI v2 --- docs/README.md | 1 + docs/tui_v2_cutover_decision.md | 7 ++---- memory-update.md | 2 ++ src/seedpass/cli/__init__.py | 23 ++++++++++++++++++ src/seedpass/cli/capabilities.py | 1 + src/tests/test_typer_cli.py | 40 +++++++++++++++++++++++++++++--- 6 files changed, 66 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index f86c4945..98a6dc44 100644 --- a/docs/README.md +++ b/docs/README.md @@ -43,6 +43,7 @@ This directory contains SeedPass project documentation. ## Discoverability Commands - `seedpass --help` +- `seedpass --legacy-tui` - `seedpass --help` - `seedpass tui2 --help` - `seedpass tui2 --check` diff --git a/docs/tui_v2_cutover_decision.md b/docs/tui_v2_cutover_decision.md index 178d0a2b..93e070df 100644 --- a/docs/tui_v2_cutover_decision.md +++ b/docs/tui_v2_cutover_decision.md @@ -6,12 +6,9 @@ Decision owner: SeedPass maintainers ## Decision -Do not switch the default interactive entrypoint (`seedpass`) to TUI v2 yet. +Addendum (March 1, 2026, `beta`): default interactive routing now launches TUI v2 first, with `--legacy-tui` retained as a one-release fallback path. -Recommended rollout: -- Keep legacy TUI as default for the next release. -- Keep `seedpass tui2` as the opt-in path. -- Schedule default switch only after cutover gates below are met. +Original recommendation in this memo was conservative (defer default switch until all gates were complete). The `beta` branch now reflects the cutover implementation requested during active hardening. ## Rationale diff --git a/memory-update.md b/memory-update.md index f436667d..c1ee8ab5 100644 --- a/memory-update.md +++ b/memory-update.md @@ -198,3 +198,5 @@ - Added docs `docs/tui_v2_large_vault_validation.md` with exact commands and budgets; parity checklist now marks large-vault CI-like validation complete. - 2026-03-01: Added deterministic TUI v2 parity scenarios across entry kinds in `src/tests/test_tui_v2_parity_scenarios.py` (kind filters, archive/restore roundtrip, non-document edit guard). - Completed help/docs alignment gate: updated `seedpass tui2` help text, capabilities hints for keybindings/palette (`Ctrl+P`, `p/n`, `x`), and docs discoverability commands (`seedpass tui2 --help/--check`). +- 2026-03-01: Switched default root CLI interactive routing to TUI v2 in `seedpass.cli.main` (when no subcommand), with automatic legacy fallback when Textual runtime is unavailable. +- Added explicit one-release override flag `--legacy-tui` and updated test coverage (`test_typer_cli.py`) for default TUI v2 launch, forced legacy path, and root fallback behavior. diff --git a/src/seedpass/cli/__init__.py b/src/seedpass/cli/__init__.py index e6cea13b..82d0ef17 100644 --- a/src/seedpass/cli/__init__.py +++ b/src/seedpass/cli/__init__.py @@ -44,6 +44,12 @@ deterministic_totp_option = typer.Option( help="Derive TOTP secrets deterministically", ) +legacy_tui_option = typer.Option( + False, + "--legacy-tui", + help="Use legacy interactive TUI (temporary fallback during TUI v2 cutover)", +) + # Sub command groups from . import entry, vault, nostr, config, fingerprint, util, api, agent from .capabilities import register_capabilities_command @@ -82,6 +88,7 @@ def main( fingerprint: Optional[str] = fingerprint_option, no_clipboard: bool = no_clipboard_option, deterministic_totp: bool = deterministic_totp_option, + legacy_tui: bool = legacy_tui_option, ) -> None: """SeedPass CLI entry point. @@ -93,8 +100,24 @@ def main( "fingerprint": fingerprint, "no_clipboard": no_clipboard, "deterministic_totp": deterministic_totp, + "legacy_tui": legacy_tui, } if ctx.invoked_subcommand is None: + if legacy_tui: + tui = importlib.import_module("main") + raise typer.Exit(tui.main(fingerprint=fingerprint)) + + launched = launch_tui2( + fingerprint=fingerprint, + entry_service_factory=lambda: _get_entry_service(ctx), + ) + if launched: + return + + typer.echo( + "TUI v2 runtime unavailable; falling back to legacy TUI. " + "Run `seedpass tui2 --check` for diagnostics." + ) tui = importlib.import_module("main") raise typer.Exit(tui.main(fingerprint=fingerprint)) diff --git a/src/seedpass/cli/capabilities.py b/src/seedpass/cli/capabilities.py index 9bb027e3..f846ee09 100644 --- a/src/seedpass/cli/capabilities.py +++ b/src/seedpass/cli/capabilities.py @@ -241,6 +241,7 @@ def build_capabilities() -> dict[str, Any]: "Run `seedpass --help` for detailed options.", "Use `seedpass capabilities --format json` for machine-readable discovery.", "Run `seedpass tui2 --check` before adopting Textual-based TUI v2.", + "Use `seedpass --legacy-tui` to force the legacy interface during cutover.", "In TUI v2, use `Ctrl+P` for the command palette, `p/n` for paging, and `x` to retry the last failed action.", "For graph workflows, start with `seedpass entry link-add --help` and `seedpass entry links --help`.", "For document workflows, run `seedpass entry import-document --help` and `seedpass agent document-import --help`.", diff --git a/src/tests/test_typer_cli.py b/src/tests/test_typer_cli.py index 31eff852..0a0c979e 100644 --- a/src/tests/test_typer_cli.py +++ b/src/tests/test_typer_cli.py @@ -972,8 +972,23 @@ def test_update_checksum_command(monkeypatch): assert called.get("called") is True -def test_tui_forward_fingerprint(monkeypatch): - """Ensure --fingerprint is forwarded when launching the TUI.""" +def test_root_tui2_forward_fingerprint(monkeypatch): + """Ensure --fingerprint is forwarded when launching default TUI v2.""" + called = {} + + def fake_launch(**kwargs): + called["fp"] = kwargs.get("fingerprint") + called["factory"] = kwargs.get("entry_service_factory") + return True + + monkeypatch.setattr(cli, "launch_tui2", fake_launch) + result = runner.invoke(app, ["--fingerprint", "abc"]) + assert result.exit_code == 0 + assert called.get("fp") == "abc" + assert callable(called.get("factory")) + + +def test_root_legacy_tui_flag_forwards_fingerprint(monkeypatch): called = {} def fake_main(*, fingerprint=None): @@ -985,7 +1000,7 @@ def test_tui_forward_fingerprint(monkeypatch): cli, "importlib", SimpleNamespace(import_module=lambda n: fake_mod) ) - result = runner.invoke(app, ["--fingerprint", "abc"]) + result = runner.invoke(app, ["--legacy-tui", "--fingerprint", "abc"]) assert result.exit_code == 0 assert called.get("fp") == "abc" @@ -1029,6 +1044,25 @@ def test_tui2_fallback_legacy(monkeypatch): assert "falling back to legacy TUI" in result.stdout +def test_root_fallback_legacy_when_tui2_unavailable(monkeypatch): + called = {} + monkeypatch.setattr(cli, "launch_tui2", lambda **_: False) + + def fake_main(*, fingerprint=None): + called["fp"] = fingerprint + return 0 + + monkeypatch.setattr( + cli, + "importlib", + SimpleNamespace(import_module=lambda _: SimpleNamespace(main=fake_main)), + ) + result = runner.invoke(app, ["--fingerprint", "abc"]) + assert result.exit_code == 0 + assert called.get("fp") == "abc" + assert "falling back to legacy TUI" in result.stdout + + def test_gui_command(monkeypatch): called = {}