Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use real pty #197

Merged
merged 38 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0b36a69
feat: use pty
caarlos0 Dec 4, 2023
aa0bb66
fix: deps
caarlos0 Dec 4, 2023
fc89255
fix: deps
caarlos0 Dec 4, 2023
0868b14
fix: update example
caarlos0 Dec 4, 2023
46447ee
fix: deps
caarlos0 Dec 4, 2023
9ba7e36
docs: fix example
caarlos0 Dec 5, 2023
ab27fe2
feat: pass lipgloss.Renderer down to the tea.App
caarlos0 Dec 8, 2023
020651e
Merge remote-tracking branch 'origin/main' into using-pty
caarlos0 Dec 8, 2023
c6e19fc
Merge remote-tracking branch 'origin/main' into using-pty
caarlos0 Dec 12, 2023
a70d0bd
fix: go mod tidy
caarlos0 Dec 12, 2023
fd4337e
improvements
caarlos0 Jan 4, 2024
488fd2f
Merge remote-tracking branch 'origin/main' into using-pty
caarlos0 Jan 4, 2024
c9ad8ee
Merge remote-tracking branch 'origin/main' into using-pty
caarlos0 Jan 5, 2024
83cc777
fix: pty
caarlos0 Jan 5, 2024
60e8254
fix: better diff
caarlos0 Jan 5, 2024
2cf714d
chore: typo
caarlos0 Jan 5, 2024
59c5884
fix: godocs
caarlos0 Jan 5, 2024
9ab015a
fix: allocate pty on macos
caarlos0 Jan 8, 2024
50a706e
fix: improvements
caarlos0 Jan 8, 2024
411f301
chore: godoc
caarlos0 Jan 8, 2024
9d68adf
Merge remote-tracking branch 'origin/main' into using-pty
caarlos0 Jan 9, 2024
47074ac
fix: review
caarlos0 Jan 9, 2024
5e7f41a
fix: example
caarlos0 Jan 9, 2024
4c7fb4f
fix: tea program handler
caarlos0 Jan 9, 2024
0431178
fix: examples
caarlos0 Jan 9, 2024
aed4f59
Merge remote-tracking branch 'origin/main' into using-pty
caarlos0 Jan 9, 2024
c40b251
Merge remote-tracking branch 'origin/main' into using-pty
caarlos0 Jan 9, 2024
d7ee1ec
refactor: improve p!=nil handling
caarlos0 Jan 9, 2024
c0d4269
fix: ensure session envs are available to renderer (#223)
aymanbagabas Jan 9, 2024
73189b2
fix: rename func to makeoptions
caarlos0 Jan 10, 2024
569d089
fix: not too much breaking
caarlos0 Jan 10, 2024
b0c56ba
chore: fix gitignore
caarlos0 Jan 10, 2024
c9668e0
Merge remote-tracking branch 'origin/main' into using-pty
caarlos0 Jan 17, 2024
91c322b
fix: dep
caarlos0 Jan 17, 2024
cd60d40
fix: update charmbracelet/ssh
aymanbagabas Jan 18, 2024
12a7a6a
fix: update dep
caarlos0 Jan 18, 2024
731c66e
chore: go mod tidy
caarlos0 Jan 18, 2024
20ca97a
chore: update example
caarlos0 Jan 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ coverage.txt

# MacOS specific
.DS_Store
id_ed25519
id_ed25519.pub
134 changes: 72 additions & 62 deletions bubbletea/tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bubbletea

import (
"context"
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
Expand All @@ -12,18 +13,6 @@ import (
"github.com/muesli/termenv"
)

// BubbleTeaHandler is the function Bubble Tea apps implement to hook into the
// SSH Middleware. This will create a new tea.Program for every connection and
// start it with the tea.ProgramOptions returned.
//
// Deprecated: use Handler instead.
type BubbleTeaHandler = Handler // nolint: revive

// Handler is the function Bubble Tea apps implement to hook into the
// SSH Middleware. This will create a new tea.Program for every connection and
// start it with the tea.ProgramOptions returned.
type Handler func(ssh.Session) (tea.Model, []tea.ProgramOption)

// ProgramHandler is the function Bubble Tea apps implement to hook into the SSH
// Middleware. This should return a new tea.Program. This handler is different
// from the default handler in that it returns a tea.Program instead of
Expand All @@ -33,73 +22,94 @@ type Handler func(ssh.Session) (tea.Model, []tea.ProgramOption)
// otherwise the program will not function properly.
type ProgramHandler func(ssh.Session) *tea.Program

// Middleware takes a Handler and hooks the input and output for the
// ssh.Session into the tea.Program. It also captures window resize events and
// sends them to the tea.Program as tea.WindowSizeMsgs. By default a 256 color
// profile will be used when rendering with Lip Gloss.
func Middleware(bth Handler) wish.Middleware {
return MiddlewareWithColorProfile(bth, termenv.ANSI256)
// Handler is the function Bubble Tea apps implement to hook into the
// SSH Middleware. This will create a new tea.Program for every connection and
// start it with the tea.ProgramOptions returned.
type Handler func(ssh.Session) (tea.Model, []tea.ProgramOption)

// MakeRenderer returns a lipgloss renderer for the current session.
// This function handle PTYs as well, and should be used to style your application.
func MakeRenderer(s ssh.Session) *lipgloss.Renderer {
return newRenderer(s)
}

// MiddlewareWithColorProfile allows you to specify the number of colors
// returned by the server when using Lip Gloss. The number of colors supported
// by an SSH client's terminal cannot be detected by the server but this will
// allow for manually setting the color profile on all SSH connections.
func MiddlewareWithColorProfile(bth Handler, cp termenv.Profile) wish.Middleware {
// MakePTYAwareOpts returns tea.WithInput and tea.WithOutput taking into
// account Emulated and Allocated PTYs.
func MakePTYAwareOpts(s ssh.Session) []tea.ProgramOption {
aymanbagabas marked this conversation as resolved.
Show resolved Hide resolved
return makeOpts(s)
}

// Middleware takes a Handler and hooks the input and output for the
// ssh.Session into the tea.Program.
//
// It also captures window resize events and sends them to the tea.Program
// as tea.WindowSizeMsgs.
func Middleware(bth Handler) wish.Middleware {
h := func(s ssh.Session) *tea.Program {
m, opts := bth(s)
if m == nil {
return nil
}
opts = append(opts, tea.WithInput(s), tea.WithOutput(s))
return tea.NewProgram(m, opts...)
return tea.NewProgram(m, append(opts, makeOpts(s)...)...)
}
return MiddlewareWithProgramHandler(h, cp)
return MiddlewareWithProgramHandler(h)
}

// MiddlewareWithProgramHandler allows you to specify the ProgramHandler to be
// able to access the underlying tea.Program. This is useful for creating custom
// middlewars that need access to tea.Program for instance to use p.Send() to
// send messages to tea.Program.
//
// Make sure to set the tea.WithInput and tea.WithOutput to the ssh.Session
// otherwise the program will not function properly.
func MiddlewareWithProgramHandler(bth ProgramHandler, cp termenv.Profile) wish.Middleware {
// XXX: This is a hack to make sure the default Termenv output color
// profile is set before the program starts. Ideally, we want a Lip Gloss
// renderer per session.
lipgloss.SetColorProfile(cp)
return func(sh ssh.Handler) ssh.Handler {
func MiddlewareWithProgramHandler(bth ProgramHandler) wish.Middleware {
return func(h ssh.Handler) ssh.Handler {
return func(s ssh.Session) {
_, windowChanges, ok := s.Pty()
if !ok {
wish.Fatalln(s, "no active terminal, skipping")
return
}
p := bth(s)
if p != nil {
_, windowChanges, _ := s.Pty()
ctx, cancel := context.WithCancel(s.Context())
go func() {
for {
select {
case <-ctx.Done():
if p != nil {
p.Quit()
return
}
case w := <-windowChanges:
if p != nil {
p.Send(tea.WindowSizeMsg{Width: w.Width, Height: w.Height})
}
}
if p == nil {
h(s)
return
}
ctx, cancel := context.WithCancel(s.Context())
go func() {
for {
select {
case <-ctx.Done():
p.Quit()
return
case w := <-windowChanges:
p.Send(tea.WindowSizeMsg{Width: w.Width, Height: w.Height})
}
}()
if _, err := p.Run(); err != nil {
log.Error("app exit with error", "error", err)
}
// p.Kill() will force kill the program if it's still running,
// and restore the terminal to its original state in case of a
// tui crash
p.Kill()
cancel()
}()
if _, err := p.Run(); err != nil {
log.Error("app exit with error", "error", err)
}
sh(s)
// p.Kill() will force kill the program if it's still running,
// and restore the terminal to its original state in case of a
// tui crash
p.Kill()
cancel()
h(s)
}
}
}

type sshEnviron []string

var _ termenv.Environ = sshEnviron(nil)

// Environ implements termenv.Environ.
func (e sshEnviron) Environ() []string {
return e
}

// Getenv implements termenv.Environ.
func (e sshEnviron) Getenv(k string) string {
for _, v := range e {
if strings.HasPrefix(v, k+"=") {
return v[len(k)+1:]
}
}
return ""
}
24 changes: 24 additions & 0 deletions bubbletea/tea_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//go:build !linux && !darwin && !freebsd && !dragonfly && !netbsd && !openbsd && !solaris
// +build !linux,!darwin,!freebsd,!dragonfly,!netbsd,!openbsd,!solaris

package bubbletea

import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/ssh"
"github.com/muesli/termenv"
)

func makeOpts(s ssh.Session) []tea.ProgramOption {
return []tea.ProgramOption{
tea.WithInput(s),
tea.WithOutput(s),
}
}

func newRenderer(s ssh.Session) *lipgloss.Renderer {
pty, _, _ := s.Pty()
env := sshEnviron(append(s.Environ(), "TERM="+pty.Term))
return lipgloss.NewRenderer(s, termenv.WithEnvironment(env), termenv.WithUnsafe(), termenv.WithColorCache(true))
}
35 changes: 35 additions & 0 deletions bubbletea/tea_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris

package bubbletea

import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/ssh"
"github.com/muesli/termenv"
)

func makeOpts(s ssh.Session) []tea.ProgramOption {
pty, _, ok := s.Pty()
if !ok || s.EmulatedPty() {
return []tea.ProgramOption{
tea.WithInput(s),
tea.WithOutput(s),
}
}

return []tea.ProgramOption{
tea.WithInput(pty.Slave),
tea.WithOutput(pty.Slave),
}
}

func newRenderer(s ssh.Session) *lipgloss.Renderer {
pty, _, ok := s.Pty()
env := sshEnviron(append(s.Environ(), "TERM="+pty.Term))
if !ok || pty.Slave == nil {
return lipgloss.NewRenderer(s, termenv.WithEnvironment(env), termenv.WithUnsafe(), termenv.WithColorCache(true))
}
return lipgloss.NewRenderer(pty.Slave, termenv.WithEnvironment(env), termenv.WithColorCache(true))
}
2 changes: 2 additions & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id_ed25519*
file.txt
5 changes: 2 additions & 3 deletions examples/bubbleteaprogram/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/charmbracelet/wish"
bm "github.com/charmbracelet/wish/bubbletea"
lm "github.com/charmbracelet/wish/logging"
"github.com/muesli/termenv"
)

const (
Expand Down Expand Up @@ -83,9 +82,9 @@ func myCustomBubbleteaMiddleware() wish.Middleware {
height: pty.Window.Height,
time: time.Now(),
}
return newProg(m, tea.WithInput(s), tea.WithOutput(s), tea.WithAltScreen())
return newProg(m, append(bm.MakePTYAwareOpts(s), tea.WithAltScreen())...)
}
return bm.MiddlewareWithProgramHandler(teaHandler, termenv.ANSI256)
return bm.MiddlewareWithProgramHandler(teaHandler)
}

// Just a generic tea.Model to demo terminal information of ssh.
Expand Down
12 changes: 7 additions & 5 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ require (
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.9.1
github.com/charmbracelet/log v0.3.1
github.com/charmbracelet/ssh v0.0.0-20240104172912-e11ae277b249
github.com/charmbracelet/ssh v0.0.0-20240108200552-7ffdd484aa1b
github.com/charmbracelet/wish v0.5.0
github.com/muesli/termenv v0.15.2
github.com/spf13/cobra v1.7.0
)

Expand All @@ -23,6 +22,7 @@ require (
github.com/charmbracelet/keygen v0.5.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/creack/pty v1.1.21 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
Expand All @@ -40,19 +40,21 @@ require (
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/u-root/u-root v0.11.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.14.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
Expand Down
21 changes: 13 additions & 8 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw=
github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g=
github.com/charmbracelet/ssh v0.0.0-20240104172912-e11ae277b249 h1:M1Q/UIbi9Cfla0HK3A9puhQhb8ZPkA5HQxeSYcVrGpo=
github.com/charmbracelet/ssh v0.0.0-20240104172912-e11ae277b249/go.mod h1:A1H384KV/cJcSKofWjdSIb+dfbikXiW6449EluL3qJI=
github.com/charmbracelet/ssh v0.0.0-20240108200552-7ffdd484aa1b h1:VPRNMDlAOQeptDNA7u3ci897ApTY7ay/aaImni+4f8U=
github.com/charmbracelet/ssh v0.0.0-20240108200552-7ffdd484aa1b/go.mod h1:H/cBXf4vsbGUlCLiR8cNGP7lpv8wuHbgD3GnWJ8quHA=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -106,6 +108,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90 h1:zTk5683I9K62wtZ6eUa6vu6IWwVHXPnoKK5n2unAwv0=
github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8=
github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand All @@ -114,8 +119,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
Expand Down Expand Up @@ -150,15 +155,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand Down
Loading
Loading