Skip to content

Commit 67ed2fe

Browse files
committed
init
1 parent 4d83080 commit 67ed2fe

9 files changed

Lines changed: 618 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ go.work.sum
3030
# Editor/IDE
3131
# .idea/
3232
# .vscode/
33+
/examples/

cmd/root.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"time"
6+
7+
"github.com/spf13/cobra"
8+
)
9+
10+
const (
11+
defaultEndpoint = "codespaces-api.services.bitrise.io:443"
12+
envEndpoint = "BITRISE_CODESPACES_GRPC_ENDPOINT"
13+
envPAT = "BITRISE_PAT"
14+
)
15+
16+
var (
17+
flagEndpoint string
18+
flagInsecure bool
19+
flagTimeout time.Duration
20+
)
21+
22+
var rootCmd = &cobra.Command{
23+
Use: "ai-qa-agent-cli",
24+
Short: "CLI for driving Bitrise Remote Development Environment (RDE) sessions",
25+
SilenceUsage: true,
26+
SilenceErrors: true,
27+
}
28+
29+
func Execute() error {
30+
return rootCmd.Execute()
31+
}
32+
33+
func init() {
34+
endpointDefault := defaultEndpoint
35+
if v := os.Getenv(envEndpoint); v != "" {
36+
endpointDefault = v
37+
}
38+
rootCmd.PersistentFlags().StringVar(&flagEndpoint, "endpoint", endpointDefault, "Codespaces gRPC endpoint (host:port). Env: "+envEndpoint)
39+
rootCmd.PersistentFlags().BoolVar(&flagInsecure, "insecure", false, "Disable TLS (for local backend)")
40+
rootCmd.PersistentFlags().DurationVar(&flagTimeout, "timeout", 15*time.Minute, "Overall timeout for the command (covers create + wait)")
41+
42+
rootCmd.AddCommand(sessionCmd)
43+
}

