[client] Add netbird agent-network ls and env commands

Surface the caller-scoped Agent Network setup on the CLI. Both
commands dial management directly with the active profile's WireGuard
key — the same path foreground login uses — so no daemon proto or
engine wiring is needed for the proof of concept.

netbird agent-network ls prints the proxy endpoint, the authorized
providers, and the allowed models (--json for the raw response).

netbird agent-network env prints POSIX export lines for
Anthropic-compatible tools such as Claude Code, applied with
eval "$(netbird agent-network env)": ANTHROPIC_BASE_URL points at
the account's proxy endpoint and ANTHROPIC_AUTH_TOKEN carries a
placeholder (the proxy authenticates by tunnel peer and injects the
real upstream credentials). A model is never guessed: ANTHROPIC_MODEL
is exported only when exactly one model is allowed or --model pins
one; anything ambiguous is printed as comment lines instead.

"Not available for this peer" is an answer, not an error: both
commands exit 0 with a plain message (on stderr for env, keeping the
eval a harmless no-op).

Linear: NET-1399
This commit is contained in:
mlsmaycon
2026-08-04 00:32:03 +00:00
parent 5d4c7f32f4
commit 74b2f5cf4f
5 changed files with 321 additions and 0 deletions
+4
View File
@@ -34,4 +34,8 @@ type Client interface {
CreateExpose(ctx context.Context, req ExposeRequest) (*ExposeResponse, error)
RenewExpose(ctx context.Context, domain string) error
StopExpose(ctx context.Context, domain string) error
// GetAgentNetworkSetup returns the Agent Network connection info the
// calling peer's groups authorize: proxy endpoint plus effective
// providers and models.
GetAgentNetworkSetup(ctx context.Context) (*proto.AgentNetworkSetupResponse, error)
}
+32
View File
@@ -914,6 +914,38 @@ func (c *GrpcClient) StopExpose(ctx context.Context, domain string) error {
return err
}
// GetAgentNetworkSetup asks the management server for the Agent Network
// connection info the calling peer's groups authorize.
func (c *GrpcClient) GetAgentNetworkSetup(ctx context.Context) (*proto.AgentNetworkSetupResponse, error) {
serverPubKey, err := c.getServerPublicKey()
if err != nil {
return nil, err
}
encReq, err := encryption.EncryptMessage(*serverPubKey, c.key, &proto.AgentNetworkSetupRequest{})
if err != nil {
return nil, fmt.Errorf("encrypt agent network setup request: %w", err)
}
mgmCtx, cancel := context.WithTimeout(ctx, ConnectTimeout)
defer cancel()
resp, err := c.realClient.GetAgentNetworkSetup(mgmCtx, &proto.EncryptedMessage{
WgPubKey: c.key.PublicKey().String(),
Body: encReq,
})
if err != nil {
return nil, err
}
setupResp := &proto.AgentNetworkSetupResponse{}
if err := encryption.DecryptMessage(*serverPubKey, c.key, resp.Body, setupResp); err != nil {
return nil, fmt.Errorf("decrypt agent network setup response: %w", err)
}
return setupResp, nil
}
func fromProtoExposeResponse(resp *proto.ExposeServiceResponse) *ExposeResponse {
return &ExposeResponse{
ServiceName: resp.ServiceName,
+8
View File
@@ -25,6 +25,7 @@ type MockClient struct {
CreateExposeFunc func(ctx context.Context, req ExposeRequest) (*ExposeResponse, error)
RenewExposeFunc func(ctx context.Context, domain string) error
StopExposeFunc func(ctx context.Context, domain string) error
GetAgentNetworkSetupFunc func(ctx context.Context) (*proto.AgentNetworkSetupResponse, error)
}
func (m *MockClient) IsHealthy() bool {
@@ -136,3 +137,10 @@ func (m *MockClient) StopExpose(ctx context.Context, domain string) error {
}
return m.StopExposeFunc(ctx, domain)
}
func (m *MockClient) GetAgentNetworkSetup(ctx context.Context) (*proto.AgentNetworkSetupResponse, error) {
if m.GetAgentNetworkSetupFunc == nil {
return &proto.AgentNetworkSetupResponse{}, nil
}
return m.GetAgentNetworkSetupFunc(ctx)
}