diff --git a/src/main.py b/src/main.py index 6219ca5..61da7f2 100644 --- a/src/main.py +++ b/src/main.py @@ -365,14 +365,15 @@ def handle_post_to_nostr( Handles the action of posting the encrypted password index to Nostr. """ try: - event_id = password_manager.sync_vault(alt_summary=alt_summary) - if event_id: - print( - colored( - f"\N{WHITE HEAVY CHECK MARK} Sync complete. Event ID: {event_id}", - "green", - ) - ) + result = password_manager.sync_vault(alt_summary=alt_summary) + if result: + print(colored("\N{WHITE HEAVY CHECK MARK} Sync complete.", "green")) + print("Event IDs:") + print(f" manifest: {result['manifest_id']}") + for cid in result["chunk_ids"]: + print(f" chunk: {cid}") + for did in result["delta_ids"]: + print(f" delta: {did}") logging.info("Encrypted index posted to Nostr successfully.") else: print(colored("\N{CROSS MARK} Sync failed…", "red")) diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 05ac948..c3c2205 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -3517,8 +3517,10 @@ class PasswordManager: # Re-raise the exception to inform the calling function of the failure raise - def sync_vault(self, alt_summary: str | None = None) -> str | None: - """Publish the current vault contents to Nostr.""" + def sync_vault( + self, alt_summary: str | None = None + ) -> dict[str, list[str] | str] | None: + """Publish the current vault contents to Nostr and return event IDs.""" try: if getattr(self, "offline_mode", False): return None @@ -3526,16 +3528,28 @@ class PasswordManager: if not encrypted: return None pub_snap = getattr(self.nostr_client, "publish_snapshot", None) + manifest = None + event_id = None if callable(pub_snap): if asyncio.iscoroutinefunction(pub_snap): - _, event_id = asyncio.run(pub_snap(encrypted)) + manifest, event_id = asyncio.run(pub_snap(encrypted)) else: - _, event_id = pub_snap(encrypted) + manifest, event_id = pub_snap(encrypted) else: # Fallback for tests using simplified stubs event_id = self.nostr_client.publish_json_to_nostr(encrypted) self.is_dirty = False - return event_id + if event_id is None: + return None + chunk_ids: list[str] = [] + if manifest is not None: + chunk_ids = [c.event_id for c in manifest.chunks if c.event_id] + delta_ids = getattr(self.nostr_client, "_delta_events", []) + return { + "manifest_id": event_id, + "chunk_ids": chunk_ids, + "delta_ids": list(delta_ids), + } except Exception as e: logging.error(f"Failed to sync vault: {e}", exc_info=True) return None diff --git a/src/seedpass/cli.py b/src/seedpass/cli.py index d8df065..2ffb0e6 100644 --- a/src/seedpass/cli.py +++ b/src/seedpass/cli.py @@ -419,9 +419,14 @@ def vault_reveal_parent_seed( def nostr_sync(ctx: typer.Context) -> None: """Sync with configured Nostr relays.""" pm = _get_pm(ctx) - event_id = pm.sync_vault() - if event_id: - typer.echo(event_id) + result = pm.sync_vault() + if result: + typer.echo("Event IDs:") + typer.echo(f"- manifest: {result['manifest_id']}") + for cid in result["chunk_ids"]: + typer.echo(f"- chunk: {cid}") + for did in result["delta_ids"]: + typer.echo(f"- delta: {did}") else: typer.echo("Error: Failed to sync vault") diff --git a/src/tests/test_cli_doc_examples.py b/src/tests/test_cli_doc_examples.py index 44bf430..9937926 100644 --- a/src/tests/test_cli_doc_examples.py +++ b/src/tests/test_cli_doc_examples.py @@ -53,7 +53,11 @@ class DummyPM: self.nostr_client = SimpleNamespace( key_manager=SimpleNamespace(get_npub=lambda: "npub") ) - self.sync_vault = lambda: "event" + self.sync_vault = lambda: { + "manifest_id": "event", + "chunk_ids": ["c1"], + "delta_ids": [], + } self.config_manager = SimpleNamespace( load_config=lambda require_pin=False: {"inactivity_timeout": 30}, set_inactivity_timeout=lambda v: None, diff --git a/src/tests/test_post_sync_messages.py b/src/tests/test_post_sync_messages.py index 2491217..c0273d5 100644 --- a/src/tests/test_post_sync_messages.py +++ b/src/tests/test_post_sync_messages.py @@ -9,12 +9,17 @@ import main def test_handle_post_success(capsys): pm = SimpleNamespace( - sync_vault=lambda alt_summary=None: "abcd", + sync_vault=lambda alt_summary=None: { + "manifest_id": "abcd", + "chunk_ids": ["c1", "c2"], + "delta_ids": ["d1"], + }, ) main.handle_post_to_nostr(pm) out = capsys.readouterr().out assert "✅ Sync complete." in out assert "abcd" in out + assert "c1" in out and "c2" in out and "d1" in out def test_handle_post_failure(capsys): diff --git a/src/tests/test_typer_cli.py b/src/tests/test_typer_cli.py index b64a2d1..878fd0c 100644 --- a/src/tests/test_typer_cli.py +++ b/src/tests/test_typer_cli.py @@ -288,7 +288,11 @@ def test_nostr_sync(monkeypatch): def sync_vault(): called["called"] = True - return "evt123" + return { + "manifest_id": "evt123", + "chunk_ids": ["c1"], + "delta_ids": ["d1"], + } pm = SimpleNamespace(sync_vault=sync_vault, select_fingerprint=lambda fp: None) monkeypatch.setattr(cli, "PasswordManager", lambda: pm) @@ -296,6 +300,8 @@ def test_nostr_sync(monkeypatch): assert result.exit_code == 0 assert called.get("called") is True assert "evt123" in result.stdout + assert "c1" in result.stdout + assert "d1" in result.stdout def test_generate_password(monkeypatch):