Merge branch 'main' into ui

Ayman Bagabas created

Change summary

internal/agent/coordinator.go |  7 +++++++
internal/tui/util/shell.go    | 15 ++-------------
internal/uiutil/uiutil.go     | 20 ++++++++++++++++++++
3 files changed, 29 insertions(+), 13 deletions(-)

Detailed changes

internal/agent/coordinator.go 🔗

@@ -132,6 +132,13 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string,
 
 	mergedOptions, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, providerCfg)
 
+	if providerCfg.OAuthToken != nil && providerCfg.OAuthToken.IsExpired() {
+		slog.Info("Token needs to be refreshed", "provider", providerCfg.ID)
+		if err := c.refreshOAuth2Token(ctx, providerCfg); err != nil {
+			return nil, err
+		}
+	}
+
 	run := func() (*fantasy.AgentResult, error) {
 		return c.currentAgent.Run(ctx, SessionAgentCall{
 			SessionID:        sessionID,

internal/tui/util/shell.go 🔗

@@ -2,25 +2,14 @@ package util
 
 import (
 	"context"
-	"errors"
-	"os/exec"
 
 	tea "charm.land/bubbletea/v2"
-	"mvdan.cc/sh/v3/shell"
+	"github.com/charmbracelet/crush/internal/uiutil"
 )
 
 // ExecShell parses a shell command string and executes it with exec.Command.
 // Uses shell.Fields for proper handling of shell syntax like quotes and
 // arguments while preserving TTY handling for terminal editors.
 func ExecShell(ctx context.Context, cmdStr string, callback tea.ExecCallback) tea.Cmd {
-	fields, err := shell.Fields(cmdStr, nil)
-	if err != nil {
-		return ReportError(err)
-	}
-	if len(fields) == 0 {
-		return ReportError(errors.New("empty command"))
-	}
-
-	cmd := exec.CommandContext(ctx, fields[0], fields[1:]...)
-	return tea.ExecProcess(cmd, callback)
+	return uiutil.ExecShell(ctx, cmdStr, callback)
 }

internal/uiutil/uiutil.go 🔗

@@ -4,10 +4,14 @@
 package uiutil
 
 import (
+	"context"
+	"errors"
 	"log/slog"
+	"os/exec"
 	"time"
 
 	tea "charm.land/bubbletea/v2"
+	"mvdan.cc/sh/v3/shell"
 )
 
 type Cursor interface {
@@ -60,3 +64,19 @@ type (
 	}
 	ClearStatusMsg struct{}
 )
+
+// ExecShell parses a shell command string and executes it with exec.Command.
+// Uses shell.Fields for proper handling of shell syntax like quotes and
+// arguments while preserving TTY handling for terminal editors.
+func ExecShell(ctx context.Context, cmdStr string, callback tea.ExecCallback) tea.Cmd {
+	fields, err := shell.Fields(cmdStr, nil)
+	if err != nil {
+		return ReportError(err)
+	}
+	if len(fields) == 0 {
+		return ReportError(errors.New("empty command"))
+	}
+
+	cmd := exec.CommandContext(ctx, fields[0], fields[1:]...)
+	return tea.ExecProcess(cmd, callback)
+}