cmd/session.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"path"
8+
"path/filepath"
9+
"strings"
10+
"time"
11+
12+
"github.com/bitrise-io/ai-qa-agent-cli/internal/codespaces"
13+
codespacesv1 "github.com/bitrise-io/bitrise-codespaces/backend/proto/codespaces/v1"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
const remotePathPlaceholder = "{{REMOTE_PATH}}"
18+
19+
var sessionCmd = &cobra.Command{
20+
Use: "session",
21+
Short: "Manage RDE sessions",
22+
}
23+
24+
var (
25+
createWorkspace string
26+
createTemplate string
27+
createName string
28+
createDescription string
29+
createInputs []string
30+
createSecretInputs []string
31+
createSavedInputs []string
32+
createFeatureFlags []string
33+
createCluster string
34+
createAIPrompt string
35+
createAutoTerminateMinutes int32
36+
createMapSavedInputs bool
37+
createWait bool
38+
createPollInterval time.Duration
39+
createOpenRemoteAccess bool
40+
createUpload string
41+
createUploadDestination string
42+
)
43+
44+
var sessionCreateCmd = &cobra.Command{
45+
Use: "create",
46+
Short: "Create a new RDE session and (optionally) wait for it to be running",
47+
RunE: runSessionCreate,
48+
}
49+
50+
func init() {
51+
sessionCmd.AddCommand(sessionCreateCmd)
52+
53+
f := sessionCreateCmd.Flags()
54+
f.StringVarP(&createWorkspace, "workspace", "w", "", "Workspace ID (required)")
55+
f.StringVarP(&createTemplate, "template", "t", "", "Template ID (required)")
56+
f.StringVar(&createName, "name", "", "Session name (required)")
57+
f.StringVar(&createDescription, "description", "", "Session description")
58+
f.StringArrayVar(&createInputs, "input", nil, "Session input as key=value (repeatable)")
59+
f.StringArrayVar(&createSecretInputs, "secret-input", nil, "Secret session input as key=value (repeatable)")
60+
f.StringArrayVar(&createSavedInputs, "saved-input", nil, "Saved input reference as key=savedInputID (repeatable)")
61+
f.StringArrayVar(&createFeatureFlags, "feature-flag", nil, "Feature flag name to enable (repeatable)")
62+
f.StringVar(&createCluster, "cluster", "", "Target cluster name (only required when image+machine-type matches multiple clusters)")
63+
f.StringVar(&createAIPrompt, "ai-prompt", "", "AI prompt to pass to Claude Code on session start. "+
64+
"Any "+remotePathPlaceholder+" is substituted with the remote path of --upload. "+
65+
"Note: the binary is uploaded AFTER the session reaches RUNNING, so phrase the prompt to wait for the file "+
66+
"(e.g. 'Wait until "+remotePathPlaceholder+" exists, then run it and report output').")
67+
f.StringVar(&createUpload, "upload", "", "Local file to upload to the session after it reaches RUNNING")
68+
f.StringVar(&createUploadDestination, "upload-destination", "/tmp", "Absolute remote directory the --upload file is extracted into")
69+
f.Int32Var(&createAutoTerminateMinutes, "auto-terminate-minutes", -1, "Minutes before auto-termination (0 disables; -1 leaves backend default)")
70+
f.BoolVar(&createMapSavedInputs, "map-saved-inputs", false, "Auto-fill template session inputs from caller's saved inputs")
71+
f.BoolVar(&createWait, "wait", true, "Poll until session reaches RUNNING")
72+
f.DurationVar(&createPollInterval, "poll-interval", 5*time.Second, "Status poll interval when --wait is set")
73+
f.BoolVar(&createOpenRemoteAccess, "open-remote-access", false, "After RUNNING, call OpenRemoteAccess and print SSH/VNC details")
74+
75+
_ = sessionCreateCmd.MarkFlagRequired("workspace")
76+
_ = sessionCreateCmd.MarkFlagRequired("template")
77+
_ = sessionCreateCmd.MarkFlagRequired("name")
78+
}
79+
80+
func runSessionCreate(cmd *cobra.Command, _ []string) error {
81+
pat := os.Getenv(envPAT)
82+
if pat == "" {
83+
return fmt.Errorf("%s not set", envPAT)
84+
}
85+
86+
aiPrompt, remotePath, err := resolveUploadAndPrompt(createUpload, createUploadDestination, createAIPrompt)
87+
if err != nil {
88+
return err
89+
}
90+
91+
inputs, err := buildSessionInputs(createInputs, createSecretInputs, createSavedInputs)
92+
if err != nil {
93+
return err
94+
}
95+
96+
req := &codespacesv1.CreateSessionRequest{
97+
Name: createName,
98+
Description: createDescription,
99+
TemplateId: createTemplate,
100+
WorkspaceId: createWorkspace,
101+
SessionInputs: inputs,
102+
EnabledFeatureFlagNames: createFeatureFlags,
103+
Cluster: createCluster,
104+
AiPrompt: aiPrompt,
105+
MapSavedToSessionInputs: createMapSavedInputs,
106+
}
107+
if createAutoTerminateMinutes >= 0 {
108+
v := createAutoTerminateMinutes
109+
req.AutoTerminateMinutes = &v
110+
}
111+
112+
ctx, cancel := context.WithTimeout(cmd.Context(), flagTimeout)
113+
defer cancel()
114+
115+
client, err := codespaces.NewClient(flagEndpoint, pat, flagInsecure)
116+
if err != nil {
117+
return err
118+
}
119+
defer client.Close()
120+
121+
session, err := client.CreateSession(ctx, req)
122+
if err != nil {
123+
return fmt.Errorf("CreateSession: %w", err)
124+
}
125+
fmt.Fprintf(os.Stderr, "created session %s (status: %s)\n", session.GetId(), session.GetStatus())
126+
127+
if createWait {
128+
session, err = client.WaitForRunning(ctx, session.GetId(), createWorkspace, createPollInterval, func(s codespacesv1.SessionStatus) {
129+
fmt.Fprintf(os.Stderr, " status: %s\n", s)
130+
})
131+
if err != nil {
132+
return err
133+
}
134+
}
135+
136+
if createUpload != "" && session.GetStatus() == codespacesv1.SessionStatus_SESSION_STATUS_RUNNING {
137+
actualPath, err := client.UploadFile(ctx, session.GetId(), createWorkspace, createUpload, createUploadDestination)
138+
if err != nil {
139+
return fmt.Errorf("upload %s: %w", createUpload, err)
140+
}
141+
fmt.Fprintf(os.Stderr, "uploaded %s -> %s\n", createUpload, actualPath)
142+
_ = remotePath // resolved earlier for the prompt; logged here from the server's confirmed path
143+
}
144+
145+
if createOpenRemoteAccess && session.GetStatus() == codespacesv1.SessionStatus_SESSION_STATUS_RUNNING {
146+
session, err = client.OpenRemoteAccess(ctx, session.GetId(), createWorkspace)
147+
if err != nil {
148+
return fmt.Errorf("OpenRemoteAccess: %w", err)
149+
}
150+
fmt.Fprintf(os.Stderr, "ssh: %s (password: %s)\n", session.GetSshAddress(), session.GetSshPassword())
151+
fmt.Fprintf(os.Stderr, "vnc: %s (user: %s, password: %s)\n", session.GetVncAddress(), session.GetVncUsername(), session.GetVncPassword())
152+
}
153+
154+
fmt.Println(session.GetId())
155+
return nil
156+
}
157+
158+
// resolveUploadAndPrompt validates the upload flags against the prompt placeholder
159+
// and returns the (possibly substituted) prompt plus the resolved remote path.
160+
// remotePath is empty when --upload is not set.
161+
func resolveUploadAndPrompt(uploadLocal, uploadDest, prompt string) (string, string, error) {
162+
hasPlaceholder := strings.Contains(prompt, remotePathPlaceholder)
163+
164+
if uploadLocal == "" {
165+
if hasPlaceholder {
166+
return "", "", fmt.Errorf("--ai-prompt contains %s but --upload is not set", remotePathPlaceholder)
167+
}
168+
return prompt, "", nil
169+
}
170+
171+
if !path.IsAbs(uploadDest) {
172+
return "", "", fmt.Errorf("--upload-destination must be absolute, got %q", uploadDest)
173+
}
174+
175+
stat, err := os.Stat(uploadLocal)
176+
if err != nil {
177+
return "", "", fmt.Errorf("--upload %s: %w", uploadLocal, err)
178+
}
179+
if stat.IsDir() {
180+
return "", "", fmt.Errorf("--upload %s: must be a file, not a directory", uploadLocal)
181+
}
182+
183+
remote := path.Join(uploadDest, filepath.Base(uploadLocal))
184+
if hasPlaceholder {
185+
prompt = strings.ReplaceAll(prompt, remotePathPlaceholder, remote)
186+
} else if prompt != "" {
187+
fmt.Fprintf(os.Stderr, "warning: --ai-prompt does not reference %s; ensure the prompt knows the file's path (%s)\n", remotePathPlaceholder, remote)
188+
}
189+
return prompt, remote, nil
190+
}
191+
192+
func buildSessionInputs(plain, secret, saved []string) ([]*codespacesv1.SessionInputValue, error) {
193+
out := make([]*codespacesv1.SessionInputValue, 0, len(plain)+len(secret)+len(saved))
194+
195+
for _, kv := range plain {
196+
k, v, ok := strings.Cut(kv, "=")
197+
if !ok {
198+
return nil, fmt.Errorf("--input %q: expected key=value", kv)
199+
}
200+
out = append(out, &codespacesv1.SessionInputValue{Key: k, Value: v})
201+
}
202+
for _, kv := range secret {
203+
k, v, ok := strings.Cut(kv, "=")
204+
if !ok {
205+
return nil, fmt.Errorf("--secret-input %q: expected key=value", kv)
206+
}
207+
out = append(out, &codespacesv1.SessionInputValue{Key: k, Value: v, IsSecret: true})
208+
}
209+
for _, kv := range saved {
210+
k, id, ok := strings.Cut(kv, "=")
211+
if !ok {
212+
return nil, fmt.Errorf("--saved-input %q: expected key=savedInputID", kv)
213+
}
214+
out = append(out, &codespacesv1.SessionInputValue{Key: k, SavedInputId: id})
215+
}
216+
return out, nil
217+
}

