From 27fb50313404d47898e5541820791fedf4df787e Mon Sep 17 00:00:00 2001 From: Yoav Date: Wed, 18 Jun 2025 13:55:25 +0200 Subject: [PATCH 1/2] added an option to open login OTP with enter --- cmd/auth.go | 13 ++++++++++--- internal/util/browser.go | 42 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/cmd/auth.go b/cmd/auth.go index cf8c1d25..8940b4b6 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -71,18 +71,22 @@ Examples: tui.ShowWarning("Please re-run the login command to continue") os.Exit(1) } - + + authURL := fmt.Sprintf("%s/auth/cli", appUrl) + body := tui.Paragraph( "Copy the following code:", tui.Bold(otp), - "Then open the url in your browser and paste the code:", - tui.Link("%s/auth/cli", appUrl), + "Then open the url in your browser (Or just press ENTER) and paste the code:", + tui.Link(authURL), tui.Muted("This code will expire in 60 seconds"), ) tui.ShowBanner("Login to Agentuity", body, false) tui.ShowSpinner("Waiting for login to complete...", func() { + go util.PromptBrowserOpen(logger, authURL) + authResult, err := auth.PollForLoginCompletion(ctx, logger, apiUrl, otp) if err != nil { if isCancelled(ctx) { @@ -218,6 +222,9 @@ Examples: }, } + + + func init() { rootCmd.AddCommand(authCmd) authCmd.AddCommand(authLoginCmd) diff --git a/internal/util/browser.go b/internal/util/browser.go index 14130058..fc19b69b 100644 --- a/internal/util/browser.go +++ b/internal/util/browser.go @@ -1,6 +1,7 @@ package util import ( + "bufio" "context" "errors" "fmt" @@ -164,7 +165,7 @@ func BrowserFlow(opts BrowserFlowOptions) error { } if !skip { if berr := browser.OpenURL(u.String()); berr != nil { - returnErr = fmt.Errorf("failed to open browser: %s", err) + returnErr = fmt.Errorf("failed to open browser: %w", berr) return } } @@ -189,3 +190,42 @@ func BrowserFlow(opts BrowserFlowOptions) error { return returnErr } + +// PromptBrowserOpen prompts the user to press Enter to open a browser to the given URL. +// It handles display detection on Linux and provides appropriate user feedback. +func PromptBrowserOpen(logger interface{ Error(string, ...interface{}) }, url string) { + var skipOpen bool + if runtime.GOOS == "linux" { + // if we don't have a display, we can't open a browser most likely + if _, ok := os.LookupEnv("DISPLAY"); !ok { + skipOpen = true + } + } + + fmt.Println() + if skipOpen { + fmt.Print(tui.Secondary("Press Enter to continue, or Ctrl+C to skip: ")) + } else { + fmt.Print(tui.Secondary("Press Enter to open browser, or Ctrl+C to skip: ")) + } + + reader := bufio.NewReader(os.Stdin) + reader.ReadLine() + + if !skipOpen { + if err := browser.OpenURL(url); err != nil { + logger.Error("Failed to open browser: %v", err) + } else { + // Clear previous line and move cursor up to remove the "Press Enter..." prompt + fmt.Print("\r\033[K\033[A\r\033[K") + tui.ShowSuccess("Browser opened!") + fmt.Println() + } + } else { + // Clear the prompt and show the URL for manual opening (and the loading spinner) + fmt.Print("\r\033[K") + fmt.Println(tui.Muted("Please visit the URL manually:")) + fmt.Println(tui.Link(url)) + fmt.Println() + } +} From 8143943a774be787caa07fd1c3c32eaf85656e4d Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Wed, 18 Jun 2025 14:06:26 -0500 Subject: [PATCH 2/2] slight tweaks to make it even easier and faster --- cmd/auth.go | 24 ++++++++++++++++-------- go.mod | 10 +++++++--- go.sum | 20 ++++++++++++++------ internal/bundler/groq.go | 2 +- internal/util/browser.go | 4 ++-- 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/cmd/auth.go b/cmd/auth.go index 8940b4b6..e612c845 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -16,6 +16,7 @@ import ( "github.com/agentuity/go-common/tui" "github.com/spf13/cobra" "github.com/spf13/viper" + "golang.design/x/clipboard" ) var authCmd = &cobra.Command{ @@ -71,14 +72,24 @@ Examples: tui.ShowWarning("Please re-run the login command to continue") os.Exit(1) } - + authURL := fmt.Sprintf("%s/auth/cli", appUrl) - + + copyMessage := "Copy the following code:" + openMessage := "Then open the url in your browser (or press ENTER) and paste the code:" + + err := clipboard.Init() + if err == nil { + clipboard.Write(clipboard.FmtText, []byte(otp)) + copyMessage = "This code was copied to your clipboard:" + openMessage = "Open the url in your browser (or press ENTER) and paste the code from your clipboard:" + } + body := tui.Paragraph( - "Copy the following code:", + copyMessage, tui.Bold(otp), - "Then open the url in your browser (Or just press ENTER) and paste the code:", - tui.Link(authURL), + openMessage, + tui.Link("%s", authURL), tui.Muted("This code will expire in 60 seconds"), ) @@ -222,9 +233,6 @@ Examples: }, } - - - func init() { rootCmd.AddCommand(authCmd) authCmd.AddCommand(authLoginCmd) diff --git a/go.mod b/go.mod index 4575f358..d66ec9d2 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 github.com/zijiren233/yaml-comment v0.2.2 + golang.design/x/clipboard v0.7.1 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.32.1 @@ -56,6 +57,9 @@ require ( github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect + golang.org/x/image v0.28.0 // indirect + golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect golang.org/x/term v0.30.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) @@ -130,9 +134,9 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/net v0.38.0 - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.32.0 - golang.org/x/text v0.24.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 + golang.org/x/text v0.26.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/grpc v1.71.0 // indirect diff --git a/go.sum b/go.sum index 4133cfb0..919343ff 100644 --- a/go.sum +++ b/go.sum @@ -294,6 +294,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c= +golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -302,6 +304,12 @@ golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE= +golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8= +golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE= +golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY= +golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8= +golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -314,8 +322,8 @@ golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -329,16 +337,16 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 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= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/internal/bundler/groq.go b/internal/bundler/groq.go index b3358c61..8217a8cc 100644 --- a/internal/bundler/groq.go +++ b/internal/bundler/groq.go @@ -8,4 +8,4 @@ func init() { Before: generateEnvGuard("GROQ_API_KEY", generateGatewayEnvGuard("GROQ_API_KEY", "process.env.AGENTUITY_SDK_KEY", "GROQ_BASE_URL", "groq")), }, } -} \ No newline at end of file +} diff --git a/internal/util/browser.go b/internal/util/browser.go index fc19b69b..01227260 100644 --- a/internal/util/browser.go +++ b/internal/util/browser.go @@ -165,7 +165,7 @@ func BrowserFlow(opts BrowserFlowOptions) error { } if !skip { if berr := browser.OpenURL(u.String()); berr != nil { - returnErr = fmt.Errorf("failed to open browser: %w", berr) + returnErr = fmt.Errorf("failed to open browser: %w", berr) return } } @@ -208,7 +208,7 @@ func PromptBrowserOpen(logger interface{ Error(string, ...interface{}) }, url st } else { fmt.Print(tui.Secondary("Press Enter to open browser, or Ctrl+C to skip: ")) } - + reader := bufio.NewReader(os.Stdin) reader.ReadLine()