feat(cmd): server: support custom host for server and client

Ayman Bagabas created

Change summary

internal/cmd/root.go   | 13 +++++++++++--
internal/cmd/run.go    | 16 ++++++++++++----
internal/cmd/server.go |  5 ++++-
3 files changed, 27 insertions(+), 7 deletions(-)

Detailed changes

internal/cmd/root.go 🔗

@@ -12,6 +12,7 @@ import (
 	"github.com/charmbracelet/crush/internal/client"
 	"github.com/charmbracelet/crush/internal/log"
 	"github.com/charmbracelet/crush/internal/proto"
+	"github.com/charmbracelet/crush/internal/server"
 	"github.com/charmbracelet/crush/internal/tui"
 	"github.com/charmbracelet/crush/internal/version"
 	"github.com/charmbracelet/fang"
@@ -19,6 +20,8 @@ import (
 	"github.com/spf13/cobra"
 )
 
+var clientHost string
+
 func init() {
 	rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
 	rootCmd.PersistentFlags().StringP("data-dir", "D", "", "Custom crush data directory")
@@ -27,6 +30,8 @@ func init() {
 	rootCmd.Flags().BoolP("help", "h", false, "Help")
 	rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)")
 
+	rootCmd.Flags().StringVar(&clientHost, "host", server.DefaultAddr(), "Connect to a specific crush server host (for advanced users)")
+
 	rootCmd.AddCommand(runCmd)
 	rootCmd.AddCommand(updateProvidersCmd)
 }
@@ -65,7 +70,10 @@ crush -y
 			return err
 		}
 
-		m := tui.New(c)
+		m, err := tui.New(c)
+		if err != nil {
+			return fmt.Errorf("failed to create TUI model: %v", err)
+		}
 
 		defer func() { c.DeleteInstance(cmd.Context(), c.ID()) }()
 
@@ -138,12 +146,13 @@ func setupApp(cmd *cobra.Command) (*client.Client, error) {
 		return nil, err
 	}
 
-	c, err := client.DefaultClient(cwd)
+	c, err := client.NewClient(cwd, "unix", clientHost)
 	if err != nil {
 		return nil, err
 	}
 
 	if _, err := c.CreateInstance(ctx, proto.Instance{
+		Path:    cwd,
 		DataDir: dataDir,
 		Debug:   debug,
 		YOLO:    yolo,

internal/cmd/run.go 🔗

@@ -26,13 +26,18 @@ crush run -q "Generate a README for this project"
 	RunE: func(cmd *cobra.Command, args []string) error {
 		quiet, _ := cmd.Flags().GetBool("quiet")
 
-		app, err := setupApp(cmd)
+		c, err := setupApp(cmd)
 		if err != nil {
 			return err
 		}
-		defer app.Shutdown()
+		defer func() { c.DeleteInstance(cmd.Context(), c.ID()) }()
 
-		if !app.Config().IsConfigured() {
+		cfg, err := c.GetConfig(cmd.Context())
+		if err != nil {
+			return fmt.Errorf("failed to get config: %v", err)
+		}
+
+		if !cfg.IsConfigured() {
 			return fmt.Errorf("no providers configured - please run 'crush' to set up a provider interactively")
 		}
 
@@ -49,7 +54,10 @@ crush run -q "Generate a README for this project"
 		}
 
 		// Run non-interactive flow using the App method
-		return app.RunNonInteractive(cmd.Context(), prompt, quiet)
+		// return c.RunNonInteractive(cmd.Context(), prompt, quiet)
+		// TODO: implement non-interactive run
+		_ = quiet
+		return nil
 	},
 }
 

internal/cmd/server.go 🔗

@@ -15,6 +15,8 @@ import (
 	"github.com/spf13/cobra"
 )
 
+var serverHost string
+
 var serverCmd = &cobra.Command{
 	Use:   "server",
 	Short: "Start the Crush server",
@@ -41,7 +43,7 @@ var serverCmd = &cobra.Command{
 			slog.SetLogLoggerLevel(slog.LevelDebug)
 		}
 
-		srv := server.NewServer(cfg, "unix", server.DefaultAddr())
+		srv := server.NewServer(cfg, "unix", serverHost)
 		srv.SetLogger(slog.Default())
 		slog.Info("Starting Crush server...", "addr", srv.Addr)
 
@@ -81,5 +83,6 @@ var serverCmd = &cobra.Command{
 }
 
 func init() {
+	serverCmd.Flags().StringVar(&serverHost, "host", server.DefaultAddr(), "Server host (TCP or Unix socket)")
 	rootCmd.AddCommand(serverCmd)
 }