11 tea "github.com/charmbracelet/bubbletea/v2"
12 "github.com/charmbracelet/crush/internal/app"
13 "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/db"
15 "github.com/charmbracelet/crush/internal/format"
16 "github.com/charmbracelet/crush/internal/llm/agent"
17 "github.com/charmbracelet/crush/internal/log"
18 "github.com/charmbracelet/crush/internal/tui"
19 "github.com/charmbracelet/crush/internal/version"
20 "github.com/charmbracelet/fang"
21 "github.com/charmbracelet/x/term"
22 "github.com/spf13/cobra"
25var rootCmd = &cobra.Command{
27 Short: "Terminal-based AI assistant for software development",
28 Long: `Crush is a powerful terminal-based AI assistant that helps with software development tasks.
29It provides an interactive chat interface with AI capabilities, code analysis, and LSP integration
30to assist developers in writing, debugging, and understanding code directly from the terminal.`,
32 # Run in interactive mode
35 # Run with debug logging
38 # Run with debug slog.in a specific directory
39 crush -d -c /path/to/project
44 # Run a single non-interactive prompt
45 crush -p "Explain the use of context in Go"
47 # Run a single non-interactive prompt with JSON output format
48 crush -p "Explain the use of context in Go" -f json
50 RunE: func(cmd *cobra.Command, args []string) error {
52 debug, _ := cmd.Flags().GetBool("debug")
53 cwd, _ := cmd.Flags().GetString("cwd")
54 prompt, _ := cmd.Flags().GetString("prompt")
55 outputFormat, _ := cmd.Flags().GetString("output-format")
56 quiet, _ := cmd.Flags().GetBool("quiet")
58 // Validate format option
59 if !format.IsValid(outputFormat) {
60 return fmt.Errorf("invalid format option: %s\n%s", outputFormat, format.GetHelpText())
66 return fmt.Errorf("failed to change directory: %v", err)
72 return fmt.Errorf("failed to get current working directory: %v", err)
77 cfg, err := config.Init(cwd, debug)
82 // Create main context for the application
83 ctx, cancel := context.WithCancel(context.Background())
86 // Connect DB, this will also run migrations
87 conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
92 app, err := app.New(ctx, conn, cfg)
94 slog.Error(fmt.Sprintf("Failed to create app instance: %v", err))
97 // Defer shutdown here so it runs for both interactive and non-interactive modes
100 // Initialize MCP tools early for both modes
101 initMCPTools(ctx, app, cfg)
103 prompt, err = maybePrependStdin(prompt)
105 slog.Error(fmt.Sprintf("Failed to read from stdin: %v", err))
109 // Non-interactive mode
111 // Run non-interactive flow using the App method
112 return app.RunNonInteractive(ctx, prompt, outputFormat, quiet)
116 program := tea.NewProgram(
119 tea.WithKeyReleases(),
120 tea.WithUniformKeyLayout(),
123 go app.Subscribe(program)
125 if _, err := program.Run(); err != nil {
126 slog.Error(fmt.Sprintf("TUI run error: %v", err))
127 return fmt.Errorf("TUI error: %v", err)
134func initMCPTools(ctx context.Context, app *app.App, cfg *config.Config) {
136 defer log.RecoverPanic("MCP-goroutine", nil)
138 // Create a context with timeout for the initial MCP tools fetch
139 ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
142 // Set this up once with proper error handling
143 agent.GetMcpTools(ctxWithTimeout, app.Permissions, cfg)
144 slog.Info("MCP message handling goroutine exiting")
149 if err := fang.Execute(
150 context.Background(),
152 fang.WithVersion(version.Version),
159 rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
161 rootCmd.Flags().BoolP("help", "h", false, "Help")
162 rootCmd.Flags().BoolP("debug", "d", false, "Debug")
163 rootCmd.Flags().StringP("prompt", "p", "", "Prompt to run in non-interactive mode")
165 // Add format flag with validation logic
166 rootCmd.Flags().StringP("output-format", "f", format.Text.String(),
167 "Output format for non-interactive mode (text, json)")
169 // Add quiet flag to hide spinner in non-interactive mode
170 rootCmd.Flags().BoolP("quiet", "q", false, "Hide spinner in non-interactive mode")
172 // Register custom validation for the format flag
173 rootCmd.RegisterFlagCompletionFunc("output-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
174 return format.SupportedFormats, cobra.ShellCompDirectiveNoFileComp
178func maybePrependStdin(prompt string) (string, error) {
179 if term.IsTerminal(os.Stdin.Fd()) {
182 fi, err := os.Stdin.Stat()
186 if fi.Mode()&os.ModeNamedPipe == 0 {
189 bts, err := io.ReadAll(os.Stdin)
193 return string(bts) + "\n\n" + prompt, nil
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list