12 tea "github.com/charmbracelet/bubbletea/v2"
13 "github.com/charmbracelet/crush/internal/app"
14 "github.com/charmbracelet/crush/internal/config"
15 "github.com/charmbracelet/crush/internal/db"
16 "github.com/charmbracelet/crush/internal/event"
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"
25 rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
26 rootCmd.PersistentFlags().StringP("data-dir", "D", "", "Custom crush data directory")
27 rootCmd.PersistentFlags().BoolP("debug", "d", false, "Debug")
29 rootCmd.Flags().BoolP("help", "h", false, "Help")
30 rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)")
32 rootCmd.AddCommand(runCmd)
33 rootCmd.AddCommand(updateProvidersCmd)
36var rootCmd = &cobra.Command{
38 Short: "Terminal-based AI assistant for software development",
39 Long: `Crush is a powerful terminal-based AI assistant that helps with software development tasks.
40It provides an interactive chat interface with AI capabilities, code analysis, and LSP integration
41to assist developers in writing, debugging, and understanding code directly from the terminal.`,
43# Run in interactive mode
46# Run with debug logging
49# Run with debug logging in a specific directory
50crush -d -c /path/to/project
52# Run with custom data directory
53crush -D /path/to/custom/.crush
58# Run a single non-interactive prompt
59crush run "Explain the use of context in Go"
61# Run in dangerous mode (auto-accept all permissions)
64 RunE: func(cmd *cobra.Command, args []string) error {
65 app, err := setupApp(cmd)
71 event.AppInitialized()
74 program := tea.NewProgram(
77 tea.WithContext(cmd.Context()),
78 tea.WithMouseCellMotion(), // Use cell motion instead of all motion to reduce event flooding
79 tea.WithFilter(tui.MouseEventFilter), // Filter mouse events based on focus state
82 go app.Subscribe(program)
84 if _, err := program.Run(); err != nil {
86 slog.Error("TUI run error", "error", err)
87 return fmt.Errorf("TUI error: %v", err)
91 PostRun: func(cmd *cobra.Command, args []string) {
97 if err := fang.Execute(
100 fang.WithVersion(version.Version),
101 fang.WithNotifySignal(os.Interrupt),
107// setupApp handles the common setup logic for both interactive and non-interactive modes.
108// It returns the app instance, config, cleanup function, and any error.
109func setupApp(cmd *cobra.Command) (*app.App, error) {
110 debug, _ := cmd.Flags().GetBool("debug")
111 yolo, _ := cmd.Flags().GetBool("yolo")
112 dataDir, _ := cmd.Flags().GetString("data-dir")
115 cwd, err := ResolveCwd(cmd)
120 cfg, err := config.Init(cwd, dataDir, debug)
125 if cfg.Permissions == nil {
126 cfg.Permissions = &config.Permissions{}
128 cfg.Permissions.SkipRequests = yolo
130 if err := createDotCrushDir(cfg.Options.DataDirectory); err != nil {
134 // Connect to DB; this will also run migrations.
135 conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
140 appInstance, err := app.New(ctx, conn, cfg)
142 slog.Error("Failed to create app instance", "error", err)
146 if shouldEnableMetrics() {
150 return appInstance, nil
153func shouldEnableMetrics() bool {
154 if v, _ := strconv.ParseBool(os.Getenv("CRUSH_DISABLE_METRICS")); v {
157 if v, _ := strconv.ParseBool(os.Getenv("DO_NOT_TRACK")); v {
160 if config.Get().Options.DisableMetrics {
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
184func ResolveCwd(cmd *cobra.Command) (string, error) {
185 cwd, _ := cmd.Flags().GetString("cwd")
189 return "", fmt.Errorf("failed to change directory: %v", err)
193 cwd, err := os.Getwd()
195 return "", fmt.Errorf("failed to get current working directory: %v", err)
200func createDotCrushDir(dir string) error {
201 if err := os.MkdirAll(dir, 0o700); err != nil {
202 return fmt.Errorf("failed to create data directory: %q %w", dir, err)
205 gitIgnorePath := filepath.Join(dir, ".gitignore")
206 if _, err := os.Stat(gitIgnorePath); os.IsNotExist(err) {
207 if err := os.WriteFile(gitIgnorePath, []byte("*\n"), 0o644); err != nil {
208 return fmt.Errorf("failed to create .gitignore file: %q %w", gitIgnorePath, err)
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