@@ -10,6 +10,7 @@ import (
11 "log/slog"
11 "log/slog"
@@ -30,13 +31,13 @@ import (
30 "github.com/charmbracelet/crush/internal/pubsub"
31 "github.com/charmbracelet/crush/internal/pubsub"
31 "github.com/charmbracelet/crush/internal/session"
32 "github.com/charmbracelet/crush/internal/session"
32 "github.com/charmbracelet/crush/internal/shell"
33 "github.com/charmbracelet/crush/internal/shell"
33− "github.com/charmbracelet/crush/internal/term"
34 "github.com/charmbracelet/crush/internal/tui/components/anim"
34 "github.com/charmbracelet/crush/internal/tui/components/anim"
35 "github.com/charmbracelet/crush/internal/tui/styles"
35 "github.com/charmbracelet/crush/internal/tui/styles"
36 "github.com/charmbracelet/crush/internal/update"
36 "github.com/charmbracelet/crush/internal/update"
37 "github.com/charmbracelet/crush/internal/version"
37 "github.com/charmbracelet/crush/internal/version"
38 "github.com/charmbracelet/x/ansi"
38 "github.com/charmbracelet/x/ansi"
39 "github.com/charmbracelet/x/exp/charmtone"
39 "github.com/charmbracelet/x/exp/charmtone"
40+ "github.com/charmbracelet/x/term"
42type App struct {
43type App struct {
@@ -61,7 +62,7 @@ type App struct {
61 cleanupFuncs []func() error
62 cleanupFuncs []func() error
64−// New initializes a new applcation instance.
65+// New initializes a new application instance.
65func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
66func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
66 q := db.New(conn)
67 q := db.New(conn)
67 sessions := session.NewService(q)
68 sessions := session.NewService(q)
@@ -129,15 +130,27 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt
129 ctx, cancel := context.WithCancel(ctx)
130 ctx, cancel := context.WithCancel(ctx)
130 defer cancel()
131 defer cancel()
132− var spinner *format.Spinner
134+ spinner *format.Spinner
140+ if f, ok := output.(*os.File); ok {
141+ stdoutTTY = term.IsTerminal(f.Fd())
143+ stderrTTY = term.IsTerminal(os.Stderr.Fd())
144+ stdinTTY = term.IsTerminal(os.Stdin.Fd())
133− if !quiet {
146+ if !quiet && stderrTTY {
134 t := styles.CurrentTheme()
147 t := styles.CurrentTheme()
136 // Detect background color to set the appropriate color for the
149 // Detect background color to set the appropriate color for the
137 // spinner's 'Generating...' text. Without this, that text would be
150 // spinner's 'Generating...' text. Without this, that text would be
138 // unreadable in light terminals.
151 // unreadable in light terminals.
139 hasDarkBG := true
152 hasDarkBG := true
140− if f, ok := output.(*os.File); ok {
153+ if f, ok := output.(*os.File); ok && stdinTTY && stdoutTTY {
141 hasDarkBG = lipgloss.HasDarkBackground(os.Stdin, f)
154 hasDarkBG = lipgloss.HasDarkBackground(os.Stdin, f)
143 defaultFG := lipgloss.LightDark(hasDarkBG)(charmtone.Pepper, t.FgBase)
156 defaultFG := lipgloss.LightDark(hasDarkBG)(charmtone.Pepper, t.FgBase)
@@ -203,10 +216,9 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt
204 messageEvents := app.Messages.Subscribe(ctx)
217 messageEvents := app.Messages.Subscribe(ctx)
205 messageReadBytes := make(map[string]int)
218 messageReadBytes := make(map[string]int)
206− supportsProgressBar := term.SupportsProgressBar()
208 defer func() {
220 defer func() {
209− if supportsProgressBar {
221+ if stderrTTY {
210 _, _ = fmt.Fprintf(os.Stderr, ansi.ResetProgressBar)
222 _, _ = fmt.Fprintf(os.Stderr, ansi.ResetProgressBar)
@@ -216,9 +228,9 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt
219− if supportsProgressBar {
231+ if stderrTTY {
220− // HACK: Reinitialize the terminal progress bar on every iteration so
232+ // HACK: Reinitialize the terminal progress bar on every iteration
221− // it doesn't get hidden by the terminal due to inactivity.
233+ // so it doesn't get hidden by the terminal due to inactivity.
222 _, _ = fmt.Fprintf(os.Stderr, ansi.SetIndeterminateProgressBar)
234 _, _ = fmt.Fprintf(os.Stderr, ansi.SetIndeterminateProgressBar)
@@ -248,6 +260,11 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt
250 part := content[readBytes:]
262 part := content[readBytes:]
263+ // Trim leading whitespace. Sometimes the LLM includes leading
264+ // formatting and intentation, which we don't want here.
266+ part = strings.TrimLeft(part, " \t")
251 fmt.Fprint(output, part)
268 fmt.Fprint(output, part)
252 messageReadBytes[msg.ID] = len(content)
269 messageReadBytes[msg.ID] = len(content)