diff --git a/README.md b/README.md
index 4a1f8ee0..85e2d391 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ For machine-readable discovery, run:
```bash
seedpass capabilities --format json
seedpass agent bootstrap-context
+seedpass tui2 --check
```
## Architecture Overview
diff --git a/docs/README.md b/docs/README.md
index 927930f8..c22c0ec2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -23,6 +23,7 @@ This directory contains SeedPass project documentation.
- `SPEC.md`
- `entry_types.md`
- `entry_graph.md`
+- `tui_v2_plan.md`
- `security.md`
- `nostr_setup.md`
- `nostr_namespace_reset.md`
diff --git a/docs/src/generator/index.js b/docs/src/generator/index.js
index 8fbd4dab..1075b936 100644
--- a/docs/src/generator/index.js
+++ b/docs/src/generator/index.js
@@ -1,4 +1,4 @@
-// Generator entry point for Archivox
+// Generator entry point for SeedPass docs
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
@@ -213,7 +213,7 @@ async function generate({ contentDir = 'content', outputDir = '_site', configPat
}
// Copy the main assets directory (theme, js, etc.)
- // Always resolve assets relative to the Archivox package so it works
+ // Always resolve assets relative to the docs package so it works
// regardless of the current working directory or config location.
const mainAssetsSrc = path.resolve(__dirname, '../../assets');
const mainAssetsDest = path.join(outputDir, 'assets');
diff --git a/docs/tui_v2_plan.md b/docs/tui_v2_plan.md
new file mode 100644
index 00000000..731b1e78
--- /dev/null
+++ b/docs/tui_v2_plan.md
@@ -0,0 +1,96 @@
+# TUI v2 Plan (Textual)
+
+Status: Draft implementation plan on `beta` branch.
+
+Goal: Introduce a modern, modular TUI (`seedpass tui2`) without rewriting core logic or breaking existing CLI/API behavior.
+
+## Constraints
+
+- Keep `seedpass.core` as the source of truth for all business logic.
+- Keep current TUI (`seedpass` default path) operational during migration.
+- Maintain interface modularity:
+ - CLI/TUI
+ - API
+ - future GUI
+
+## Proposed Stack
+
+- Primary: `Textual` for TUI v2 shell and interaction model.
+- Keep dependency optional initially; runtime check from `seedpass tui2 --check`.
+
+## Architecture
+
+1. `seedpass.core`
+- Existing logic/services (`EntryManager`, `EntryService`, sync, vault, policy).
+
+2. `seedpass.app` (future thin orchestration layer)
+- UI-agnostic use-cases and view-model mappers.
+- Shared by Textual TUI and future GUI.
+
+3. Interface adapters
+- `seedpass.cli` (Typer commands)
+- `seedpass.tui_v2` (Textual app)
+- `seedpass.api` (FastAPI)
+
+## Phased Rollout
+
+### Phase 0: Scaffold (this PR)
+- [x] Add plan doc.
+- [x] Add `seedpass tui2` command.
+- [x] Add runtime check utility and placeholder launcher.
+- [ ] Add CI smoke command for `seedpass tui2 --check`.
+
+### Phase 1: Read-only shell
+- [ ] Textual app skeleton with 3-pane layout:
+ - filters panel
+ - entry list panel
+ - details panel
+- [ ] Unlock/profile context hookup.
+- [ ] Entry list/search/detail read paths.
+- [ ] Status line for sync/profile/lock state.
+
+### Phase 2: Core editing workflows
+- [ ] Add/modify/archive flows for common entry kinds.
+- [ ] Document editor panel with dirty-state and save/cancel.
+- [ ] Keybindings + command palette.
+
+### Phase 3: Graph workflows
+- [ ] Entry links panel (list, add, remove).
+- [ ] Relation-aware search controls.
+- [ ] Neighbor navigation and quick traversal.
+
+### Phase 4: Cutover readiness
+- [ ] Feature parity checklist against legacy TUI.
+- [ ] UX/perf pass for large vaults.
+- [ ] Error messaging and recovery flows.
+- [ ] Decide default switch timing (`seedpass` -> `tui2`).
+
+## Test Strategy
+
+- Unit tests:
+ - runtime checks and command wiring.
+ - view-model transformations (when `seedpass.app` exists).
+- Integration tests:
+ - command-level smoke (`seedpass tui2 --check`).
+ - later: Textual interaction tests for list/search/edit/link.
+- Regression:
+ - full suite must remain green during phased migration.
+
+## Risks and Mitigations
+
+- Risk: UI code bypasses core invariants.
+ - Mitigation: enforce service-layer-only mutation from TUI.
+- Risk: dependency churn (`textual` changes).
+ - Mitigation: pin versions when enabling default path.
+- Risk: branch drift while both TUIs exist.
+ - Mitigation: parity checklist and shared orchestration layer.
+
+## Deliverables
+
+- New command:
+ - `seedpass tui2`
+ - `seedpass tui2 --check`
+- New package:
+ - `seedpass/tui_v2/`
+- Docs updates:
+ - this file listed in docs index and discoverability nav.
diff --git a/landing/docs.html b/landing/docs.html
index be20c7ec..d7b1e6e4 100644
--- a/landing/docs.html
+++ b/landing/docs.html
@@ -56,6 +56,7 @@
> Specification
> Entry Types
> Entry Graph
+ > TUI v2 Plan
> Security
> Security Readiness
> Agent Security Plan
diff --git a/memory-update.md b/memory-update.md
index cbad1a23..9871e363 100644
--- a/memory-update.md
+++ b/memory-update.md
@@ -162,3 +162,17 @@
- Verification:
- `.venv/bin/pytest -q src/tests/test_typer_cli.py -k "capabilities_text or capabilities_json" src/tests/test_cli_agent_mode.py -k "bootstrap_context"`
- Result: `2 passed`.
+
+## TUI v2 scaffold kickoff on beta
+- Added `docs/tui_v2_plan.md` with phased migration plan, risk controls, and testing strategy for Textual-based TUI v2.
+- Added experimental CLI command `seedpass tui2` and diagnostics mode `seedpass tui2 --check`.
+- Added new runtime-check/launcher scaffold package at `src/seedpass/tui_v2/`.
+- Updated discoverability/docs surfaces:
+ - `docs/README.md` includes `tui_v2_plan.md`
+ - `landing/docs.html` includes `TUI v2 Plan`
+ - `README.md` discovery snippet includes `seedpass tui2 --check`
+ - `capabilities` root commands/help hints include `tui2`
+- Validation:
+ - `py_compile` for updated modules
+ - `pytest` capabilities tests (`2 passed`)
+ - `.venv` smoke run confirms `seedpass tui2 --check` emits runtime diagnostics.
diff --git a/src/seedpass/cli/__init__.py b/src/seedpass/cli/__init__.py
index f684b69c..35ae8558 100644
--- a/src/seedpass/cli/__init__.py
+++ b/src/seedpass/cli/__init__.py
@@ -2,6 +2,7 @@ from __future__ import annotations
import importlib
import importlib.util
+import json
import subprocess
import sys
from typing import Optional
@@ -11,6 +12,7 @@ import typer
from .common import _get_services
from seedpass.core.errors import SeedPassError
from constants import GUI_BACKEND_CONFIG
+from seedpass.tui_v2.app import check_tui2_runtime, launch_tui2
app = typer.Typer(
help=(
@@ -105,6 +107,45 @@ def root_lock(ctx: typer.Context) -> None:
typer.echo("locked")
+@app.command("tui2")
+def tui2(
+ ctx: typer.Context,
+ check: bool = typer.Option(
+ False,
+ "--check",
+ help="Check whether TUI v2 runtime dependencies are available",
+ ),
+ fallback_legacy: bool = typer.Option(
+ True,
+ "--fallback-legacy/--no-fallback-legacy",
+ help="Fall back to legacy TUI when TUI v2 runtime is unavailable",
+ ),
+) -> None:
+ """Launch experimental TUI v2 scaffold."""
+ if check:
+ typer.echo(json.dumps(check_tui2_runtime(), indent=2, sort_keys=True))
+ return
+
+ fingerprint = (ctx.obj or {}).get("fingerprint")
+ launched = launch_tui2(fingerprint=fingerprint)
+ if launched:
+ return
+
+ if fallback_legacy:
+ 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))
+
+ typer.echo(
+ "TUI v2 runtime unavailable. Install `textual` and retry.",
+ err=True,
+ )
+ raise typer.Exit(1)
+
+
@app.command()
def gui(
install: bool = typer.Option(
diff --git a/src/seedpass/cli/capabilities.py b/src/seedpass/cli/capabilities.py
index 29638bbd..9f46c337 100644
--- a/src/seedpass/cli/capabilities.py
+++ b/src/seedpass/cli/capabilities.py
@@ -17,6 +17,7 @@ def build_capabilities() -> dict[str, Any]:
"root_commands": [
"lock",
"gui",
+ "tui2",
"capabilities",
"entry",
"vault",
@@ -239,6 +240,7 @@ def build_capabilities() -> dict[str, Any]:
"Run `seedpass --help` for top-level commands.",
"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.",
"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`.",
"After unlock/login, run `seedpass capabilities` before autonomous runs.",
diff --git a/src/seedpass/tui_v2/__init__.py b/src/seedpass/tui_v2/__init__.py
new file mode 100644
index 00000000..95883444
--- /dev/null
+++ b/src/seedpass/tui_v2/__init__.py
@@ -0,0 +1,2 @@
+"""TUI v2 package scaffold (Textual-backed)."""
+
diff --git a/src/seedpass/tui_v2/app.py b/src/seedpass/tui_v2/app.py
new file mode 100644
index 00000000..4349586f
--- /dev/null
+++ b/src/seedpass/tui_v2/app.py
@@ -0,0 +1,62 @@
+from __future__ import annotations
+
+import importlib.util
+from typing import Any
+
+
+def check_tui2_runtime() -> dict[str, Any]:
+ """Return runtime capability diagnostics for TUI v2."""
+ textual_available = importlib.util.find_spec("textual") is not None
+ return {
+ "status": "ok" if textual_available else "unavailable",
+ "backend": "textual",
+ "textual_available": textual_available,
+ "message": (
+ "Textual runtime available."
+ if textual_available
+ else "Textual is not installed. Install `textual` to run tui2."
+ ),
+ }
+
+
+def launch_tui2(*, fingerprint: str | None = None) -> bool:
+ """Launch TUI v2 when runtime dependencies are available.
+
+ Returns ``True`` when launch succeeds, ``False`` when runtime is unavailable.
+ """
+ runtime = check_tui2_runtime()
+ if not runtime["textual_available"]:
+ return False
+
+ # Placeholder shell for incremental migration.
+ from textual.app import App, ComposeResult
+ from textual.containers import Container
+ from textual.widgets import Footer, Header, Static
+
+ class SeedPassTuiV2(App[None]):
+ BINDINGS = [("q", "quit", "Quit")]
+
+ def compose(self) -> ComposeResult:
+ yield Header(show_clock=True)
+ with Container():
+ fp_line = f"Fingerprint: {fingerprint}" if fingerprint else "No fingerprint selected"
+ yield Static(
+ "\n".join(
+ [
+ "SeedPass TUI v2 scaffold",
+ fp_line,
+ "",
+ "Next steps:",
+ "- Implement list/search/details panes",
+ "- Add document editor panel",
+ "- Add graph links panel",
+ "",
+ "Press 'q' to quit.",
+ ]
+ )
+ )
+ yield Footer()
+
+ SeedPassTuiV2().run()
+ return True
+