Merge pull request #608 from PR0M3TH3AN/codex/update-passwordmanager.sync_vault-return-format

Expose all event IDs when syncing vault
This commit is contained in:
thePR0M3TH3AN
2025-07-17 15:30:40 -04:00
committed by GitHub
6 changed files with 54 additions and 19 deletions

View File

@@ -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"))

View File

@@ -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

View File

@@ -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")

View File

@@ -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,

View File

@@ -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):

View File

@@ -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):