10 tea "github.com/charmbracelet/bubbletea/v2"
11 "github.com/charmbracelet/crush/internal/app"
12 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/db"
14 "github.com/charmbracelet/crush/internal/tui"
15 "github.com/charmbracelet/crush/internal/version"
16 "github.com/charmbracelet/fang"
17 "github.com/charmbracelet/x/term"
18 "github.com/spf13/cobra"
21var rootCmd = &cobra.Command{
23 Short: "Terminal-based AI assistant for software development",
24 Long: `Crush is a powerful terminal-based AI assistant that helps with software development tasks.
25It provides an interactive chat interface with AI capabilities, code analysis, and LSP integration
26to assist developers in writing, debugging, and understanding code directly from the terminal.`,
28 # Run in interactive mode
31 # Run with debug logging
34 # Run with debug slog.in a specific directory
35 crush -d -c /path/to/project
40 # Run a single non-interactive prompt
41 crush -p "Explain the use of context in Go"
43 # Run a single non-interactive prompt with JSON output format
44 crush -p "Explain the use of context in Go" -f json
46 # Run in dangerous mode (auto-accept all permissions)
49 RunE: func(cmd *cobra.Command, args []string) error {
51 // XXX: Handle errors.
52 debug, _ := cmd.Flags().GetBool("debug")
53 cwd, _ := cmd.Flags().GetString("cwd")
54 prompt, _ := cmd.Flags().GetString("prompt")
55 quiet, _ := cmd.Flags().GetBool("quiet")
56 yolo, _ := cmd.Flags().GetBool("yolo")
61 return fmt.Errorf("failed to change directory: %v", err)
67 return fmt.Errorf("failed to get current working directory: %v", err)
72 cfg, err := config.Init(cwd, debug)
76 if cfg.Permissions == nil {
77 cfg.Permissions = &config.Permissions{}
79 cfg.Permissions.SkipRequests = yolo
83 // Connect to DB; this will also run migrations.
84 conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
89 app, err := app.New(ctx, conn, cfg)
91 slog.Error("Failed to create app instance", "error", err)
96 prompt, err = maybePrependStdin(prompt)
98 slog.Error("Failed to read from stdin", "error", err)
102 // Non-interactive mode.
104 // Run non-interactive flow using the App method
105 return app.RunNonInteractive(ctx, prompt, quiet)
109 program := tea.NewProgram(
112 tea.WithContext(ctx),
113 tea.WithMouseCellMotion(), // Use cell motion instead of all motion to reduce event flooding
114 tea.WithFilter(tui.MouseEventFilter), // Filter mouse events based on focus state
117 go app.Subscribe(program)
119 if _, err := program.Run(); err != nil {
120 slog.Error("TUI run error", "error", err)
121 return fmt.Errorf("TUI error: %v", err)
128 if err := fang.Execute(
129 context.Background(),
131 fang.WithVersion(version.Version),
132 fang.WithNotifySignal(os.Interrupt),
139 rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
141 rootCmd.Flags().BoolP("help", "h", false, "Help")
142 rootCmd.Flags().BoolP("debug", "d", false, "Debug")
143 rootCmd.Flags().StringP("prompt", "p", "", "Prompt to run in non-interactive mode")
144 rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)")
146 // Add quiet flag to hide spinner in non-interactive mode
147 rootCmd.Flags().BoolP("quiet", "q", false, "Hide spinner in non-interactive mode")
150func maybePrependStdin(prompt string) (string, error) {
151 if term.IsTerminal(os.Stdin.Fd()) {
154 fi, err := os.Stdin.Stat()
158 if fi.Mode()&os.ModeNamedPipe == 0 {
161 bts, err := io.ReadAll(os.Stdin)
165 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