feat(cli): show relay version in `portal list` output
Call GET /sdk/domain on each relay in parallel to fetch its release version, then print tab-separated URL and version. Shows "unknown" when a relay is unreachable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Yechan Kim committed
Apr 16, 2026 at 19:38 UTC
5fdfb4dff79994a42c32a509346670ad89002377
2 files changed
+37
-2
cmd/portal-tunnel/main.go
+18
-2
@@ -7,6 +7,7 @@ import (
7
"fmt"
8
"io"
9
"os"
10
+ "sync"
11
"time"
12
13
"github.com/rs/zerolog"
@@ -175,8 +176,23 @@ func runListCommand(args []string) error {
176
return errors.New("no relay URLs configured")
177
}
178
178
- for _, relayURL := range relayURLs {
179
- fmt.Println(relayURL)
179
+ versions := make([]string, len(relayURLs))
180
+ var wg sync.WaitGroup
181
+ wg.Add(len(relayURLs))
182
+ for i, u := range relayURLs {
183
+ go func(idx int, url string) {
184
+ defer wg.Done()
185
+ versions[idx] = utils.FetchRelayVersion(ctx, url)
186
+ }(i, u)
187
+ }
188
+ wg.Wait()
189
+
190
+ for i, relayURL := range relayURLs {
191
+ ver := versions[i]
192
+ if ver == "" {
193
+ ver = "unknown"
194
+ }
195
+ fmt.Printf("%s\t%s\n", relayURL, ver)
196
}
197
return nil
198
}
utils/network.go
+19
@@ -122,6 +122,25 @@ func SanitizeReportedIP(raw string) string {
122
return candidate
123
}
124
125
+// FetchRelayVersion calls GET /sdk/domain on a relay and returns its release version.
126
+// Returns an empty string on any error (timeout, unreachable, bad response).
127
+func FetchRelayVersion(ctx context.Context, relayURL string) string {
128
+ client := &http.Client{Timeout: 3 * time.Second}
129
+ resp, err := httpDo(ctx, client, http.MethodGet, relayURL+types.PathSDKDomain, nil, nil)
130
+ if err != nil {
131
+ return ""
132
+ }
133
+ defer resp.Body.Close()
134
+ if resp.StatusCode != http.StatusOK {
135
+ return ""
136
+ }
137
+ var envelope types.APIEnvelope[types.DomainResponse]
138
+ if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil || !envelope.OK {
139
+ return ""
140
+ }
141
+ return envelope.Data.ReleaseVersion
142
+}
143
+
144
func ResolvePortalRelayURLs(ctx context.Context, explicit []string, includeDefaults bool) ([]string, error) {
145
explicit, err := NormalizeRelayURLs(explicit...)
146
if err != nil {