Skip to content

Commit 45820dd

Browse files
committed
implement the attach command
1 parent 806ff7c commit 45820dd

10 files changed

Lines changed: 401 additions & 13 deletions

File tree

cmd/attach/attach.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package attach
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"io"
10+
"net/http"
11+
"os"
12+
"strings"
13+
"time"
14+
15+
tea "github.com/charmbracelet/bubbletea"
16+
"github.com/coder/agentapi/lib/httpapi"
17+
"github.com/spf13/cobra"
18+
sse "github.com/tmaxmax/go-sse"
19+
"golang.org/x/term"
20+
"golang.org/x/xerrors"
21+
)
22+
23+
type ChannelWriter struct {
24+
ch chan []byte
25+
}
26+
27+
func (c *ChannelWriter) Write(p []byte) (n int, err error) {
28+
c.ch <- p
29+
return len(p), nil
30+
}
31+
32+
func (c *ChannelWriter) Receive() ([]byte, bool) {
33+
data, ok := <-c.ch
34+
return data, ok
35+
}
36+
37+
type model struct {
38+
screen string
39+
}
40+
41+
func (m model) Init() tea.Cmd {
42+
// Just return `nil`, which means "no I/O right now, please."
43+
return nil
44+
}
45+
46+
type screenMsg struct {
47+
screen string
48+
}
49+
50+
type finishMsg struct{}
51+
52+
//lint:ignore U1000 The Update function is used by the Bubble Tea framework
53+
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
54+
switch msg := msg.(type) {
55+
case screenMsg:
56+
m.screen = msg.screen
57+
if m.screen != "" && m.screen[len(m.screen)-1] != '\n' {
58+
m.screen += "\n"
59+
}
60+
case tea.KeyMsg:
61+
if msg.String() == "ctrl+c" {
62+
return m, tea.Quit
63+
}
64+
case finishMsg:
65+
return m, tea.Quit
66+
}
67+
68+
return m, nil
69+
}
70+
71+
func (m model) View() string {
72+
return m.screen
73+
}
74+
75+
func ReadScreenOverHTTP(ctx context.Context, url string, ch chan<- httpapi.ScreenUpdateBody) error {
76+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
77+
req.Header.Set("Content-Type", "application/json")
78+
79+
res, err := http.DefaultClient.Do(req)
80+
if err != nil {
81+
return xerrors.Errorf("failed to do request: %w", err)
82+
}
83+
defer res.Body.Close()
84+
85+
for ev, err := range sse.Read(res.Body, &sse.ReadConfig{
86+
// 256KB: screen can be big. The default terminal size is 80x1000,
87+
// which can be over 80000 bytes.
88+
MaxEventSize: 256 * 1024,
89+
}) {
90+
if err != nil {
91+
return xerrors.Errorf("failed to read sse: %w", err)
92+
}
93+
var screen httpapi.ScreenUpdateBody
94+
if err := json.Unmarshal([]byte(ev.Data), &screen); err != nil {
95+
return xerrors.Errorf("failed to unmarshal screen: %w", err)
96+
}
97+
ch <- screen
98+
}
99+
return nil
100+
}
101+
102+
func WriteRawInputOverHTTP(ctx context.Context, url string, msg string) error {
103+
messageRequest := httpapi.MessageRequestBody{
104+
Type: httpapi.MessageTypeRaw,
105+
Content: msg,
106+
}
107+
messageRequestBytes, err := json.Marshal(messageRequest)
108+
if err != nil {
109+
return xerrors.Errorf("failed to marshal message request: %w", err)
110+
}
111+
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(messageRequestBytes))
112+
req.Header.Set("Content-Type", "application/json")
113+
114+
res, err := http.DefaultClient.Do(req)
115+
if err != nil {
116+
return xerrors.Errorf("failed to do request: %w", err)
117+
}
118+
defer res.Body.Close()
119+
if res.StatusCode != http.StatusOK {
120+
return xerrors.Errorf("failed to write raw input: %w", errors.New(res.Status))
121+
}
122+
123+
return nil
124+
}
125+
126+
func runAttach(remoteUrl string) error {
127+
ctx, cancel := context.WithCancel(context.Background())
128+
defer cancel()
129+
stdin := int(os.Stdin.Fd())
130+
131+
oldState, err := term.MakeRaw(stdin)
132+
if err != nil {
133+
return xerrors.Errorf("failed to make raw: %w", err)
134+
}
135+
defer term.Restore(stdin, oldState)
136+
137+
stdinWriter := &ChannelWriter{
138+
ch: make(chan []byte, 4096),
139+
}
140+
tee := io.TeeReader(os.Stdin, stdinWriter)
141+
p := tea.NewProgram(model{}, tea.WithInput(tee), tea.WithAltScreen())
142+
screenCh := make(chan httpapi.ScreenUpdateBody, 64)
143+
144+
readScreenErrCh := make(chan error, 1)
145+
go func() {
146+
defer close(readScreenErrCh)
147+
if err := ReadScreenOverHTTP(ctx, remoteUrl+"/internal/screen", screenCh); err != nil {
148+
if errors.Is(err, context.Canceled) {
149+
return
150+
}
151+
readScreenErrCh <- xerrors.Errorf("failed to read screen: %w", err)
152+
}
153+
}()
154+
writeRawInputErrCh := make(chan error, 1)
155+
go func() {
156+
defer close(writeRawInputErrCh)
157+
for {
158+
select {
159+
case <-ctx.Done():
160+
return
161+
case buf, ok := <-stdinWriter.ch:
162+
if !ok {
163+
return
164+
}
165+
input := string(buf)
166+
// Don't send Ctrl+C to the agent
167+
if input == "\x03" {
168+
continue
169+
}
170+
if err := WriteRawInputOverHTTP(ctx, remoteUrl+"/message", input); err != nil {
171+
writeRawInputErrCh <- xerrors.Errorf("failed to write raw input: %w", err)
172+
return
173+
}
174+
}
175+
}
176+
}()
177+
go func() {
178+
for {
179+
select {
180+
case <-ctx.Done():
181+
return
182+
case screenUpdate, ok := <-screenCh:
183+
if !ok {
184+
return
185+
}
186+
p.Send(screenMsg{
187+
screen: screenUpdate.Screen,
188+
})
189+
}
190+
}
191+
}()
192+
pErrCh := make(chan error, 1)
193+
go func() {
194+
_, err := p.Run()
195+
pErrCh <- err
196+
close(pErrCh)
197+
}()
198+
199+
select {
200+
case err = <-readScreenErrCh:
201+
case err = <-writeRawInputErrCh:
202+
case err = <-pErrCh:
203+
case <-ctx.Done():
204+
err = nil
205+
}
206+
207+
p.Send(finishMsg{})
208+
select {
209+
case <-pErrCh:
210+
case <-time.After(1 * time.Second):
211+
}
212+
213+
return err
214+
}
215+
216+
var remoteUrlArg string
217+
218+
var AttachCmd = &cobra.Command{
219+
Use: "attach",
220+
Short: "Attach to a running agent",
221+
Long: `Attach to a running agent`,
222+
Run: func(cmd *cobra.Command, args []string) {
223+
remoteUrl := remoteUrlArg
224+
if remoteUrl == "" {
225+
fmt.Fprintln(os.Stderr, "URL is required")
226+
os.Exit(1)
227+
}
228+
if !strings.HasPrefix(remoteUrl, "http") {
229+
remoteUrl = "http://" + remoteUrl
230+
}
231+
remoteUrl = strings.TrimRight(remoteUrl, "/")
232+
if err := runAttach(remoteUrl); err != nil {
233+
fmt.Fprintf(os.Stderr, "Attach failed: %+v\n", err)
234+
os.Exit(1)
235+
}
236+
},
237+
}
238+
239+
func init() {
240+
AttachCmd.Flags().StringVarP(&remoteUrlArg, "url", "u", "localhost:3284", "URL of the agentapi server to attach to. May optionally include a protocol and a path.")
241+
}

