run.go

  1package cmd
  2
  3import (
  4	"context"
  5	"fmt"
  6	"log/slog"
  7	"os"
  8	"os/signal"
  9	"strings"
 10
 11	"charm.land/log/v2"
 12	"github.com/charmbracelet/crush/internal/event"
 13	"github.com/spf13/cobra"
 14)
 15
 16var runCmd = &cobra.Command{
 17	Use:   "run [prompt...]",
 18	Short: "Run a single non-interactive prompt",
 19	Long: `Run a single prompt in non-interactive mode and exit.
 20The prompt can be provided as arguments or piped from stdin.`,
 21	Example: `
 22# Run a simple prompt
 23crush run "Guess my 5 favorite Pokรฉmon"
 24
 25# Pipe input from stdin
 26curl https://charm.land | crush run "Summarize this website"
 27
 28# Read from a file
 29crush run "What is this code doing?" <<< prrr.go
 30
 31# Redirect output to a file
 32crush run "Generate a hot README for this project" > MY_HOT_README.md
 33
 34# Run in quiet mode (hide the spinner)
 35crush run --quiet "Generate a README for this project"
 36
 37# Run in verbose mode (show logs)
 38crush run --verbose "Generate a README for this project"
 39
 40# Continue a previous session
 41crush run --session {session-id} "Follow up on your last response"
 42
 43# Continue the most recent session
 44crush run --continue "Follow up on your last response"
 45
 46  `,
 47	RunE: func(cmd *cobra.Command, args []string) error {
 48		var (
 49			quiet, _      = cmd.Flags().GetBool("quiet")
 50			verbose, _    = cmd.Flags().GetBool("verbose")
 51			largeModel, _ = cmd.Flags().GetString("model")
 52			smallModel, _ = cmd.Flags().GetString("small-model")
 53			sessionID, _  = cmd.Flags().GetString("session")
 54			useLast, _    = cmd.Flags().GetBool("continue")
 55		)
 56
 57		// Cancel on SIGINT or SIGTERM.
 58		ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
 59		defer cancel()
 60
 61		app, err := setupApp(cmd)
 62		if err != nil {
 63			return err
 64		}
 65		defer app.Shutdown()
 66
 67		if sessionID != "" {
 68			sess, err := resolveSessionID(ctx, app.Sessions, sessionID)
 69			if err != nil {
 70				return err
 71			}
 72			sessionID = sess.ID
 73		}
 74
 75		if !app.Config().IsConfigured() {
 76			return fmt.Errorf("no providers configured - please run 'crush' to set up a provider interactively")
 77		}
 78
 79		if verbose {
 80			slog.SetDefault(slog.New(log.New(os.Stderr)))
 81		}
 82
 83		prompt := strings.Join(args, " ")
 84
 85		prompt, err = MaybePrependStdin(prompt)
 86		if err != nil {
 87			slog.Error("Failed to read from stdin", "error", err)
 88			return err
 89		}
 90
 91		if prompt == "" {
 92			return fmt.Errorf("no prompt provided")
 93		}
 94
 95		event.SetNonInteractive(true)
 96		event.AppInitialized()
 97
 98		switch {
 99		case sessionID != "":
100			event.SetContinueBySessionID(true)
101		case useLast:
102			event.SetContinueLastSession(true)
103		}
104
105		return app.RunNonInteractive(ctx, os.Stdout, prompt, largeModel, smallModel, quiet || verbose, sessionID, useLast)
106	},
107}
108
109func init() {
110	runCmd.Flags().BoolP("quiet", "q", false, "Hide spinner")
111	runCmd.Flags().BoolP("verbose", "v", false, "Show logs")
112	runCmd.Flags().StringP("model", "m", "", "Model to use. Accepts 'model' or 'provider/model' to disambiguate models with the same name across providers")
113	runCmd.Flags().String("small-model", "", "Small model to use. If not provided, uses the default small model for the provider")
114	runCmd.Flags().StringP("session", "s", "", "Continue a previous session by ID")
115	runCmd.Flags().BoolP("continue", "C", false, "Continue the most recent session")
116	runCmd.MarkFlagsMutuallyExclusive("session", "continue")
117}