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 RunE: func(cmd *cobra.Command, args []string) error {
51 debug, _ := cmd.Flags().GetBool("debug")
52 cwd, _ := cmd.Flags().GetString("cwd")
53 prompt, _ := cmd.Flags().GetString("prompt")
54 quiet, _ := cmd.Flags().GetBool("quiet")
59 return fmt.Errorf("failed to change directory: %v", err)
65 return fmt.Errorf("failed to get current working directory: %v", err)
70 cfg, err := config.Init(cwd, debug)
77 // Connect DB, this will also run migrations
78 conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
83 app, err := app.New(ctx, conn, cfg)
85 slog.Error(fmt.Sprintf("Failed to create app instance: %v", err))
90 // Initialize MCP tools early for both modes
91 initMCPTools(ctx, app, cfg)
93 prompt, err = maybePrependStdin(prompt)
95 slog.Error(fmt.Sprintf("Failed to read from stdin: %v", err))
99 // Non-interactive mode
101 // Run non-interactive flow using the App method
102 return app.RunNonInteractive(ctx, prompt, quiet)
106 program := tea.NewProgram(
109 tea.WithKeyReleases(),
110 tea.WithUniformKeyLayout(),
111 tea.WithContext(ctx),
112 tea.WithMouseCellMotion(), // Use cell motion instead of all motion to reduce event flooding
113 tea.WithFilter(tui.MouseEventFilter), // Filter mouse events based on focus state
116 go app.Subscribe(program)
118 if _, err := program.Run(); err != nil {
119 slog.Error(fmt.Sprintf("TUI run error: %v", err))
120 return fmt.Errorf("TUI error: %v", err)
126func initMCPTools(ctx context.Context, app *app.App, cfg *config.Config) {
128 defer log.RecoverPanic("MCP-goroutine", nil)
130 // Create a context with timeout for the initial MCP tools fetch
131 ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
134 // Set this up once with proper error handling
135 agent.GetMcpTools(ctxWithTimeout, app.Permissions, cfg)
136 slog.Info("MCP message handling goroutine exiting")
141 if err := fang.Execute(
142 context.Background(),
144 fang.WithVersion(version.Version),
145 fang.WithNotifySignal(os.Interrupt),
152 rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
154 rootCmd.Flags().BoolP("help", "h", false, "Help")
155 rootCmd.Flags().BoolP("debug", "d", false, "Debug")
156 rootCmd.Flags().StringP("prompt", "p", "", "Prompt to run in non-interactive mode")
158 // Add quiet flag to hide spinner in non-interactive mode
159 rootCmd.Flags().BoolP("quiet", "q", false, "Hide spinner in non-interactive mode")
162func maybePrependStdin(prompt string) (string, error) {
163 if term.IsTerminal(os.Stdin.Fd()) {
166 fi, err := os.Stdin.Stat()
170 if fi.Mode()&os.ModeNamedPipe == 0 {
173 bts, err := io.ReadAll(os.Stdin)
177 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