From 2d39d7a5bda4566f1668ef8c88f47f63ebbf7de4 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 28 Jul 2025 13:31:15 -0400 Subject: [PATCH 01/33] feat(stats): refresh triggers background sync --- src/main.py | 18 +++++++++++++++++- src/tests/test_stats_screen.py | 23 ++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main.py b/src/main.py index 3dea1f7..1fa2482 100644 --- a/src/main.py +++ b/src/main.py @@ -275,12 +275,23 @@ def handle_display_npub(password_manager: PasswordManager): def _display_live_stats( password_manager: PasswordManager, interval: float = 1.0 ) -> None: - """Continuously refresh stats until the user presses Enter.""" + """Continuously refresh stats until the user presses Enter. + + Each refresh also triggers a background sync so the latest stats are + displayed if newer data exists on Nostr. + """ display_fn = getattr(password_manager, "display_stats", None) + sync_fn = getattr(password_manager, "start_background_sync", None) if not callable(display_fn): return + if callable(sync_fn): + try: + sync_fn() + except Exception as exc: # pragma: no cover - sync best effort + logging.debug("Background sync failed during stats display: %s", exc) + if not sys.stdin or not sys.stdin.isatty(): clear_screen() display_fn() @@ -292,6 +303,11 @@ def _display_live_stats( return while True: + if callable(sync_fn): + try: + sync_fn() + except Exception: # pragma: no cover - sync best effort + logging.debug("Background sync failed during stats display") clear_screen() display_fn() note = get_notification_text(password_manager) diff --git a/src/tests/test_stats_screen.py b/src/tests/test_stats_screen.py index f65d72d..182c7b9 100644 --- a/src/tests/test_stats_screen.py +++ b/src/tests/test_stats_screen.py @@ -9,7 +9,10 @@ import main def _make_pm(): - return SimpleNamespace(display_stats=lambda: print("stats")) + return SimpleNamespace( + display_stats=lambda: print("stats"), + start_background_sync=lambda: None, + ) def test_live_stats_shows_message(monkeypatch, capsys): @@ -36,3 +39,21 @@ def test_live_stats_shows_notification(monkeypatch, capsys): main._display_live_stats(pm) out = capsys.readouterr().out assert "note" in out + + +def test_live_stats_triggers_background_sync(monkeypatch): + called = {"sync": 0} + + pm = _make_pm() + pm.start_background_sync = lambda: called.__setitem__("sync", called["sync"] + 1) + + monkeypatch.setattr(main, "get_notification_text", lambda *_: "") + monkeypatch.setattr( + main, + "timed_input", + lambda *_: (_ for _ in ()).throw(KeyboardInterrupt()), + ) + + main._display_live_stats(pm) + + assert called["sync"] >= 1 From a3d45a117c2f5cb389f647c31bb37630fa1d7dcf Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 28 Jul 2025 13:55:55 -0400 Subject: [PATCH 02/33] Fix relay URL handling for nostr-sdk 0.43 --- src/nostr/client.py | 19 ++++++++++++++++--- src/tests/test_nostr_client.py | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/nostr/client.py b/src/nostr/client.py index 0155617..76fdb00 100644 --- a/src/nostr/client.py +++ b/src/nostr/client.py @@ -21,6 +21,7 @@ from nostr_sdk import ( Kind, KindStandard, Tag, + RelayUrl, ) from datetime import timedelta from nostr_sdk import EventId, Timestamp @@ -173,14 +174,26 @@ class NostrClient: async def _initialize_client_pool(self) -> None: if self.offline_mode or not self.relays: return + + formatted = [] + for relay in self.relays: + if isinstance(relay, str): + try: + formatted.append(RelayUrl.parse(relay)) + except Exception: + logger.error("Invalid relay URL: %s", relay) + else: + formatted.append(relay) + if hasattr(self.client, "add_relays"): - await self.client.add_relays(self.relays) + await self.client.add_relays(formatted) else: - for relay in self.relays: + for relay in formatted: await self.client.add_relay(relay) + await self.client.connect() self._connected = True - logger.info(f"NostrClient connected to relays: {self.relays}") + logger.info("NostrClient connected to relays: %s", formatted) async def _ping_relay(self, relay: str, timeout: float) -> bool: """Attempt to retrieve the latest event from a single relay.""" diff --git a/src/tests/test_nostr_client.py b/src/tests/test_nostr_client.py index 1aa998f..0fc5a61 100644 --- a/src/tests/test_nostr_client.py +++ b/src/tests/test_nostr_client.py @@ -91,7 +91,7 @@ def test_initialize_client_pool_add_relays_used(tmp_path): client = _setup_client(tmp_path, FakeAddRelaysClient) fc = client.client client.connect() - assert fc.added == [client.relays] + assert [[str(r) for r in relays] for relays in fc.added] == [client.relays] assert fc.connected is True @@ -99,7 +99,7 @@ def test_initialize_client_pool_add_relay_fallback(tmp_path): client = _setup_client(tmp_path, FakeAddRelayClient) fc = client.client client.connect() - assert fc.added == client.relays + assert [str(r) for r in fc.added] == client.relays assert fc.connected is True From 4a20817094e3d5939f45ff9ac3999c368aef3c56 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:04:56 -0400 Subject: [PATCH 03/33] Add key field to key/value entries --- README.md | 2 +- .../01-getting-started/01-advanced_cli.md | 4 ++-- .../01-getting-started/03-json_entries.md | 1 + docs/docs/content/index.md | 2 +- src/seedpass/api.py | 1 + src/seedpass/cli.py | 3 ++- src/seedpass/core/api.py | 8 ++++++-- src/seedpass/core/entry_management.py | 9 +++++++++ src/seedpass/core/manager.py | 12 ++++++++++- src/seedpass_gui/app.py | 11 ++++++++-- src/tests/test_cli_doc_examples.py | 2 +- src/tests/test_cli_entry_add_commands.py | 4 ++-- src/tests/test_entry_add.py | 4 ++-- src/tests/test_gui_headless.py | 20 +++++++++++-------- src/tests/test_key_value_entry.py | 8 +++++--- src/tests/test_manager_list_entries.py | 10 +++++----- src/tests/test_search_entries.py | 2 +- 17 files changed, 71 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 902914e..7ce92b9 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ The table below summarizes the extra fields stored for each entry type. Every en | Seed Phrase | `index`, `word_count` *(mnemonic regenerated; never stored)*, `archived`, optional `notes`, optional `tags` | | PGP Key | `index`, `key_type`, `archived`, optional `user_id`, optional `notes`, optional `tags` | | Nostr Key Pair | `index`, `archived`, optional `notes`, optional `tags` | -| Key/Value | `value`, `archived`, optional `notes`, optional `custom_fields`, optional `tags` | +| Key/Value | `key`, `value`, `archived`, optional `notes`, optional `custom_fields`, optional `tags` | | Managed Account | `index`, `word_count`, `fingerprint`, `archived`, optional `notes`, optional `tags` | ### Managing Multiple Seeds diff --git a/docs/docs/content/01-getting-started/01-advanced_cli.md b/docs/docs/content/01-getting-started/01-advanced_cli.md index 3c86dcc..92d41aa 100644 --- a/docs/docs/content/01-getting-started/01-advanced_cli.md +++ b/docs/docs/content/01-getting-started/01-advanced_cli.md @@ -55,7 +55,7 @@ Manage individual entries within a vault. | Add a PGP key entry | `entry add-pgp` | `seedpass entry add-pgp Personal --user-id me@example.com` | | Add a Nostr key entry | `entry add-nostr` | `seedpass entry add-nostr Chat` | | Add a seed phrase entry | `entry add-seed` | `seedpass entry add-seed Backup --words 24` | -| Add a key/value entry | `entry add-key-value` | `seedpass entry add-key-value "API Token" --value abc123` | +| Add a key/value entry | `entry add-key-value` | `seedpass entry add-key-value "API Token" --key api --value abc123` | | Add a managed account entry | `entry add-managed-account` | `seedpass entry add-managed-account Trading` | | Modify an entry | `entry modify` | `seedpass entry modify 1 --username alice` | | Archive an entry | `entry archive` | `seedpass entry archive 1` | @@ -144,7 +144,7 @@ Run or stop the local HTTP API. - **`seedpass entry add-pgp