go.mod

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module github.com/bitrise-io/ai-qa-agent-cli
2+
3+
go 1.26.2
4+
5+
require (
6+
github.com/bitrise-io/bitrise-codespaces/backend v0.0.0-20260504130057-ba324a92da8c
7+
github.com/spf13/cobra v1.10.2
8+
google.golang.org/grpc v1.81.0
9+
)
10+
11+
require (
12+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
13+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
14+
github.com/spf13/pflag v1.0.10 // indirect
15+
golang.org/x/net v0.53.0 // indirect
16+
golang.org/x/sys v0.43.0 // indirect
17+
golang.org/x/text v0.36.0 // indirect
18+
google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 // indirect
19+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect
20+
google.golang.org/protobuf v1.36.11 // indirect
21+
)

go.sum

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
github.com/bitrise-io/bitrise-codespaces/backend v0.0.0-20260504130057-ba324a92da8c h1:dsZNyHDhMWH176kWGwCNH7/bZ/r3CT9Cd0oQavvp7fo=
2+
github.com/bitrise-io/bitrise-codespaces/backend v0.0.0-20260504130057-ba324a92da8c/go.mod h1:byAJeoz+TQErjY2M2k3+BdtVTbI7cB7rDJ3/JV8uAYw=
3+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
4+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
6+
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
7+
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
8+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
9+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
10+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
11+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
12+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
13+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
14+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
15+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
16+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
17+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
18+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
19+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
20+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
21+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
22+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
23+
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
24+
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
25+
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
26+
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
27+
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
28+
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
29+
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
30+
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
31+
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
32+
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
33+
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
34+
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
35+
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
36+
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
37+
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
38+
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
39+
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
40+
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
41+
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
42+
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
43+
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
44+
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
45+
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
46+
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
47+
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
48+
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
49+
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
50+
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
51+
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
52+
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
53+
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
54+
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
55+
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
56+
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
57+
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
58+
google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 h1:U8orV30l6KpDsi9dxU0CoJZGbjS8EEpw+6ba+XwGPQA=
59+
google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348/go.mod h1:Yzdzr5OOZFgSsEV2D/Xi9NL3bszpXFAg0hFJiRohcD8=
60+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
61+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
62+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE=
63+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
64+
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
65+
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
66+
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
67+
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
68+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

0 commit comments

Comments
 (0)