feat: crush dirs (#551)

Carlos Alexandro Becker and Christian Rocha created

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: Christian Rocha <christian@rocha.is>

Change summary

internal/cmd/dirs.go    | 66 +++++++++++++++++++++++++++++++++++++++++++
internal/cmd/logs.go    |  1 
internal/cmd/root.go    |  9 ++++-
internal/cmd/schema.go  |  4 --
internal/config/load.go |  5 +-
5 files changed, 76 insertions(+), 9 deletions(-)

Detailed changes

internal/cmd/dirs.go 🔗

@@ -0,0 +1,66 @@
+package cmd
+
+import (
+	"os"
+	"path/filepath"
+
+	"github.com/charmbracelet/crush/internal/config"
+	"github.com/charmbracelet/lipgloss/v2"
+	"github.com/charmbracelet/lipgloss/v2/table"
+	"github.com/charmbracelet/x/term"
+	"github.com/spf13/cobra"
+)
+
+var dirsCmd = &cobra.Command{
+	Use:   "dirs",
+	Short: "Print directories used by Crush",
+	Long: `Print the directories where Crush stores its configuration and data files.
+This includes the global configuration directory and data directory.`,
+	Example: `
+# Print all directories
+crush dirs
+
+# Print only the config directory
+crush dirs config
+
+# Print only the data directory
+crush dirs data
+  `,
+	Run: func(cmd *cobra.Command, args []string) {
+		if term.IsTerminal(os.Stdout.Fd()) {
+			// We're in a TTY: make it fancy.
+			t := table.New().
+				Border(lipgloss.RoundedBorder()).
+				StyleFunc(func(row, col int) lipgloss.Style {
+					return lipgloss.NewStyle().Padding(0, 2)
+				}).
+				Row("Config", filepath.Dir(config.GlobalConfig())).
+				Row("Data", filepath.Dir(config.GlobalConfigData()))
+			lipgloss.Println(t)
+			return
+		}
+		// Not a TTY.
+		cmd.Println(filepath.Dir(config.GlobalConfig()))
+		cmd.Println(filepath.Dir(config.GlobalConfigData()))
+	},
+}
+
+var configDirCmd = &cobra.Command{
+	Use:   "config",
+	Short: "Print the configuration directory used by Crush",
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.Println(filepath.Dir(config.GlobalConfig()))
+	},
+}
+
+var dataDirCmd = &cobra.Command{
+	Use:   "data",
+	Short: "Print the datauration directory used by Crush",
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.Println(filepath.Dir(config.GlobalConfigData()))
+	},
+}
+
+func init() {
+	dirsCmd.AddCommand(configDirCmd, dataDirCmd)
+}

internal/cmd/logs.go 🔗

@@ -68,7 +68,6 @@ var logsCmd = &cobra.Command{
 func init() {
 	logsCmd.Flags().BoolP("follow", "f", false, "Follow log output")
 	logsCmd.Flags().IntP("tail", "t", defaultTailLines, "Show only the last N lines default: 1000 for performance")
-	rootCmd.AddCommand(logsCmd)
 }
 
 func followLogs(ctx context.Context, logsFile string, tailLines int) error {

internal/cmd/root.go 🔗

@@ -33,8 +33,13 @@ func init() {
 	rootCmd.Flags().BoolP("help", "h", false, "Help")
 	rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)")
 
-	rootCmd.AddCommand(runCmd)
-	rootCmd.AddCommand(updateProvidersCmd)
+	rootCmd.AddCommand(
+		runCmd,
+		dirsCmd,
+		updateProvidersCmd,
+		logsCmd,
+		schemaCmd,
+	)
 }
 
 var rootCmd = &cobra.Command{

internal/cmd/schema.go 🔗

@@ -24,7 +24,3 @@ var schemaCmd = &cobra.Command{
 		return nil
 	},
 }
-
-func init() {
-	rootCmd.AddCommand(schemaCmd)
-}

internal/config/load.go 🔗

@@ -520,7 +520,7 @@ func (c *Config) configureSelectedModels(knownProviders []catwalk.Provider) erro
 func lookupConfigs(cwd string) []string {
 	// prepend default config paths
 	configPaths := []string{
-		globalConfig(),
+		GlobalConfig(),
 		GlobalConfigData(),
 	}
 
@@ -596,7 +596,8 @@ func hasAWSCredentials(env env.Env) bool {
 	return false
 }
 
-func globalConfig() string {
+// GlobalConfig returns the global configuration file path for the application.
+func GlobalConfig() string {
 	xdgConfigHome := os.Getenv("XDG_CONFIG_HOME")
 	if xdgConfigHome != "" {
 		return filepath.Join(xdgConfigHome, appName, fmt.Sprintf("%s.json", appName))