From 967991753260f3dbee42fba30db04c8a59868dc2 Mon Sep 17 00:00:00 2001 From: pashpashpash Date: Mon, 20 Oct 2025 16:47:07 -0700 Subject: [PATCH] `cline doctor` command: terminal shift enter support + auto updates (#6883) * terminal shift enter support * not needed * detecting windows * removing enhancedkeyboard * removing enhanced keyboard * ghostty * proper ghostty support * docs for posterity * better logging * removing md * doctor command * adding arguments for sync/async for doctor and keyboard setup - and moved keyboard setup to doctor command * doctor help * cleaning up logging and making things more explicit * language and positioning --- cli/cmd/cline/main.go | 12 +- cli/go.mod | 2 +- cli/pkg/cli/doctor.go | 62 +++ cli/pkg/cli/terminal/keyboard.go | 688 +++++++++++++++++++++++++++++++ cli/pkg/cli/updater/updater.go | 42 +- 5 files changed, 792 insertions(+), 14 deletions(-) create mode 100644 cli/pkg/cli/doctor.go create mode 100644 cli/pkg/cli/terminal/keyboard.go diff --git a/cli/cmd/cline/main.go b/cli/cmd/cline/main.go index 315a197cab..95f6945765 100644 --- a/cli/cmd/cline/main.go +++ b/cli/cmd/cline/main.go @@ -99,12 +99,7 @@ see the manual page: man cline`, // Check if user has credentials configured if !isUserReadyToUse(ctx, instanceAddress) { - // Create renderer for welcome messages - renderer := display.NewRenderer(global.Config.OutputFormat) - - markdown := "## hey there! looks like you're new here. let's get you set up" - rendered := renderer.RenderMarkdown(markdown) - fmt.Printf("\n%s\n\n", rendered) + fmt.Printf("\n\033[90mHey there! Looks like you're new here. Let's get you set up\033[0m\n\n") if err := auth.HandleAuthMenuNoArgs(ctx); err != nil { // Check if user cancelled - exit cleanly @@ -119,9 +114,7 @@ see the manual page: man cline`, return fmt.Errorf("credentials still not configured - please run 'cline auth' to complete setup") } - markdown = "## ✓ setup complete, you can now use the cline cli" - rendered = renderer.RenderMarkdown(markdown) - fmt.Printf("\n%s\n\n", rendered) + fmt.Printf("\n\033[90m✓ Setup complete, you can now use the Cline CLI\033[0m\n\n") } } else { // User specified --address flag, use that @@ -187,6 +180,7 @@ see the manual page: man cline`, rootCmd.AddCommand(cli.NewVersionCommand()) rootCmd.AddCommand(cli.NewAuthCommand()) rootCmd.AddCommand(cli.NewLogsCommand()) + rootCmd.AddCommand(cli.NewDoctorCommand()) if err := rootCmd.ExecuteContext(context.Background()); err != nil { os.Exit(1) diff --git a/cli/go.mod b/cli/go.mod index 4c34d00c0f..1e0cc9208e 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -8,6 +8,7 @@ require ( github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/glamour v0.10.0 github.com/charmbracelet/huh v0.7.1-0.20251005153135-a01a1e304532 + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/cline/grpc-go v0.0.0 github.com/glebarez/go-sqlite v1.22.0 github.com/spf13/cobra v1.8.0 @@ -24,7 +25,6 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect github.com/charmbracelet/x/ansi v0.9.3 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect diff --git a/cli/pkg/cli/doctor.go b/cli/pkg/cli/doctor.go new file mode 100644 index 0000000000..d461c4b2c2 --- /dev/null +++ b/cli/pkg/cli/doctor.go @@ -0,0 +1,62 @@ +package cli + +import ( + "fmt" + + "github.com/cline/cli/pkg/cli/global" + "github.com/cline/cli/pkg/cli/terminal" + "github.com/cline/cli/pkg/cli/updater" + "github.com/spf13/cobra" +) + +// NewDoctorCommand creates the doctor command +func NewDoctorCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "doctor", + Aliases: []string{"d"}, + Short: "Check system health and diagnose problems", + Long: `Check the health of your Cline CLI installation and diagnose problems. + +Currently this command performs the following checks and fixes: + +Terminal Configuration: + - Detects your terminal emulator (VS Code, Cursor, Ghostty, Kitty, WezTerm, Alacritty) + - Configures shift+enter to insert newlines in multiline input + - Creates backups before modifying configuration files + - Supported terminals: VS Code, Cursor, Ghostty, Kitty, WezTerm, Alacritty + - iTerm2 works by default, Terminal.app requires manual setup + +CLI Updates: + - Checks npm registry for the latest version + - Automatically installs updates via npm if available + - Respects NO_AUTO_UPDATE environment variable + - Skipped in CI environments + +Note: Future versions will include additional health checks for Node.js version, +npm availability, Cline Core connectivity, database integrity, and more.`, + RunE: func(cmd *cobra.Command, args []string) error { + return runDoctorChecks() + }, + } + + return cmd +} + +// runDoctorChecks performs all doctor diagnostics and configuration +func runDoctorChecks() error { + fmt.Println("\n\033[1mCline Doctor - System Health Check\033[0m\n") + + // Configure terminal keybindings (terminal.go prints its own status) + fmt.Println("\033[90m━━━ Terminal Configuration ━━━\033[0m\n") + terminal.SetupKeyboardSync() + + // Check for updates (updater.go prints its own status) + fmt.Println("\n\033[90m━━━ CLI Updates ━━━\033[0m\n") + updater.CheckAndUpdateSync(global.Config.Verbose, true) + + // Summary + fmt.Println("\n\033[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m") + fmt.Println("\n\033[32m✓ Health check complete\033[0m\n") + + return nil +} diff --git a/cli/pkg/cli/terminal/keyboard.go b/cli/pkg/cli/terminal/keyboard.go new file mode 100644 index 0000000000..b35a3f3d89 --- /dev/null +++ b/cli/pkg/cli/terminal/keyboard.go @@ -0,0 +1,688 @@ +package terminal + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "sync" +) + +// KeyboardProtocol manages enhanced keyboard protocol support for detecting +// modified keys like shift+enter across all major terminals. +type KeyboardProtocol struct { + enabled bool + mu sync.Mutex +} + +var globalProtocol = &KeyboardProtocol{} + +// EnableEnhancedKeyboard enables enhanced keyboard protocols to support +// shift+enter and other modified keys across all major terminals: +// - VS Code integrated terminal +// - iTerm2 +// - Terminal.app +// - Ghostty +// - Kitty +// - WezTerm +// - Alacritty +// - foot +// - xterm +// +// This function is safe to call multiple times and handles cleanup automatically. +// It enables both modifyOtherKeys (xterm protocol) and Kitty keyboard protocol +// for maximum compatibility. +func EnableEnhancedKeyboard() { + globalProtocol.mu.Lock() + defer globalProtocol.mu.Unlock() + + if globalProtocol.enabled { + return // Already enabled + } + + // Check if we're in a TTY (not piped/redirected) + if !isatty(os.Stdin.Fd()) { + return + } + + // Enable modifyOtherKeys mode 2 + // This tells xterm-compatible terminals (VS Code, iTerm2, Terminal.app, etc.) + // to send escape sequences for modified keys including shift+enter + // Format: CSI > 4 ; 2 m + // - Mode 2 enables for ALL keys including well-known ones + fmt.Print("\x1b[>4;2m") + + // Also enable Kitty keyboard protocol for terminals that support it + // This is a more modern protocol supported by Kitty, Ghostty, WezTerm, foot, etc. + // Format: CSI = u where flags=1 means "disambiguate escape codes" + // This makes shift+enter distinguishable from plain enter + fmt.Print("\x1b[=1u") + + globalProtocol.enabled = true +} + +// DisableEnhancedKeyboard restores the terminal to its default keyboard mode. +// This should be called on program exit to be a good citizen. +func DisableEnhancedKeyboard() { + globalProtocol.mu.Lock() + defer globalProtocol.mu.Unlock() + + if !globalProtocol.enabled { + return + } + + // Disable modifyOtherKeys (restore to mode 0) + fmt.Print("\x1b[>4;0m") + + // Disable Kitty keyboard protocol + fmt.Print("\x1b[