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