cmd/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66

7+
"github.com/coder/agentapi/cmd/attach"
78
"github.com/coder/agentapi/cmd/server"
89
"github.com/spf13/cobra"
910
)
@@ -24,4 +25,5 @@ func Execute() {
2425

2526
func init() {
2627
rootCmd.AddCommand(server.ServerCmd)
28+
rootCmd.AddCommand(attach.AttachCmd)
2729
}

go.mod

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ go 1.23.2
44

55
require (
66
github.com/ActiveState/termtest/xpty v0.6.0
7+
github.com/charmbracelet/bubbletea v1.3.4
78
github.com/danielgtaylor/huma/v2 v2.32.0
89
github.com/go-chi/chi/v5 v5.2.1
910
github.com/go-chi/cors v1.2.1
1011
github.com/spf13/cobra v1.9.1
1112
github.com/stretchr/testify v1.10.0
13+
github.com/tmaxmax/go-sse v0.10.0
14+
golang.org/x/term v0.30.0
1215
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da
1316
)
1417

@@ -17,16 +20,30 @@ require (
1720
github.com/ActiveState/vt10x v1.3.1 // indirect
1821
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
1922
github.com/Netflix/go-expect v0.0.0-20200312175327-da48e75238e2 // indirect
23+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
24+
github.com/charmbracelet/lipgloss v1.0.0 // indirect
25+
github.com/charmbracelet/x/ansi v0.8.0 // indirect
26+
github.com/charmbracelet/x/term v0.2.1 // indirect
2027
github.com/creack/pty v1.1.24 // indirect
2128
github.com/davecgh/go-spew v1.1.1 // indirect
29+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
2230
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2331
github.com/kr/pty v1.1.8 // indirect
2432
github.com/kr/text v0.1.0 // indirect
33+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
34+
github.com/mattn/go-isatty v0.0.20 // indirect
35+
github.com/mattn/go-localereader v0.0.1 // indirect
36+
github.com/mattn/go-runewidth v0.0.16 // indirect
37+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
38+
github.com/muesli/cancelreader v0.2.2 // indirect
39+
github.com/muesli/termenv v0.15.2 // indirect
2540
github.com/pmezard/go-difflib v1.0.0 // indirect
41+
github.com/rivo/uniseg v0.4.7 // indirect
2642
github.com/rogpeppe/go-internal v1.14.1 // indirect
2743
github.com/spf13/pflag v1.0.6 // indirect
44+
golang.org/x/sync v0.11.0 // indirect
2845
golang.org/x/sys v0.31.0 // indirect
29-
golang.org/x/term v0.30.0 // indirect
46+
golang.org/x/text v0.21.0 // indirect
3047
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
3148
gopkg.in/yaml.v3 v3.0.1 // indirect
3249
)

go.sum

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ github.com/Netflix/go-expect v0.0.0-20200312175327-da48e75238e2 h1:y2avNRjCeJT8b
1111
github.com/Netflix/go-expect v0.0.0-20200312175327-da48e75238e2/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
1212
github.com/autarch/testify v1.2.2 h1:9Q9V6zqhP7R6dv+zRUddv6kXKLo6ecQhnFRFWM71i1c=
1313
github.com/autarch/testify v1.2.2/go.mod h1:oDbHKfFv2/D5UtVrxkk90OKcb6P4/AqF1Pcf6ZbvDQo=
14+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
15+
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
16+
github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
17+
github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
18+
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
19+
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
20+
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
21+
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
22+
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
23+
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
1424
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
1525
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
1626
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -21,6 +31,8 @@ github.com/danielgtaylor/huma/v2 v2.32.0/go.mod h1:9BxJwkeoPPDEJ2Bg4yPwL1mM1rYpA
2131
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2232
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2333
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
35+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
2436
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
2537
github.com/gdamore/tcell v1.0.1-0.20180608172421-b3cebc399d6f/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
2638
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
@@ -42,9 +54,26 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
4254
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
4355
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
4456
github.com/lucasb-eyer/go-colorful v0.0.0-20180526135729-345fbb3dbcdb/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
57+
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
58+
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
59+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
60+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
61+
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
62+
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
4563
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
64+
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
65+
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
66+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
67+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
68+
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
69+
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
70+
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
71+
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
4672
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4773
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
74+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
75+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
76+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
4877
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
4978
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
5079
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -59,21 +88,29 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
5988
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
6089
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
6190
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
91+
github.com/tmaxmax/go-sse v0.10.0 h1:j9F93WB4Hxt8wUf6oGffMm4dutALvUPoDDxfuDQOSqA=
92+
github.com/tmaxmax/go-sse v0.10.0/go.mod h1:u/2kZQR1tyngo1lKaNCj1mJmhXGZWS1Zs5yiSOD+Eg8=
6293
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
6394
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
6495
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
6596
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
6697
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
6798
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
99+
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
100+
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
68101
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
69102
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
70103
golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
71104
golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
105+
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
106+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
72107
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
73108
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
74109
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
75110
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
76111
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
112+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
113+
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
77114
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
78115
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
79116
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=

0 commit comments

Comments
 (0)