diff --git a/src/psyc/_federation_cli.py b/src/psyc/_federation_cli.py index e36bfd7..b51223c 100644 --- a/src/psyc/_federation_cli.py +++ b/src/psyc/_federation_cli.py @@ -13,7 +13,7 @@ import httpx import typer from psyc import db, log -from psyc.lines import discovery, federation, pulse, translog +from psyc.lines import discovery, federation, network_view, pulse, translog from psyc.result import Err, Ok @@ -274,6 +274,58 @@ def register(typer_app: typer.Typer) -> None: f" id={e.id:5d} {e.entry_type:6s} {e.timestamp[:19]} hash={e.entry_hash[:16]}…" ) + # ---------- network view ------------------------------------------ + + @typer_app.command("fed-network") + def fed_network() -> None: + """Print the local federation network view — nodes, vouches, stats.""" + db.init_db() + view = network_view.build_local_view() + + # Nodes table. + typer.echo("NODES") + typer.echo(f" {'fingerprint':<34} {'label':<32} {'status':<9} dist") + for n in view.nodes: + fp = f"{n.fingerprint[:8]}…{n.fingerprint[-8:]}" if len(n.fingerprint) >= 16 else n.fingerprint + label = (n.label or "")[:30] + typer.echo(f" {fp:<34} {label:<32} {n.status:<9} {n.distance}") + + # Vouches breakdown. + our_fp = view.nodes[0].fingerprint + vouch_out = [e for e in view.edges if e.kind == "vouch" and e.source_fingerprint == our_fp] + vouch_in = [e for e in view.edges if e.kind == "vouch" and e.target_fingerprint == our_fp] + bidir = [e for e in vouch_out if e.bidirectional] + + typer.echo("") + typer.echo("VOUCHES") + if not vouch_out and not vouch_in and not bidir: + typer.echo(" (no vouches)") + else: + for e in vouch_out: + arrow = "↔" if e.bidirectional else "→" + fp = f"{e.target_fingerprint[:8]}…{e.target_fingerprint[-8:]}" + typer.echo(f" us {arrow} {fp}") + for e in vouch_in: + fp = f"{e.source_fingerprint[:8]}…{e.source_fingerprint[-8:]}" + typer.echo(f" {fp} → us") + + # Signal edges. + sig_edges = [e for e in view.edges if e.kind == "signal"] + typer.echo("") + typer.echo("SIGNALS (24h)") + if not sig_edges: + typer.echo(" (no signals)") + else: + for e in sig_edges: + fp = f"{e.source_fingerprint[:8]}…{e.source_fingerprint[-8:]}" + typer.echo(f" from {fp}: {int(e.weight)}") + + # Stats footer. + typer.echo("") + typer.echo("STATS") + for k, v in view.stats.items(): + typer.echo(f" {k:<32} {v}") + @typer_app.command("fed-log-verify") def fed_log_verify() -> None: """Re-walk the chain locally and report verification status."""