From 77da29d673bd2d01a80849821db06f0e6444010d Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 26 Nov 2025 10:34:47 -0700 Subject: [PATCH 1/4] fix(editor): fix opening `$EDITOR` when it contains arguments (#1481) --- internal/tui/components/chat/editor/editor.go | 13 ++--- internal/tui/util/shell.go | 58 +++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 internal/tui/util/shell.go diff --git a/internal/tui/components/chat/editor/editor.go b/internal/tui/components/chat/editor/editor.go index de1a98b34595613594e83063cc12add4ba820c84..4b719f6b674176c79f42e96013c670182f0d282d 100644 --- a/internal/tui/components/chat/editor/editor.go +++ b/internal/tui/components/chat/editor/editor.go @@ -6,7 +6,6 @@ import ( "math/rand" "net/http" "os" - "os/exec" "path/filepath" "runtime" "slices" @@ -96,7 +95,7 @@ type OpenEditorMsg struct { func (m *editorCmp) openEditor(value string) tea.Cmd { editor := os.Getenv("EDITOR") if editor == "" { - // Use platform-appropriate default editor + // Use platform-appropriate default editor. if runtime.GOOS == "windows" { editor = "notepad" } else { @@ -112,11 +111,11 @@ func (m *editorCmp) openEditor(value string) tea.Cmd { if _, err := tmpfile.WriteString(value); err != nil { return util.ReportError(err) } - c := exec.CommandContext(context.TODO(), editor, tmpfile.Name()) - c.Stdin = os.Stdin - c.Stdout = os.Stdout - c.Stderr = os.Stderr - return tea.ExecProcess(c, func(err error) tea.Msg { + + // Build the full shell command with the file argument. + cmdStr := fmt.Sprintf("%s %s", editor, tmpfile.Name()) + + return util.ExecShell(context.TODO(), cmdStr, func(err error) tea.Msg { if err != nil { return util.ReportError(err) } diff --git a/internal/tui/util/shell.go b/internal/tui/util/shell.go new file mode 100644 index 0000000000000000000000000000000000000000..422831649d043c24265ebaafaa58bcc041214557 --- /dev/null +++ b/internal/tui/util/shell.go @@ -0,0 +1,58 @@ +package util + +import ( + "context" + "io" + "strings" + + tea "charm.land/bubbletea/v2" + "mvdan.cc/sh/v3/interp" + "mvdan.cc/sh/v3/syntax" +) + +// shellCommand wraps a shell interpreter to implement tea.ExecCommand. +type shellCommand struct { + ctx context.Context + file *syntax.File + stdin io.Reader + stdout io.Writer + stderr io.Writer +} + +func (s *shellCommand) SetStdin(r io.Reader) { + s.stdin = r +} + +func (s *shellCommand) SetStdout(w io.Writer) { + s.stdout = w +} + +func (s *shellCommand) SetStderr(w io.Writer) { + s.stderr = w +} + +func (s *shellCommand) Run() error { + runner, err := interp.New( + interp.StdIO(s.stdin, s.stdout, s.stderr), + ) + if err != nil { + return err + } + return runner.Run(s.ctx, s.file) +} + +// ExecShell executes a shell command string using tea.Exec. +// The command is parsed and executed via mvdan.cc/sh/v3/interp, allowing +// proper handling of shell syntax like quotes and arguments. +func ExecShell(ctx context.Context, cmdStr string, callback tea.ExecCallback) tea.Cmd { + parsed, err := syntax.NewParser().Parse(strings.NewReader(cmdStr), "") + if err != nil { + return ReportError(err) + } + + cmd := &shellCommand{ + ctx: ctx, + file: parsed, + } + return tea.Exec(cmd, callback) +} From 3a5375faf49699ce47ed05ef30e1eab02f1b06b9 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Wed, 26 Nov 2025 14:57:20 -0300 Subject: [PATCH 2/4] fix: fix `h` and `l` keys not working on models filter --- internal/tui/components/dialogs/models/models.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/tui/components/dialogs/models/models.go b/internal/tui/components/dialogs/models/models.go index 406f4a5853a88bfb22914071e7e53d19662b7ecd..820c8b0fa3c574521ed773f62032e2e7cb6747d1 100644 --- a/internal/tui/components/dialogs/models/models.go +++ b/internal/tui/components/dialogs/models/models.go @@ -154,11 +154,9 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) { util.ReportInfo("URL copied to clipboard"), ) } - case key.Matches(msg, m.keyMap.Choose): - if m.showClaudeAuthMethodChooser { - m.claudeAuthMethodChooser.ToggleChoice() - return m, nil - } + case key.Matches(msg, m.keyMap.Choose) && m.showClaudeAuthMethodChooser: + m.claudeAuthMethodChooser.ToggleChoice() + return m, nil case key.Matches(msg, m.keyMap.Select): selectedItem := m.modelList.SelectedModel() From 9f536ba81f79d4bbe10def517815ee1d4dd99372 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Wed, 26 Nov 2025 15:00:41 -0300 Subject: [PATCH 3/4] fix(claude): simplify code and fix potential unauthorized error --- internal/agent/coordinator.go | 26 +++++++------------------- internal/config/config.go | 4 +--- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go index 4bfcc0062ae9a06dc858989f2cce925976d6d32b..c01ae619726343e33992b1c0c98066697e0b5f7f 100644 --- a/internal/agent/coordinator.go +++ b/internal/agent/coordinator.go @@ -475,27 +475,15 @@ func (c *coordinator) buildAgentModels(ctx context.Context) (Model, Model, error } func (c *coordinator) buildAnthropicProvider(baseURL, apiKey string, headers map[string]string) (fantasy.Provider, error) { - hasBearerAuth := false - for key := range headers { - if strings.ToLower(key) == "authorization" { - hasBearerAuth = true - break - } - } - - isBearerToken := strings.HasPrefix(apiKey, "Bearer ") - var opts []anthropic.Option - if apiKey != "" && !hasBearerAuth { - if isBearerToken { - slog.Debug("API key starts with 'Bearer ', using as Authorization header") - headers["Authorization"] = apiKey - apiKey = "" // clear apiKey to avoid using X-Api-Key header - } - } - if apiKey != "" { - // Use standard X-Api-Key header + if strings.HasPrefix(apiKey, "Bearer ") { + // NOTE: Prevent the SDK from picking up the API key from env. + os.Setenv("ANTHROPIC_API_KEY", "") + + headers["Authorization"] = apiKey + } else if apiKey != "" { + // X-Api-Key header opts = append(opts, anthropic.WithAPIKey(apiKey)) } diff --git a/internal/config/config.go b/internal/config/config.go index 66e90e062cad91fb088d8ad97970a4f790960d92..2d1882ba876e3d0ab1ea284dce7edbbe3503013a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -117,9 +117,7 @@ type ProviderConfig struct { } func (pc *ProviderConfig) SetupClaudeCode() { - if !strings.HasPrefix(pc.APIKey, "Bearer ") { - pc.APIKey = fmt.Sprintf("Bearer %s", pc.APIKey) - } + pc.APIKey = fmt.Sprintf("Bearer %s", pc.OAuthToken.AccessToken) pc.SystemPromptPrefix = "You are Claude Code, Anthropic's official CLI for Claude." pc.ExtraHeaders["anthropic-version"] = "2023-06-01"