From 0d05c1f5920ba5378aec875b6c80f2f014dd997a Mon Sep 17 00:00:00 2001 From: Djordje Lukic Date: Fri, 31 Oct 2025 10:14:04 +0100 Subject: [PATCH] Add feedback and bug report commands in the command palette Signed-off-by: Djordje Lukic --- cmd/root/feedback.go | 5 ++--- cmd/root/root.go | 5 +++-- pkg/browser/browser.go | 34 ++++++++++++++++++++++++++++++++++ pkg/feedback/feedback.go | 3 +++ pkg/tools/mcp/oauth.go | 32 +++----------------------------- pkg/tui/commands/commands.go | 32 ++++++++++++++++++++++++++++++++ pkg/tui/tui.go | 5 +++++ 7 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 pkg/browser/browser.go create mode 100644 pkg/feedback/feedback.go diff --git a/cmd/root/feedback.go b/cmd/root/feedback.go index 172f397c9..e21f7b9e0 100644 --- a/cmd/root/feedback.go +++ b/cmd/root/feedback.go @@ -5,11 +5,10 @@ import ( "github.com/spf13/cobra" + "github.com/docker/cagent/pkg/feedback" "github.com/docker/cagent/pkg/telemetry" ) -var FeedbackLink = "https://docker.qualtrics.com/jfe/form/SV_cNsCIg92nQemlfw" - // NewFeedbackCmd creates a new feedback command func NewFeedbackCmd() *cobra.Command { return &cobra.Command{ @@ -19,7 +18,7 @@ func NewFeedbackCmd() *cobra.Command { Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { telemetry.TrackCommand("feedback", args) - fmt.Println("Feel free to give feedback:\n", FeedbackLink) + fmt.Println("Feel free to give feedback:\n", feedback.FeedbackLink) }, } } diff --git a/cmd/root/root.go b/cmd/root/root.go index e47330f8c..ae699793d 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/cobra" "github.com/docker/cagent/pkg/environment" + "github.com/docker/cagent/pkg/feedback" "github.com/docker/cagent/pkg/paths" "github.com/docker/cagent/pkg/telemetry" "github.com/docker/cagent/pkg/version" @@ -84,7 +85,7 @@ func NewRootCmd() *cobra.Command { }))) } if cmd.DisplayName() != "exec" && os.Getenv("CAGENT_HIDE_FEEDBACK_LINK") != "1" { - _, _ = cmd.OutOrStdout().Write([]byte("\nFor any feedback, please visit: " + FeedbackLink + "\n\n")) + _, _ = cmd.OutOrStdout().Write([]byte("\nFor any feedback, please visit: " + feedback.FeedbackLink + "\n\n")) } telemetry.SetGlobalTelemetryDebugMode(debugMode) @@ -146,7 +147,7 @@ For any feedback, please visit: %s We collect anonymous usage data to help improve cagent. To disable: - Set environment variable: TELEMETRY_ENABLED=false -`, FeedbackLink) +`, feedback.FeedbackLink) _, _ = os.Stderr.WriteString(startupMsg) } diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go new file mode 100644 index 000000000..4ea05923b --- /dev/null +++ b/pkg/browser/browser.go @@ -0,0 +1,34 @@ +package browser + +import ( + "context" + "fmt" + "os/exec" + "runtime" +) + +func Open(ctx context.Context, urlToOpen string) error { + var cmd string + var args []string + + switch runtime.GOOS { + case "windows": + cmd = "rundll32" + args = []string{"url.dll,FileProtocolHandler", urlToOpen} + case "darwin": + cmd = "open" + args = []string{urlToOpen} + case "linux": + cmd = "xdg-open" + args = []string{urlToOpen} + default: + return fmt.Errorf("unsupported platform: %s", runtime.GOOS) + } + + err := exec.CommandContext(ctx, cmd, args...).Start() + if err != nil { + return fmt.Errorf("failed to open browser: %w", err) + } + + return nil +} diff --git a/pkg/feedback/feedback.go b/pkg/feedback/feedback.go new file mode 100644 index 000000000..e07b74b3b --- /dev/null +++ b/pkg/feedback/feedback.go @@ -0,0 +1,3 @@ +package feedback + +var FeedbackLink = "https://docker.qualtrics.com/jfe/form/SV_cNsCIg92nQemlfw" diff --git a/pkg/tools/mcp/oauth.go b/pkg/tools/mcp/oauth.go index 0c0041c72..a287016b9 100644 --- a/pkg/tools/mcp/oauth.go +++ b/pkg/tools/mcp/oauth.go @@ -11,13 +11,13 @@ import ( "log/slog" "net/http" "net/url" - "os/exec" "regexp" - "runtime" "strings" "time" "golang.org/x/oauth2" + + "github.com/docker/cagent/pkg/browser" ) // resourceMetadataFromWWWAuth extracts resource metadata URL from WWW-Authenticate header @@ -221,7 +221,7 @@ func exchangeCodeForToken(ctx context.Context, tokenEndpoint, code, codeVerifier // requestAuthorizationCode requests the user to open the authorization URL and waits for the callback func requestAuthorizationCode(ctx context.Context, authURL string, callbackServer *CallbackServer, expectedState string) (string, string, error) { - if err := openBrowser(ctx, authURL); err != nil { + if err := browser.Open(ctx, authURL); err != nil { return "", "", err } @@ -237,32 +237,6 @@ func requestAuthorizationCode(ctx context.Context, authURL string, callbackServe return code, state, nil } -func openBrowser(ctx context.Context, urlToOpen string) error { - var cmd string - var args []string - - switch runtime.GOOS { - case "windows": - cmd = "rundll32" - args = []string{"url.dll,FileProtocolHandler", urlToOpen} - case "darwin": - cmd = "open" - args = []string{urlToOpen} - case "linux": - cmd = "xdg-open" - args = []string{urlToOpen} - default: - return fmt.Errorf("unsupported platform: %s", runtime.GOOS) - } - - err := exec.CommandContext(ctx, cmd, args...).Start() - if err != nil { - return fmt.Errorf("failed to open browser: %w", err) - } - - return nil -} - // registerClient performs dynamic client registration func registerClient(ctx context.Context, authMetadata *authorizationServerMetadata, redirectURI string, scopes []string) (clientID, clientSecret string, err error) { if authMetadata.RegistrationEndpoint == "" { diff --git a/pkg/tui/commands/commands.go b/pkg/tui/commands/commands.go index d64f363a1..d1bc3f4df 100644 --- a/pkg/tui/commands/commands.go +++ b/pkg/tui/commands/commands.go @@ -6,6 +6,7 @@ import ( tea "github.com/charmbracelet/bubbletea/v2" "github.com/docker/cagent/pkg/app" + "github.com/docker/cagent/pkg/feedback" "github.com/docker/cagent/pkg/tui/core" ) @@ -38,6 +39,10 @@ type Item struct { Execute func() tea.Cmd } +type OpenURLMsg struct { + URL string +} + func BuiltInSessionCommands() []Item { return []Item{ { @@ -83,6 +88,29 @@ func BuiltInSessionCommands() []Item { } } +func builtInFeedbackCommands() []Item { + return []Item{ + { + ID: "feedback.bug", + Label: "Report Bug", + Description: "Report a bug or issue", + Category: "Feedback", + Execute: func() tea.Cmd { + return core.CmdHandler(OpenURLMsg{URL: "https://github.com/docker/cagent/issues/new/choose"}) + }, + }, + { + ID: "feedback.feeedback", + Label: "Give Feedback", + Description: "Provide feedback about cagent", + Category: "Feedback", + Execute: func() tea.Cmd { + return core.CmdHandler(OpenURLMsg{URL: feedback.FeedbackLink}) + }, + }, + } +} + // BuildCommandCategories builds the list of command categories for the command palette func BuildCommandCategories(ctx context.Context, application *app.App) []Category { categories := []Category{ @@ -90,6 +118,10 @@ func BuildCommandCategories(ctx context.Context, application *app.App) []Categor Name: "Session", Commands: BuiltInSessionCommands(), }, + { + Name: "Feedback", + Commands: builtInFeedbackCommands(), + }, } agentCommands := application.CurrentAgentCommands(ctx) diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index 91af12dd5..90a9d412c 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -11,6 +11,7 @@ import ( "github.com/charmbracelet/lipgloss/v2" "github.com/docker/cagent/pkg/app" + "github.com/docker/cagent/pkg/browser" "github.com/docker/cagent/pkg/evaluation" "github.com/docker/cagent/pkg/runtime" "github.com/docker/cagent/pkg/tui/commands" @@ -172,6 +173,10 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { resolvedCommand := a.application.ResolveCommand(context.Background(), msg.Command) return a, core.CmdHandler(editor.SendMsg{Content: resolvedCommand}) + case commands.OpenURLMsg: + _ = browser.Open(context.Background(), msg.URL) + return a, nil + case error: a.err = msg return a, nil