tui2: complete parity phases with settings/relay tools and CI smoke

This commit is contained in:
thePR0M3TH3AN committed 2026-03-01 20:36:04 -05:00
1 parent 176d12d9ae
commit 367cad6a04
13 files changed
+3435 -10

No files matched your search

+2
View File
@@ -30,6 +30,8 @@ jobs:
run: |
poetry run pip-audit || echo "::warning::pip-audit found vulnerabilities"
shell: bash
- name: TUI v2 check smoke
run: poetry run seedpass tui2 --check
- name: Run deterministic regression tests
run: poetry run pytest -q src/tests/test_deterministic_artifact_regression.py
- name: Run tests with coverage
+1
View File
@@ -25,6 +25,7 @@ This directory contains SeedPass project documentation.
- `entry_graph.md`
- `tui_v2_plan.md`
- `tui_v2_parity_checklist.md`
- `tui_v2_parity_backlog.md`
- `tui_v2_cutover_decision.md`
- `tui_v2_large_vault_validation.md`
- `security.md`
+87
View File
@@ -0,0 +1,87 @@
# TUI v2 Parity Backlog
Status: Active tracking on `beta` (updated 2026-03-01).
This document captures parity learnings and the remaining implementation queue
for Textual TUI v2 versus legacy interactive TUI.
Linked plans:
- `docs/tui_v2_plan.md`
- `docs/tui_v2_parity_checklist.md`
- `docs/tui_v2_cutover_decision.md`
## What We Learned
1. Core shell workflows are stable in TUI v2:
- list/search/filter/detail
- archive/restore
- document edit save/cancel
- graph links + relation filtering + neighbor traversal
- retry/error-recovery flows
2. Security-sensitive parity has improved:
- reveal/QR for `password`, `totp`, `seed`, `managed_account`, `ssh`, `pgp`, `nostr`
- high-risk confirm gates (`reveal confirm`, `qr private confirm`)
- secret-mode clipboard-aware reveal behavior
3. Scale validation now exists for KB-like workloads:
- core sort/tag/search + high-degree graph link stress
- TUI large-index navigation stress
4. Remaining parity is mostly feature-surface breadth, not stability:
- add-entry creation flows in v2
- legacy retrieve/action micro-workflows
- dedicated 2FA board
- settings/profile/nostr operational menus
## Active Build Queue
### Phase A: Add Entry in TUI v2
- [x] Add password creation flow
- [x] Add TOTP creation flow
- [x] Add key/value creation flow
- [x] Add document creation flow
- [x] Add SSH/PGP/Nostr/Seed/Managed Account creation flows
- [x] Add validation and post-create focus/selection behavior
### Phase B: Retrieve/Action Parity
- [x] Add note from selected entry
- [x] Add custom field / hidden field actions
- [x] Edit tags action parity
- [x] Field-level edit parity for non-document kinds
- [x] Document export action
- [x] Nostr QR submenu parity (public/private explicit flows)
### Phase C: 2FA Board Parity
- [x] Dedicated 2FA list board view
- [x] rolling code refresh + remaining timer UI
- [x] secret-mode clipboard behavior in board view
- [x] deterministic/imported split parity and display semantics
### Phase D: Settings/Profile/Nostr Parity
- [x] profile switch/add/remove/list/rename
- [x] secret mode / quick unlock / offline toggles
- [x] inactivity timeout + KDF settings
- [x] relay view/add/remove/reset + sync controls
- [x] checksum verify/generate, import/export db, export totp, parent seed backup/reveal
### Phase E: Release/CI Closure
- [x] Add CI smoke step for `seedpass tui2 --check`
- [x] Extend docs/help parity after each feature landing
- [x] Keep `legacy` fallback path until parity checklist has no high-severity gaps
Release note:
- CI now runs a dedicated `tui2 --check` smoke gate via `scripts/tui2_check_smoke.sh`
(invoked by `scripts/run_ci_tests.sh`) and in Poetry workflow (`tests.yml`).
- Legacy fallback command/flag paths remain available (`seedpass legacy`,
`seedpass --legacy-tui`, and `tui2` fallback toggle) while cutover hardening
continues.
## Execution Notes
- Implement in small vertical slices and check boxes as each slice lands with
tests.
- Each parity slice must include:
1) command/keybind discoverability,
2) service-layer coverage,
3) Textual interaction tests.
+15
View File
@@ -54,3 +54,18 @@ This checklist tracks readiness to switch default interactive mode from legacy T
- standard: `10,000` rows, stress: `50,000` rows (`--stress`)
- [x] Confirm help text and docs match shipped keybindings and palette commands.
- Updated `seedpass tui2 --help`, capabilities hints, and docs discoverability commands.
## Ongoing Tracking
- Active parity continuation and checkoff queue:
- `docs/tui_v2_parity_backlog.md`
## Phase E Closure Notes
- CI smoke now explicitly validates `seedpass tui2 --check` output shape:
- `scripts/tui2_check_smoke.sh` (called from `scripts/run_ci_tests.sh`)
- `.github/workflows/tests.yml` (`poetry run seedpass tui2 --check`)
- Legacy fallback paths remain intentionally available during hardening:
- `seedpass --legacy-tui`
- `seedpass legacy`
- `seedpass tui2 --fallback-legacy`
+2 -1
View File
@@ -38,7 +38,7 @@ Goal: Introduce a modern, modular TUI (`seedpass tui2`) without rewriting core l
- [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`.
- [x] Add CI smoke command for `seedpass tui2 --check`.
### Phase 1: Read-only shell
- [x] Textual app skeleton with 3-pane layout:
@@ -94,3 +94,4 @@ Goal: Introduce a modern, modular TUI (`seedpass tui2`) without rewriting core l
- `seedpass/tui_v2/`
- Docs updates:
- this file listed in docs index and discoverability nav.
- parity continuation tracker: `docs/tui_v2_parity_backlog.md`
+228
View File
@@ -300,3 +300,231 @@
- Added docs `docs/kb_scale_validation.md` with exact commands for standard and `--stress` runs.
- Updated launch documentation to clearly show all interactive launch modes:
- default (`seedpass`), explicit v2 (`seedpass tui2`), and legacy (`seedpass legacy` / `seedpass --legacy-tui`).
## 2026-03-01 TUI v2 parity backlog prioritization
- TORCH latest daily/weekly memory snapshots are mostly scheduler metadata; TUI parity priorities should continue to be driven by `docs/tui_v2_plan.md`, `docs/tui_v2_parity_checklist.md`, and direct legacy-vs-v2 code diff.
- Highest-impact remaining parity gaps identified:
1) Add-entry workflows for all kinds in TUI v2
2) Retrieve/action parity (notes/custom fields/tags/edit field-level variants/document export/QR submenus)
3) 2FA codes live board parity with timer + secret-mode clipboard behavior
4) Settings/profile/nostr operational parity in TUI v2
5) CI smoke gate for `seedpass tui2 --check` still marked open in plan
## 2026-03-01 TUI v2 parity tracking + Phase A add-entry slice
- Added a dedicated continuation tracker `docs/tui_v2_parity_backlog.md` and linked it from:
- `docs/tui_v2_plan.md`
- `docs/tui_v2_parity_checklist.md`
- `docs/README.md`
- Landed TUI v2 command-palette add-entry flows in `src/seedpass/tui_v2/app.py`:
- `add-password <label> <length> [username] [url]`
- `add-totp <label> [period] [digits] [secret]`
- `add-key-value <label> <key> <value>`
- `add-document <label> <file_type> <content>`
- Added usage validation and post-create behavior:
- strict argument/typing checks with status feedback
- refresh + selection focus to created entry (or nearest match for TOTP)
- retry wiring through existing `x` recovery path on service failures
- Extended Textual tests:
- `src/tests/test_tui_v2_textual_interactions.py` now includes add-command success and validation-error scenarios.
- `src/tests/test_tui_v2_action_matrix.py` test service now implements add methods and exercises add-command guards/success paths.
- Validation run:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
- Result: `24 passed`.
- Updated backlog checkoffs (Phase A): password/TOTP/key-value/document creation + validation/post-create focus are now marked complete; remaining work is SSH/PGP/Nostr/Seed/Managed Account creation flows and downstream parity phases.
## 2026-03-01 TUI v2 parity Phase A completion (remaining add kinds)
- Extended TUI v2 command palette add workflows in `src/seedpass/tui_v2/app.py` for remaining entry kinds:
- `add-ssh <label> [index]`
- `add-pgp <label> [index] [key_type] [user_id]`
- `add-nostr <label> [index]`
- `add-seed <label> [words] [index]`
- `add-managed-account <label> [index]`
- Added argument validation and status hints for all new commands, including integer checks for index/words arguments and retry integration on service failures.
- Updated discoverability text in palette/help/filter hints to include the new add command set.
- Expanded test doubles and coverage:
- `src/tests/test_tui_v2_textual_interactions.py`: success + validation coverage for all add commands (existing and new).
- `src/tests/test_tui_v2_action_matrix.py`: guard paths and success paths for new add commands.
- Validation run repeated after formatting:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
- Result: `24 passed`.
- Backlog update:
- `docs/tui_v2_parity_backlog.md` Phase A is now fully checked off.
- Next priority to start: Phase B retrieve/action parity (notes/custom fields/tags/field-level edits/document export/nostr QR submenu behaviors).
## 2026-03-01 TUI v2 parity Phase B slice: notes/tags/custom fields/document export
- Added retrieve/action parity commands to TUI v2 palette in `src/seedpass/tui_v2/app.py`:
- `notes-set <text>` / `notes-clear`
- `tag-add <tag>` / `tag-rm <tag>` / `tags-set <comma-list>` / `tags-clear`
- `field-add <label> <value> [hidden-token]` / `field-rm <label>`
- `doc-export [output_path]` (selected document only)
- Introduced helper paths for selected-entry mutation:
- `_selected_entry_payload()` for reliable selected entry fetch/guarding
- `_apply_selected_modify(...)` to centralize modify->reload->reselect->retry behavior
- Added/extended tests:
- `src/tests/test_tui_v2_textual_interactions.py` adds success + validation coverage for notes/tags/custom fields/doc export.
- `src/tests/test_tui_v2_action_matrix.py` now exercises new command guards and paths.
- Fake services in both test files now implement `export_document_file(...)`.
- Validation:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
- Result: `26 passed`.
- UI-status formatting note for Textual markup:
- Bracketed usage strings like `[hidden]`/`[output_path]` get treated as markup and partially disappear in status rendering.
- Switched those usage strings to plain text `(optional: ...)` format.
- Backlog updates in `docs/tui_v2_parity_backlog.md`:
- Checked off Phase B items for notes, tags, custom fields, document export, and Nostr public/private QR submenu parity.
- Remaining Phase B gap: field-level edit parity for non-document kinds.
## 2026-03-01 TUI v2 parity Phase B slice: field-level edits for non-document kinds
- Added palette commands in `src/seedpass/tui_v2/app.py`:
- `set-field <name> <value>` (alias `field-set`)
- `clear-field <name>` (alias `field-clear`)
- Implemented kind-aware field edit allowlists and validation:
- password: `label/notes/username/url/length`
- totp: `label/notes/period/digits`
- key_value: `label/notes/key/value`
- document: `label/notes/file_type/content`
- other kinds: `label/notes`
- Integer coercion/validation added for `length/period/digits` with explicit status errors.
- Added shared selected-entry modify helpers to keep behavior deterministic and consistent:
- `_selected_entry_payload()`
- `_apply_selected_modify(...)`
- Expanded tests:
- `src/tests/test_tui_v2_textual_interactions.py` adds success + validation tests for set/clear field workflows across password/totp/key_value.
- `src/tests/test_tui_v2_action_matrix.py` includes command guard and action-path coverage for new commands.
- Validation:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
- Result: `28 passed`.
- Backlog update:
- `docs/tui_v2_parity_backlog.md` now marks Phase B field-level edit parity complete.
- Phase B is effectively complete in tracker and next focus should move to Phase C (dedicated 2FA board parity).
## 2026-03-01 TUI v2 parity Phase C: dedicated 2FA board
- Added dedicated 2FA board mode in `src/seedpass/tui_v2/app.py`:
- keybind: `6` (`action_toggle_totp_board`)
- palette commands: `2fa-board`, `2fa-hide`, `2fa-refresh`, `2fa-copy <entry_id>`
- Added right-pane board widget `#totp-board` and visibility switching helpers.
- Added live refresh/timer behavior:
- app interval tick (`set_interval(1.0, _tick_totp_board)`) updates board while visible
- per-row remaining seconds (`rem`) based on each TOTP period
- Added board display semantics for deterministic/imported split:
- source column (`det` / `imp`) from `deterministic` flag fallback to secret presence
- Added secret-mode behavior for board:
- board masks visible codes as `******` when secret mode is enabled
- `2fa-copy` copies actual code to clipboard and reports clear-delay status
- Updated help/filter/palette discoverability text with new 2FA board commands.
- Test coverage added/expanded:
- `src/tests/test_tui_v2_textual_interactions.py`:
- `test_tui2_textual_2fa_board_view_timer_and_copy`
- `test_tui2_textual_2fa_board_secret_mode_and_validation`
- `src/tests/test_tui_v2_action_matrix.py` command matrix includes 2FA command guards/actions
- Validation:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
- Result: `30 passed`.
- Backlog update: all Phase C checkbox items now marked complete in `docs/tui_v2_parity_backlog.md`.
## 2026-03-01 TUI v2 parity Phase D slice: profile + settings palette plumbing
- Extended `launch_tui2(...)` to accept optional `profile_service_factory` and `config_service_factory`, in addition to entry service factory.
- Updated CLI preflight wiring (`src/seedpass/cli/__init__.py`) so TUI v2 receives entry/profile/config services from a single initialized manager when available, while preserving existing monkeypatch compatibility in CLI tests.
- Added new TUI v2 palette commands in `src/seedpass/tui_v2/app.py`:
- `profiles-list`
- `profile-switch <fingerprint> [password]`
- `setting-secret <on|off> [delay]`
- `setting-offline <on|off>`
- `setting-quick-unlock <on|off>`
- Added toggle parsing helper for robust boolean token handling (`on/off/true/false/...`).
- Added Textual interaction test fakes for profile/config services and new command coverage:
- `test_tui2_textual_profiles_and_settings_palette_commands`
- `test_tui2_textual_profiles_and_settings_palette_validation`
- Expanded action-matrix command coverage to include new profile/settings command guard paths.
- Validation run:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py`
- Result: `86 passed`.
- Backlog update (`docs/tui_v2_parity_backlog.md`):
- marked `secret mode / quick unlock / offline toggles` complete.
- profile switch/add/remove/list/rename remains open because only list/switch command-path parity is currently implemented; add/remove/rename UX flows still pending.
## 2026-03-01 TUI v2 parity Phase D slice: profile lifecycle + relay/sync command paths
- Extended TUI launch wiring to accept optional `nostr_service_factory` and `sync_service_factory` in `launch_tui2(...)`.
- Updated CLI preflight service bundle (`src/seedpass/cli/__init__.py`) to pass entry/profile/config/nostr/sync services from a shared manager when available, while preserving monkeypatch compatibility in CLI tests.
- Added new palette commands in `src/seedpass/tui_v2/app.py`:
- profile lifecycle: `profile-add`, `profile-remove <fingerprint>` (plus existing `profiles-list`, `profile-switch`)
- relays/sync: `relay-list`, `relay-add <url>`, `relay-rm <index>`, `sync-now`, `sync-bg`
- Added Textual interaction fakes + coverage for new service surfaces:
- fake profile service now supports add/remove
- new fake nostr service for relay list/add/remove
- new fake sync service for sync-now/bg paths
- Expanded command matrix coverage to include new profile/relay/sync guard-path commands.
- Validation:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py`
- Result: `86 passed`.
- Backlog status update:
- Phase D rows for profile and relay/sync remain open because profile rename and relay reset parity are still missing.
- Added explicit progress note in `docs/tui_v2_parity_backlog.md` documenting currently landed command coverage.
## 2026-03-01 TUI v2 parity Phase D completion slice: profile rename + relay reset
- Service-layer additions in `src/seedpass/core/api.py`:
- `ProfileService.rename_profile(fingerprint, name)` using fingerprint manager naming support.
- `NostrService.reset_relays()` to restore default relays, persist config/state, and update active Nostr client relay list.
- TUI palette additions in `src/seedpass/tui_v2/app.py`:
- `profile-rename <fingerprint> <name>`
- `relay-reset`
- Existing profile/relay command set now covers:
- profiles: list/switch/add/remove/rename
- relays/sync: list/add/remove/reset + sync-now/sync-bg
- Test updates:
- `src/tests/test_tui_v2_textual_interactions.py` fakes now implement `rename_profile` and `reset_relays`; success/validation tests assert both command paths.
- `src/tests/test_tui_v2_action_matrix.py` matrix guard coverage includes `profile-rename` and `relay-reset`.
- Validation:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py`
- Result: `86 passed`.
- Backlog update:
- `docs/tui_v2_parity_backlog.md` now marks both `profile switch/add/remove/list/rename` and `relay view/add/remove/reset + sync controls` complete.
- Remaining Phase D open items are `inactivity timeout + KDF settings` and checksum/import-export utility actions.
## 2026-03-01 TUI v2 parity Phase D completion slice: inactivity/KDF + checksum/export utilities
- Extended TUI launch dependency injection to include optional `utility_service_factory` and `vault_service_factory`.
- CLI preflight bundle (`src/seedpass/cli/__init__.py`) now provides utility/vault services to TUI v2 when manager-backed services are available.
- Added new settings commands in `src/seedpass/tui_v2/app.py`:
- `setting-timeout <seconds>`
- `setting-kdf-iterations <n>`
- `setting-kdf-mode <mode>`
- Added utility/export commands:
- `checksum-verify`
- `checksum-update`
- `db-export <path>`
- `db-import <path>`
- `totp-export <path>`
- `parent-seed-backup [path] [password]`
- Added support commands to existing profile/relay set:
- `profile-rename <fingerprint> <name>` and `relay-reset` (wired to new service methods).
- Service-layer additions in `src/seedpass/core/api.py`:
- `ProfileService.rename_profile(...)`
- `NostrService.reset_relays()`
- Test coverage updates:
- `src/tests/test_tui_v2_textual_interactions.py` now has fakes and assertions for all new settings/checksum/export/profile-rename/relay-reset commands.
- `src/tests/test_tui_v2_action_matrix.py` command matrix includes guard path invocations for the new commands.
- Validation:
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py`
- Result: `86 passed`.
- Backlog update:
- `docs/tui_v2_parity_backlog.md` now marks all Phase D items complete.
- Remaining backlog focus shifts to Phase E release/CI closure work.
## 2026-03-01 TUI v2 parity Phase E: CI smoke + docs/help closure
- Added dedicated CI smoke script for TUI v2 runtime diagnostics:
- `scripts/tui2_check_smoke.sh`
- Executes `seedpass tui2 --check` when CLI binary is available, otherwise falls back to Typer `CliRunner` invocation.
- Validates JSON output keys (`status`, `backend`, `textual_available`, `message`) and backend value (`textual`).
- Includes `python`/`python3` fallback handling for portability.
- Wired CI smoke gate into default CI test runner:
- `scripts/run_ci_tests.sh` now runs `./scripts/tui2_check_smoke.sh` before determinism/full test suite.
- Added explicit workflow smoke step for Poetry matrix:
- `.github/workflows/tests.yml` includes `poetry run seedpass tui2 --check`.
- Phase E docs/status updates:
- `docs/tui_v2_parity_backlog.md` marks all Phase E closure items complete and includes release notes.
- `docs/tui_v2_plan.md` marks the CI smoke command item complete in Phase 0.
- `docs/tui_v2_parity_checklist.md` adds closure notes for CI smoke and retained legacy fallback paths.
- Validation run:
- `scripts/tui2_check_smoke.sh` => `tui2 --check smoke: ok`
- `bash -n scripts/tui2_check_smoke.sh scripts/run_ci_tests.sh` => clean
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py` => `86 passed`.
+2
View File
@@ -3,6 +3,8 @@ set -eo pipefail
mkdir -p artifacts/coverage
./scripts/tui2_check_smoke.sh
if [[ "${DETERMINISM_GATE:-1}" == "1" ]]; then
./scripts/run_determinism_tests.sh
fi
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail
# CI smoke gate for TUI v2 runtime diagnostics.
# Prefer the real CLI command when available; fall back to Typer CliRunner.
if command -v seedpass >/dev/null 2>&1; then
raw_output="$(seedpass tui2 --check)"
else
py_bin="python"
if ! command -v "$py_bin" >/dev/null 2>&1; then
py_bin="python3"
fi
raw_output="$("$py_bin" - <<'PY'
import json
from typer.testing import CliRunner
from seedpass.cli import app
runner = CliRunner()
result = runner.invoke(app, ["tui2", "--check"])
if result.exit_code != 0:
raise SystemExit(result.exit_code)
print(result.stdout.strip())
PY
)"
fi
py_bin="python"
if ! command -v "$py_bin" >/dev/null 2>&1; then
py_bin="python3"
fi
"$py_bin" - <<'PY' "$raw_output"
import json
import sys
text = sys.argv[1].strip()
data = json.loads(text)
required = {"status", "backend", "textual_available", "message"}
missing = sorted(required - set(data))
if missing:
raise SystemExit(f"tui2 --check missing keys: {', '.join(missing)}")
if data.get("backend") != "textual":
raise SystemExit("tui2 --check backend mismatch")
print("tui2 --check smoke: ok")
PY
+37 -6
View File
@@ -84,14 +84,33 @@ def _gui_backend_available() -> bool:
def _prime_tui2_service(
ctx: typer.Context,
) -> tuple[Optional[object], Optional[Exception]]:
) -> tuple[Optional[dict[str, object]], Optional[Exception]]:
"""Initialize entry service before Textual mounts.
PasswordManager initialization may prompt for credentials. Doing that before
entering Textual prevents hidden prompt stalls that appear as a blank screen.
"""
try:
return _get_entry_service(ctx), None
from seedpass.core.api import (
ProfileService,
ConfigService,
NostrService,
SyncService,
UtilityService,
VaultService,
)
entry_service = _get_entry_service(ctx)
pm = getattr(entry_service, "_manager", None)
return {
"entry": entry_service,
"profile": ProfileService(pm) if pm is not None else None,
"config": ConfigService(pm) if pm is not None else None,
"nostr": NostrService(pm) if pm is not None else None,
"sync": SyncService(pm) if pm is not None else None,
"utility": UtilityService(pm) if pm is not None else None,
"vault": VaultService(pm) if pm is not None else None,
}, None
except Exception as exc: # pragma: no cover - defensive runtime path
return None, exc
@@ -126,7 +145,7 @@ def main(
if legacy_tui:
raise _launch_legacy_tui(fingerprint=fingerprint)
service, preflight_error = _prime_tui2_service(ctx)
services, preflight_error = _prime_tui2_service(ctx)
if preflight_error is not None:
typer.echo(
"TUI v2 preflight failed; falling back to legacy TUI. "
@@ -136,7 +155,13 @@ def main(
launched = launch_tui2(
fingerprint=fingerprint,
entry_service_factory=lambda: service,
entry_service_factory=lambda: services["entry"],
profile_service_factory=lambda: services["profile"],
config_service_factory=lambda: services["config"],
nostr_service_factory=lambda: services["nostr"],
sync_service_factory=lambda: services["sync"],
utility_service_factory=lambda: services["utility"],
vault_service_factory=lambda: services["vault"],
)
if launched:
return
@@ -176,7 +201,7 @@ def tui2(
return
fingerprint = (ctx.obj or {}).get("fingerprint")
service, preflight_error = _prime_tui2_service(ctx)
services, preflight_error = _prime_tui2_service(ctx)
if preflight_error is not None:
if fallback_legacy:
typer.echo(
@@ -192,7 +217,13 @@ def tui2(
launched = launch_tui2(
fingerprint=fingerprint,
entry_service_factory=lambda: service,
entry_service_factory=lambda: services["entry"],
profile_service_factory=lambda: services["profile"],
config_service_factory=lambda: services["config"],
nostr_service_factory=lambda: services["nostr"],
sync_service_factory=lambda: services["sync"],
utility_service_factory=lambda: services["utility"],
vault_service_factory=lambda: services["vault"],
)
if launched:
return
+20
View File
@@ -224,6 +224,14 @@ class ProfileService:
with self._lock:
self._manager.select_fingerprint(req.fingerprint, password=req.password)
def rename_profile(self, fingerprint: str, name: str | None) -> None:
"""Set or clear a display name for ``fingerprint``."""
with self._lock:
ok = self._manager.fingerprint_manager.set_name(fingerprint, name)
if not ok:
raise ValueError("profile not found")
class SyncService:
"""Thread-safe wrapper around vault synchronization."""
@@ -889,3 +897,15 @@ class NostrService:
self._manager.nostr_client.relays = (
self._manager.state_manager.list_relays()
)
def reset_relays(self) -> list[str]:
with self._lock:
from nostr.client import DEFAULT_RELAYS
relays = list(DEFAULT_RELAYS)
self._manager.config_manager.set_relays(relays, require_pin=False)
state = self._manager.state_manager._load()
state["relays"] = relays
self._manager.state_manager._save(state)
self._manager.nostr_client.relays = list(relays)
return relays
File diff suppressed because it is too large. Load diff
+274
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
from pathlib import Path
from types import SimpleNamespace
import pytest
@@ -39,6 +40,9 @@ class MatrixEntryService:
self.clipboard_delay = 30
self.clipboard_values: list[str] = []
def _next_id(self) -> int:
return (max(self._entries.keys()) + 1) if self._entries else 1
def search_entries(self, query: str, kinds: list[str] | None = None):
if self.fail_search:
raise RuntimeError("search failed")
@@ -69,6 +73,198 @@ class MatrixEntryService:
return {}
return dict(self._entries[int(entry_id)])
def add_entry(
self,
label: str,
length: int,
username: str | None = None,
url: str | None = None,
) -> int:
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "password",
"label": label,
"length": int(length),
"username": username,
"url": url,
"archived": False,
}
return entry_id
def add_totp(
self,
label: str,
*,
index: int | None = None,
secret: str | None = None,
period: int = 30,
digits: int = 6,
deterministic: bool = False,
) -> str:
_ = (index, deterministic)
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "totp",
"label": label,
"secret": secret or "JBSWY3DPEHPK3PXP",
"period": int(period),
"digits": int(digits),
"archived": False,
}
return f"otpauth://totp/{label}"
def add_key_value(
self, label: str, key: str, value: str, *, notes: str = ""
) -> int:
_ = notes
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "key_value",
"label": label,
"key": key,
"value": value,
"archived": False,
}
return entry_id
def add_document(
self,
label: str,
content: str,
*,
file_type: str = "txt",
notes: str = "",
tags: list[str] | None = None,
archived: bool = False,
) -> int:
_ = notes
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "document",
"label": label,
"content": content,
"file_type": file_type,
"tags": tags or [],
"archived": bool(archived),
}
return entry_id
def add_ssh_key(
self, label: str, *, index: int | None = None, notes: str = ""
) -> int:
_ = (index, notes)
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "ssh",
"label": label,
"private_key": f"SSH_PRIVATE_{entry_id}",
"public_key": f"ssh-ed25519 AAAA-{entry_id}",
"archived": False,
}
return entry_id
def add_pgp_key(
self,
label: str,
*,
index: int | None = None,
key_type: str = "ed25519",
user_id: str = "",
notes: str = "",
) -> int:
_ = (index, key_type, user_id, notes)
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "pgp",
"label": label,
"private_key": "-----BEGIN PGP PRIVATE KEY BLOCK-----",
"fingerprint": f"FPR-{entry_id}",
"archived": False,
}
return entry_id
def add_nostr_key(
self, label: str, *, index: int | None = None, notes: str = ""
) -> int:
_ = (index, notes)
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "nostr",
"label": label,
"npub": f"npub{entry_id}",
"nsec": f"nsec{entry_id}",
"archived": False,
}
return entry_id
def add_seed(
self,
label: str,
*,
index: int | None = None,
words: int = 24,
notes: str = "",
) -> int:
_ = (index, notes)
word_count = max(12, int(words))
phrase = " ".join(["abandon"] * (word_count - 1) + ["about"])
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "seed",
"label": label,
"seed_phrase": phrase,
"archived": False,
}
return entry_id
def add_managed_account(
self, label: str, *, index: int | None = None, notes: str = ""
) -> int:
_ = (index, notes)
entry_id = self._next_id()
self._entries[entry_id] = {
"id": entry_id,
"kind": "managed_account",
"label": label,
"seed_phrase": "legal winner thank year wave sausage worth useful legal winner thank yellow",
"archived": False,
}
return entry_id
def export_document_file(
self,
entry_id: int,
output_path: str | Path | None = None,
*,
overwrite: bool = False,
) -> Path:
_ = overwrite
entry = self._entries[int(entry_id)]
kind = str(entry.get("kind", ""))
if kind != "document":
raise ValueError("Entry is not a document entry")
name = str(entry.get("label", f"document-{entry_id}")).replace(" ", "_")
ext = str(entry.get("file_type", "txt")).lstrip(".") or "txt"
if output_path is None:
dest = Path.cwd() / f"{name}.{ext}"
else:
raw = Path(output_path)
if raw.suffix:
dest = raw
else:
dest = raw / f"{name}.{ext}"
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(str(entry.get("content", "")), encoding="utf-8")
return dest
def modify_entry(self, entry_id: int, **kwargs) -> None:
if self.fail_modify:
raise RuntimeError("modify failed")
@@ -289,6 +485,61 @@ async def test_tui2_matrix_actions_palette_events_and_guards() -> None:
app._run_palette_command("link-add no")
app._run_palette_command("link-rm")
app._run_palette_command("link-rm no")
app._run_palette_command("add-password")
app._run_palette_command("add-password Site nope")
app._run_palette_command("add-totp")
app._run_palette_command("add-totp Auth bad")
app._run_palette_command("add-key-value")
app._run_palette_command("add-document")
app._run_palette_command("add-ssh")
app._run_palette_command("add-ssh Host nope")
app._run_palette_command("add-pgp")
app._run_palette_command("add-pgp Pgp nope")
app._run_palette_command("add-nostr")
app._run_palette_command("add-nostr Nostr nope")
app._run_palette_command("add-seed")
app._run_palette_command("add-seed Seed nope")
app._run_palette_command("add-managed-account")
app._run_palette_command("add-managed-account Managed nope")
app._run_palette_command("notes-set")
app._run_palette_command("notes-clear nope")
app._run_palette_command("tag-add")
app._run_palette_command("tag-rm")
app._run_palette_command("tags-set")
app._run_palette_command("tags-clear nope")
app._run_palette_command("field-add")
app._run_palette_command("field-rm")
app._run_palette_command("set-field")
app._run_palette_command("clear-field")
app._run_palette_command("2fa-board nope")
app._run_palette_command("2fa-hide nope")
app._run_palette_command("2fa-refresh nope")
app._run_palette_command("2fa-copy")
app._run_palette_command("2fa-copy nope")
app._run_palette_command("profiles-list")
app._run_palette_command("profile-switch")
app._run_palette_command("profile-add")
app._run_palette_command("profile-remove")
app._run_palette_command("profile-rename")
app._run_palette_command("setting-secret on")
app._run_palette_command("setting-offline on")
app._run_palette_command("setting-quick-unlock on")
app._run_palette_command("setting-timeout 300")
app._run_palette_command("setting-kdf-iterations 100000")
app._run_palette_command("setting-kdf-mode pbkdf2")
app._run_palette_command("relay-list")
app._run_palette_command("relay-add wss://relay.example")
app._run_palette_command("relay-rm 1")
app._run_palette_command("relay-reset")
app._run_palette_command("sync-now")
app._run_palette_command("sync-bg")
app._run_palette_command("checksum-verify")
app._run_palette_command("checksum-update")
app._run_palette_command("db-export /tmp/seedpass-db.enc")
app._run_palette_command("db-import /tmp/seedpass-db.enc")
app._run_palette_command("totp-export /tmp/seedpass-totp.json")
app._run_palette_command("parent-seed-backup")
app._run_palette_command("doc-export one two")
app._run_palette_command("filter")
app._run_palette_command("unknown-cmd")
await pilot.pause()
@@ -301,6 +552,29 @@ async def test_tui2_matrix_actions_palette_events_and_guards() -> None:
app._run_palette_command("page 2")
app._run_palette_command("open 1")
app._run_palette_command("jump 2")
app._run_palette_command("add-password NewLogin 18 user https://seedpass.dev")
app._run_palette_command("add-totp NewAuth 30 6 JBSWY3DPEHPK3PXP")
app._run_palette_command("add-key-value Env API_KEY value")
app._run_palette_command("add-document Runbook md body")
app._run_palette_command("add-ssh Host 10")
app._run_palette_command("add-pgp Pgp 11 ed25519 [email protected]")
app._run_palette_command("add-nostr Nostr 12")
app._run_palette_command("add-seed Seed 24 13")
app._run_palette_command("add-managed-account Managed 14")
app._run_palette_command('notes-set "matrix note"')
app._run_palette_command("tag-add matrix")
app._run_palette_command("tag-rm matrix")
app._run_palette_command("tags-set m1, m2")
app._run_palette_command("tags-clear")
app._run_palette_command("field-add scope admin hidden")
app._run_palette_command("field-rm scope")
app._run_palette_command("set-field notes matrix-updated")
app._run_palette_command("clear-field notes")
app._run_palette_command("2fa-board")
app._run_palette_command("2fa-copy 2")
app._run_palette_command("2fa-refresh")
app._run_palette_command("2fa-hide")
app._run_palette_command("doc-export /tmp/seedpass-matrix-export")
await pilot.pause()
app._run_palette_command("open 2")
File diff suppressed because it is too large. Load diff