@@ -429,12 +429,13 @@ Service Agents:
429429Diagnostic commands:
430430 pilotctl info
431431 pilotctl health
432- pilotctl peers [--search <query>]
432+ pilotctl peers [--search <query>] [--show-endpoints]
433433 pilotctl ping <address|hostname> [--count <n>] [--timeout <dur>]
434434 pilotctl traceroute <address> [--timeout <dur>]
435435 pilotctl bench <address|hostname> [size_mb] [--timeout <dur>]
436436 pilotctl listen <port> [--count <n>] [--timeout <dur>]
437437 pilotctl broadcast <network_id> <message>
438+ pilotctl updates [--count <n>] [--scope <scope>] read https://teoslayer.github.io/pilot-changelog/feed.xml
438439
439440Agent tool discovery:
440441 pilotctl context
@@ -483,6 +484,10 @@ func main() {
483484 fmt .Println (version )
484485 return
485486
487+ case "updates" :
488+ cmdUpdates (cmdArgs )
489+ return
490+
486491 // Bootstrap
487492 case "init" :
488493 cmdInit (cmdArgs )
@@ -764,7 +769,7 @@ func main() {
764769
765770 // Diagnostics
766771 case "info" :
767- cmdInfo ()
772+ cmdInfo (cmdArgs )
768773 case "my-polo" :
769774 cmdMyPolo ()
770775 case "health" :
@@ -1892,18 +1897,46 @@ func cmdRegister(args []string) {
18921897
18931898func cmdLookup (args []string ) {
18941899 if len (args ) < 1 {
1895- fatalCode ("invalid_argument" , "usage: pilotctl lookup <node_id>" )
1900+ fatalCode ("invalid_argument" , "usage: pilotctl lookup <node_id> [--show-endpoints] " )
18961901 }
1897- nodeID := parseNodeID (args [0 ])
1902+ flags , pos := parseFlags (args )
1903+ if len (pos ) < 1 {
1904+ fatalCode ("invalid_argument" , "usage: pilotctl lookup <node_id> [--show-endpoints]" )
1905+ }
1906+ showEndpoints := flagBool (flags , "show-endpoints" )
1907+ nodeID := parseNodeID (pos [0 ])
18981908 rc := connectRegistry ()
18991909 defer rc .Close ()
19001910 resp , err := rc .Lookup (nodeID )
19011911 if err != nil {
19021912 fatalCode ("connection_failed" , "lookup: %v" , err )
19031913 }
1914+ if ! showEndpoints {
1915+ redactPeerEndpoints (resp )
1916+ }
19041917 output (resp )
19051918}
19061919
1920+ // redactPeerEndpoints walks a registry/daemon response and drops any
1921+ // IP-bearing fields. Operates in-place; safe to call on nil/non-map types.
1922+ // Removed keys: endpoint, real_addr, lan_addrs, public_addr, ip, addr.
1923+ // Recurses into nested maps and into peer_list / nodes / data sub-objects.
1924+ func redactPeerEndpoints (v interface {}) {
1925+ switch x := v .(type ) {
1926+ case map [string ]interface {}:
1927+ for _ , k := range []string {"endpoint" , "real_addr" , "lan_addrs" , "public_addr" , "stun_addr" , "observed_addr" } {
1928+ delete (x , k )
1929+ }
1930+ for _ , vv := range x {
1931+ redactPeerEndpoints (vv )
1932+ }
1933+ case []interface {}:
1934+ for _ , vv := range x {
1935+ redactPeerEndpoints (vv )
1936+ }
1937+ }
1938+ }
1939+
19071940func cmdRotateKey (args []string ) {
19081941 _ = args
19091942 d := connectDriver ()
@@ -3773,7 +3806,10 @@ func cmdMyPolo() {
37733806 fmt .Printf ("My polo score: %d\n " , score )
37743807}
37753808
3776- func cmdInfo () {
3809+ func cmdInfo (args []string ) {
3810+ flags , _ := parseFlags (args )
3811+ showEndpoints := flagBool (flags , "show-endpoints" )
3812+
37773813 d := connectDriver ()
37783814 defer d .Close ()
37793815
@@ -3782,6 +3818,14 @@ func cmdInfo() {
37823818 fatalCode ("connection_failed" , "info: %v" , err )
37833819 }
37843820
3821+ // Privacy: strip per-peer endpoints + STUN-discovered own addresses
3822+ // from the JSON dump unless the operator explicitly opts in via
3823+ // --show-endpoints. The summary counters (peers, encrypted_peers)
3824+ // stay; only the IP-bearing fields are redacted.
3825+ if ! showEndpoints {
3826+ redactPeerEndpoints (info )
3827+ }
3828+
37853829 if jsonOutput {
37863830 output (info )
37873831 return
@@ -3926,6 +3970,10 @@ func cmdHealth() {
39263970func cmdPeers (args []string ) {
39273971 flags , _ := parseFlags (args )
39283972 search := flagString (flags , "search" , "" )
3973+ // Privacy default: peer real IPs are hidden. Opt in with --show-endpoints
3974+ // (ops/debug only). Search by node_id always works; search by endpoint
3975+ // fragment only works when endpoints are visible.
3976+ showEndpoints := flagBool (flags , "show-endpoints" )
39293977
39303978 d := connectDriver ()
39313979 defer d .Close ()
@@ -3950,13 +3998,38 @@ func cmdPeers(args []string) {
39503998 peer := p .(map [string ]interface {})
39513999 searchLower := strings .ToLower (search )
39524000 nodeIDStr := fmt .Sprintf ("%d" , int (peer ["node_id" ].(float64 )))
3953- endpoint , _ := peer ["endpoint" ].(string )
3954- if strings .Contains (nodeIDStr , searchLower ) ||
3955- strings .Contains (strings .ToLower (endpoint ), searchLower ) {
4001+ match := strings .Contains (nodeIDStr , searchLower )
4002+ if ! match && showEndpoints {
4003+ // only consult endpoint when the user has explicitly asked
4004+ // to see it; never let a search prompt leak IP existence.
4005+ endpoint , _ := peer ["endpoint" ].(string )
4006+ match = strings .Contains (strings .ToLower (endpoint ), searchLower )
4007+ }
4008+ if match {
39564009 filtered = append (filtered , p )
39574010 }
39584011 }
39594012
4013+ // Redact endpoint unless the operator opted in.
4014+ if ! showEndpoints {
4015+ redacted := make ([]interface {}, 0 , len (filtered ))
4016+ for _ , p := range filtered {
4017+ peer , _ := p .(map [string ]interface {})
4018+ if peer == nil {
4019+ continue
4020+ }
4021+ cp := make (map [string ]interface {}, len (peer ))
4022+ for k , v := range peer {
4023+ if k == "endpoint" || k == "real_addr" || k == "lan_addrs" || k == "public_addr" {
4024+ continue
4025+ }
4026+ cp [k ] = v
4027+ }
4028+ redacted = append (redacted , cp )
4029+ }
4030+ filtered = redacted
4031+ }
4032+
39604033 if jsonOutput {
39614034 output (map [string ]interface {}{
39624035 "peers" : filtered ,
@@ -3976,7 +4049,11 @@ func cmdPeers(args []string) {
39764049 }
39774050
39784051 maxDisplay := 50
3979- fmt .Printf ("%-10s %-30s %-20s %s\n " , "NODE ID" , "ENDPOINT" , "ENCRYPTED" , "AUTH" )
4052+ if showEndpoints {
4053+ fmt .Printf ("%-10s %-30s %-20s %s\n " , "NODE ID" , "ENDPOINT" , "ENCRYPTED" , "AUTH" )
4054+ } else {
4055+ fmt .Printf ("%-10s %-20s %s\n " , "NODE ID" , "ENCRYPTED" , "AUTH" )
4056+ }
39804057 displayed := 0
39814058 for _ , p := range filtered {
39824059 if displayed >= maxDisplay {
@@ -4001,7 +4078,11 @@ func cmdPeers(args []string) {
40014078 if authenticated {
40024079 authStr = "yes (Ed25519)"
40034080 }
4004- fmt .Printf ("%-10d %-30s %-20s %s\n " , int (peer ["node_id" ].(float64 )), peer ["endpoint" ], encStr , authStr )
4081+ if showEndpoints {
4082+ fmt .Printf ("%-10d %-30s %-20s %s\n " , int (peer ["node_id" ].(float64 )), peer ["endpoint" ], encStr , authStr )
4083+ } else {
4084+ fmt .Printf ("%-10d %-20s %s\n " , int (peer ["node_id" ].(float64 )), encStr , authStr )
4085+ }
40054086 }
40064087}
40074088
@@ -4431,7 +4512,44 @@ func cmdListen(args []string) {
44314512}
44324513
44334514func cmdBroadcast (args []string ) {
4434- fatalCode ("unavailable" , "broadcast is not available yet — custom networks are WIP" )
4515+ flags , positional := parseFlags (args )
4516+ if len (positional ) < 2 {
4517+ fatalCode ("usage" , "usage: pilotctl broadcast <network_id> <message> [--port <port>]" )
4518+ }
4519+ netID64 , err := strconv .ParseUint (positional [0 ], 10 , 16 )
4520+ if err != nil {
4521+ fatalCode ("usage" , "invalid network_id: %v" , err )
4522+ }
4523+ netID := uint16 (netID64 )
4524+ message := positional [1 ]
4525+
4526+ port := uint16 (1000 )
4527+ if v := flagString (flags , "port" , "" ); v != "" {
4528+ p , err := strconv .ParseUint (v , 10 , 16 )
4529+ if err != nil {
4530+ fatalCode ("usage" , "invalid --port: %v" , err )
4531+ }
4532+ port = uint16 (p )
4533+ }
4534+
4535+ token := requireAdminToken ()
4536+
4537+ d := connectDriver ()
4538+ defer d .Close ()
4539+
4540+ if err := d .Broadcast (netID , port , []byte (message ), token ); err != nil {
4541+ fatalCode ("broadcast_failed" , "%v" , err )
4542+ }
4543+
4544+ if jsonOutput {
4545+ output (map [string ]interface {}{
4546+ "network_id" : netID ,
4547+ "port" : port ,
4548+ "bytes" : len (message ),
4549+ })
4550+ } else {
4551+ fmt .Printf ("broadcast on network %d port %d (%d bytes)\n " , netID , port , len (message ))
4552+ }
44354553}
44364554
44374555// ===================== MAILBOX =====================
@@ -4656,19 +4774,23 @@ func cmdNetworkList() {
46564774 fmt .Println ("no networks" )
46574775 return
46584776 }
4777+ // Member counts are admin-only at the registry. Without admin_token
4778+ // the registry omits the `members` field; render "—" so the column
4779+ // stays aligned and it's clear the count is hidden by policy rather
4780+ // than broken.
46594781 fmt .Printf ("%-8s %-30s %-10s %s\n " , "ID" , "NAME" , "JOIN RULE" , "MEMBERS" )
46604782 for _ , n := range nets {
46614783 nm , _ := n .(map [string ]interface {})
46624784 id := uint16 (nm ["id" ].(float64 ))
46634785 name , _ := nm ["name" ].(string )
46644786 rule , _ := nm ["join_rule" ].(string )
4665- count := 0
4787+ memberStr := "—"
46664788 if members , ok := nm ["members" ].([]interface {}); ok {
4667- count = len (members )
4789+ memberStr = fmt . Sprintf ( "%d" , len (members ) )
46684790 } else if mc , ok := nm ["members" ].(float64 ); ok {
4669- count = int (mc )
4791+ memberStr = fmt . Sprintf ( "%d" , int (mc ) )
46704792 }
4671- fmt .Printf ("%-8d %-30s %-10s %d \n " , id , name , rule , count )
4793+ fmt .Printf ("%-8d %-30s %-10s %s \n " , id , name , rule , memberStr )
46724794 }
46734795}
46744796
0 commit comments