Skip to content

Commit ba718f5

Browse files
Add daemon command and gRPC list support
1 parent e8af622 commit ba718f5

File tree

11 files changed

+744
-15
lines changed

11 files changed

+744
-15
lines changed

Taskfile.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,19 @@ tasks:
126126
general:prepare-deps:
127127
desc: Prepare project dependencies for license check
128128
# No preparation is needed for Go module-based projects.
129+
130+
protoc:compile:
131+
desc: Compile protobuf definitions
132+
cmds:
133+
- buf dep update
134+
- buf generate
135+
136+
protoc:check:
137+
desc: Perform linting of the protobuf definitions
138+
cmds:
139+
- buf lint
140+
141+
protoc:format:
142+
desc: Perform formatting of the protobuf definitions
143+
cmds:
144+
- buf format --write

buf.gen.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: v2
2+
plugins:
3+
# Use protoc-gen-go
4+
- remote: buf.build/protocolbuffers/go:v1.34.2
5+
out: ./rpc
6+
opt:
7+
- paths=source_relative
8+
# Use of protoc-gen-go-grpc
9+
- remote: buf.build/grpc/go:v1.5.1
10+
out: ./rpc
11+
opt:
12+
- paths=source_relative
13+
inputs:
14+
- directory: ./rpc
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// This file is part of arduino-flasher-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-flasher-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package daemon
17+
18+
import (
19+
"encoding/json"
20+
"errors"
21+
"fmt"
22+
"net"
23+
"os"
24+
"strings"
25+
"syscall"
26+
27+
"github.com/arduino/arduino-flasher-cli/cmd/feedback"
28+
"github.com/arduino/arduino-flasher-cli/cmd/i18n"
29+
"github.com/spf13/cobra"
30+
"google.golang.org/grpc"
31+
32+
rpc "github.com/arduino/arduino-flasher-cli/rpc/cc/arduino/flasher/cli/commands/v1"
33+
)
34+
35+
func NewDaemonCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
36+
var daemonPort string
37+
var maxGRPCRecvMsgSize int
38+
daemonCommand := &cobra.Command{
39+
Use: "daemon",
40+
Short: i18n.Tr("Run the Arduino Flasher CLI as a gRPC daemon."),
41+
Example: " " + os.Args[0] + " daemon",
42+
Args: cobra.NoArgs,
43+
Run: func(cmd *cobra.Command, args []string) {
44+
runDaemonCommand(srv, daemonPort, maxGRPCRecvMsgSize)
45+
},
46+
}
47+
48+
daemonCommand.Flags().StringVar(&daemonPort,
49+
"port", "50051",
50+
i18n.Tr("The TCP port the daemon will listen to"))
51+
52+
daemonCommand.Flags().IntVar(&maxGRPCRecvMsgSize,
53+
"max-grpc-recv-message-size", 16*1024*1024,
54+
i18n.Tr("Sets the maximum message size in bytes the daemon can receive"))
55+
56+
return daemonCommand
57+
}
58+
59+
func runDaemonCommand(srv rpc.ArduinoCoreServiceServer, daemonPort string, maxGRPCRecvMsgSize int) {
60+
gRPCOptions := []grpc.ServerOption{}
61+
gRPCOptions = append(gRPCOptions, grpc.MaxRecvMsgSize(maxGRPCRecvMsgSize))
62+
s := grpc.NewServer(gRPCOptions...)
63+
64+
// register the commands service
65+
rpc.RegisterArduinoCoreServiceServer(s, srv)
66+
67+
daemonIP := "127.0.0.1"
68+
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", daemonIP, daemonPort))
69+
if err != nil {
70+
// Invalid port, such as "Foo"
71+
var dnsError *net.DNSError
72+
if errors.As(err, &dnsError) {
73+
feedback.Fatal(i18n.Tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name.", daemonPort, dnsError.Name), feedback.ErrBadTCPPortArgument)
74+
}
75+
// Invalid port number, such as -1
76+
var addrError *net.AddrError
77+
if errors.As(err, &addrError) {
78+
feedback.Fatal(i18n.Tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port.", daemonPort, addrError.Addr), feedback.ErrBadTCPPortArgument)
79+
}
80+
// Port is already in use
81+
var syscallErr *os.SyscallError
82+
if errors.As(err, &syscallErr) && errors.Is(syscallErr.Err, syscall.EADDRINUSE) {
83+
feedback.Fatal(i18n.Tr("Failed to listen on TCP port: %s. Address already in use.", daemonPort), feedback.ErrFailedToListenToTCPPort)
84+
}
85+
feedback.Fatal(i18n.Tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v", daemonPort, err), feedback.ErrFailedToListenToTCPPort)
86+
}
87+
88+
// We need to retrieve the port used only if the user did not specify it
89+
// and let the OS choose it randomly, in all other cases we already know
90+
// which port is used.
91+
if daemonPort == "0" {
92+
address := lis.Addr()
93+
split := strings.Split(address.String(), ":")
94+
95+
if len(split) <= 1 {
96+
feedback.Fatal(i18n.Tr("Invalid TCP address: port is missing"), feedback.ErrBadTCPPortArgument)
97+
}
98+
99+
daemonPort = split[1]
100+
}
101+
102+
feedback.PrintResult(daemonResult{
103+
IP: daemonIP,
104+
Port: daemonPort,
105+
})
106+
107+
if err := s.Serve(lis); err != nil {
108+
feedback.Fatal(fmt.Sprintf("Failed to serve: %v", err), feedback.ErrFailedToListenToTCPPort)
109+
}
110+
}
111+
112+
type daemonResult struct {
113+
IP string
114+
Port string
115+
}
116+
117+
func (r daemonResult) Data() interface{} {
118+
return r
119+
}
120+
121+
func (r daemonResult) String() string {
122+
j, _ := json.Marshal(r)
123+
return fmt.Sprintln(i18n.Tr("Daemon is now listening on %s:%s", r.IP, r.Port)) + fmt.Sprintln(string(j))
124+
}

cmd/arduino-flasher-cli/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,24 @@ import (
2323
"github.com/spf13/cobra"
2424
"go.bug.st/cleanup"
2525

26+
"github.com/arduino/arduino-flasher-cli/cmd/arduino-flasher-cli/daemon"
2627
"github.com/arduino/arduino-flasher-cli/cmd/arduino-flasher-cli/download"
2728
"github.com/arduino/arduino-flasher-cli/cmd/arduino-flasher-cli/drivers"
2829
"github.com/arduino/arduino-flasher-cli/cmd/arduino-flasher-cli/flash"
2930
"github.com/arduino/arduino-flasher-cli/cmd/arduino-flasher-cli/list"
3031
"github.com/arduino/arduino-flasher-cli/cmd/arduino-flasher-cli/version"
3132
"github.com/arduino/arduino-flasher-cli/cmd/feedback"
3233
"github.com/arduino/arduino-flasher-cli/cmd/i18n"
34+
"github.com/arduino/arduino-flasher-cli/commands"
3335
)
3436

3537
// Version will be set a build time with -ldflags
3638
var Version string = "0.0.0-dev"
3739
var format string
3840

3941
func main() {
42+
srv := commands.NewArduinoCoreServer()
43+
4044
rootCmd := &cobra.Command{
4145
Use: "arduino-flasher-cli",
4246
Short: "A CLI to update your Arduino UNO Q board, by downloading and flashing the latest Arduino Linux image",
@@ -61,6 +65,7 @@ func main() {
6165
list.NewListCmd(),
6266
download.NewDownloadCmd(),
6367
version.NewVersionCmd(Version),
68+
daemon.NewDaemonCommand(srv),
6469
)
6570

6671
ctx := context.Background()

commands/service.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// This file is part of arduino-flasher-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-flasher-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package commands
17+
18+
import rpc "github.com/arduino/arduino-flasher-cli/rpc/cc/arduino/flasher/cli/commands/v1"
19+
20+
type arduinoCoreServerImpl struct {
21+
rpc.UnsafeArduinoCoreServiceServer // Force compile error for unimplemented methods
22+
}
23+
24+
func NewArduinoCoreServer() rpc.ArduinoCoreServiceServer {
25+
return &arduinoCoreServerImpl{}
26+
}

commands/service_list.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// This file is part of arduino-flasher-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-flasher-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package commands
17+
18+
import (
19+
"context"
20+
21+
"github.com/arduino/arduino-flasher-cli/internal/updater"
22+
rpc "github.com/arduino/arduino-flasher-cli/rpc/cc/arduino/flasher/cli/commands/v1"
23+
)
24+
25+
func (s *arduinoCoreServerImpl) List(ctx context.Context, req *rpc.ListRequest) (*rpc.ListResponse, error) {
26+
client := updater.NewClient()
27+
28+
manifest, err := client.GetInfoManifest(ctx)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
resp := &rpc.ListResponse{}
34+
resp.Latest = manifest.Latest.Version
35+
for _, r := range manifest.Releases {
36+
resp.Releases = append(resp.Releases, &rpc.Release{BuildId: r.Version})
37+
}
38+
39+
return resp, nil
40+
}

go.mod

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ require (
1616
go.bug.st/cleanup v1.0.0
1717
go.bug.st/f v0.4.0
1818
go.bug.st/relaxed-semver v0.15.0
19+
google.golang.org/grpc v1.77.0
20+
google.golang.org/protobuf v1.36.10
1921
)
2022

2123
require (
@@ -71,12 +73,13 @@ require (
7173
github.com/xanzy/ssh-agent v0.3.3 // indirect
7274
github.com/yusufpapurcu/wmi v1.2.4 // indirect
7375
github.com/zeebo/xxh3 v1.0.2 // indirect
74-
golang.org/x/crypto v0.37.0 // indirect
75-
golang.org/x/net v0.39.0 // indirect
76-
golang.org/x/sync v0.16.0 // indirect
76+
golang.org/x/crypto v0.43.0 // indirect
77+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
78+
golang.org/x/sync v0.17.0 // indirect
7779
golang.org/x/sys v0.37.0 // indirect
78-
golang.org/x/term v0.35.0 // indirect
79-
golang.org/x/text v0.24.0 // indirect
80+
golang.org/x/term v0.36.0 // indirect
81+
golang.org/x/text v0.30.0 // indirect
82+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
8083
gopkg.in/warnings.v0 v0.1.2 // indirect
8184
gopkg.in/yaml.v3 v3.0.1 // indirect
8285
mvdan.cc/sh/v3 v3.12.0 // indirect

go.sum

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
6565
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
6666
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
6767
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
68+
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
69+
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
70+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
71+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
6872
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
6973
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
7074
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -78,6 +82,8 @@ github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviB
7882
github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc=
7983
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
8084
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
85+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
86+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
8187
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
8288
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
8389
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -181,16 +187,28 @@ go.bug.st/f v0.4.0 h1:Vstqb950nMA+PhAlRxUw8QL1ntHy/gXHNyyzjkQLJ10=
181187
go.bug.st/f v0.4.0/go.mod h1:bMo23205ll7UW63KwO1ut5RdlJ9JK8RyEEr88CmOF5Y=
182188
go.bug.st/relaxed-semver v0.15.0 h1:w37+SYQPxF53RQO7QZZuPIMaPouOifdaP0B1ktst2nA=
183189
go.bug.st/relaxed-semver v0.15.0/go.mod h1:bwHiCtYuD2m716tBk2OnOBjelsbXw9el5EIuyxT/ksU=
190+
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
191+
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
192+
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
193+
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
194+
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
195+
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
196+
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
197+
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
198+
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
199+
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
200+
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
201+
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
184202
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
185-
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
186-
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
203+
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
204+
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
187205
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
188206
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
189207
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
190-
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
191-
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
192-
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
193-
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
208+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
209+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
210+
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
211+
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
194212
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
195213
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
196214
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -206,12 +224,20 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
206224
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
207225
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
208226
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
209-
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
210-
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
227+
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
228+
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
211229
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
212-
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
213-
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
230+
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
231+
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
214232
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
233+
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
234+
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
235+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
236+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
237+
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
238+
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
239+
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
240+
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
215241
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
216242
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
217243
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

0 commit comments

Comments
 (0)