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/llm/agent"
16 "github.com/charmbracelet/crush/internal/log"
17 "github.com/charmbracelet/crush/internal/tui"
18 "github.com/charmbracelet/crush/internal/version"
19 "github.com/charmbracelet/fang"
20 "github.com/charmbracelet/x/term"
21 "github.com/spf13/cobra"
24var rootCmd = &cobra.Command{
26 Short: "Terminal-based AI assistant for software development",
27 Long: `Crush is a powerful terminal-based AI assistant that helps with software development tasks.
28It provides an interactive chat interface with AI capabilities, code analysis, and LSP integration
29to assist developers in writing, debugging, and understanding code directly from the terminal.`,
31 # Run in interactive mode
34 # Run with debug logging
37 # Run with debug slog.in a specific directory
38 crush -d -c /path/to/project
43 # Run a single non-interactive prompt
44 crush -p "Explain the use of context in Go"
46 # Run a single non-interactive prompt with JSON output format
47 crush -p "Explain the use of context in Go" -f json
49 # Run in dangerous mode (auto-accept all permissions)
52 RunE: func(cmd *cobra.Command, args []string) error {
54 debug, _ := cmd.Flags().GetBool("debug")
55 cwd, _ := cmd.Flags().GetString("cwd")
56 prompt, _ := cmd.Flags().GetString("prompt")
57 quiet, _ := cmd.Flags().GetBool("quiet")
58 yolo, _ := cmd.Flags().GetBool("yolo")
63 return fmt.Errorf("failed to change directory: %v", err)
69 return fmt.Errorf("failed to get current working directory: %v", err)
74 cfg, err := config.Init(cwd, debug)
78 cfg.Options.SkipPermissionsRequests = yolo
82 // Connect DB, this will also run migrations
83 conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
88 app, err := app.New(ctx, conn, cfg)
90 slog.Error(fmt.Sprintf("Failed to create app instance: %v", err))
95 // Initialize MCP tools early for both modes
96 initMCPTools(ctx, app, cfg)
98 prompt, err = maybePrependStdin(prompt)
100 slog.Error(fmt.Sprintf("Failed to read from stdin: %v", err))
104 // Non-interactive mode
106 // Run non-interactive flow using the App method
107 return app.RunNonInteractive(ctx, prompt, quiet)
111 program := tea.NewProgram(
114 tea.WithContext(ctx),
115 tea.WithMouseCellMotion(), // Use cell motion instead of all motion to reduce event flooding
116 tea.WithFilter(tui.MouseEventFilter), // Filter mouse events based on focus state
119 go app.Subscribe(program)
121 if _, err := program.Run(); err != nil {
122 slog.Error(fmt.Sprintf("TUI run error: %v", err))
123 return fmt.Errorf("TUI error: %v", err)
129func initMCPTools(ctx context.Context, app *app.App, cfg *config.Config) {
131 defer log.RecoverPanic("MCP-goroutine", nil)
133 // Create a context with timeout for the initial MCP tools fetch
134 ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
137 // Set this up once with proper error handling
138 agent.GetMcpTools(ctxWithTimeout, app.Permissions, cfg)
139 slog.Info("MCP message handling goroutine exiting")
144 if err := fang.Execute(
145 context.Background(),
147 fang.WithVersion(version.Version),
148 fang.WithNotifySignal(os.Interrupt),
155 rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
157 rootCmd.Flags().BoolP("help", "h", false, "Help")
158 rootCmd.Flags().BoolP("debug", "d", false, "Debug")
159 rootCmd.Flags().StringP("prompt", "p", "", "Prompt to run in non-interactive mode")
160 rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)")
162 // Add quiet flag to hide spinner in non-interactive mode
163 rootCmd.Flags().BoolP("quiet", "q", false, "Hide spinner in non-interactive mode")
166func maybePrependStdin(prompt string) (string, error) {
167 if term.IsTerminal(os.Stdin.Fd()) {
170 fi, err := os.Stdin.Stat()
174 if fi.Mode()&os.ModeNamedPipe == 0 {
177 bts, err := io.ReadAll(os.Stdin)
181 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