Detailed changes
@@ -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))
}
@@ -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"
@@ -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)
}
@@ -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()
@@ -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)
+}