From b5fde0adbfb9941aec1407407feae86a26b127b9 Mon Sep 17 00:00:00 2001 From: pashpashpash Date: Mon, 13 Oct 2025 00:59:59 -0700 Subject: [PATCH] added instance list support for jetbrains and kill all (#6800) * added instance list support for jetbrains and kill all * copilot reviews --- cli/pkg/cli/instances.go | 120 ++++++++++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 19 deletions(-) diff --git a/cli/pkg/cli/instances.go b/cli/pkg/cli/instances.go index 257cc4b871..f3418c0fa1 100644 --- a/cli/pkg/cli/instances.go +++ b/cli/pkg/cli/instances.go @@ -11,11 +11,53 @@ import ( "github.com/cline/cli/pkg/cli/display" "github.com/cline/cli/pkg/cli/global" + "github.com/cline/cli/pkg/common" + client2 "github.com/cline/grpc-go/client" "github.com/cline/grpc-go/cline" "github.com/spf13/cobra" "google.golang.org/grpc/health/grpc_health_v1" ) +const ( + platformCLI = "CLI" + platformJetBrains = "JetBrains" + platformNA = "N/A" + hostPlatformCLI = "Cline CLI" // Value returned by host bridge for CLI instances +) + +// detectInstancePlatform connects to an instance's host bridge and determines its platform +func detectInstancePlatform(ctx context.Context, instance *common.CoreInstanceInfo) (string, error) { + hostTarget, err := common.NormalizeAddressForGRPC(instance.HostServiceAddress) + if err != nil { + return platformNA, err + } + + hostClient, err := client2.NewClineClient(hostTarget) + if err != nil { + return platformNA, err + } + defer hostClient.Disconnect() + + if err := hostClient.Connect(ctx); err != nil { + return platformNA, err + } + + hostVersion, err := hostClient.Env.GetHostVersion(ctx, &cline.EmptyRequest{}) + if err != nil { + return platformNA, err + } + + if hostVersion.Platform == nil { + return platformNA, fmt.Errorf("host returned nil platform") + } + + platformStr := *hostVersion.Platform + if platformStr == hostPlatformCLI { + return platformCLI, nil + } + return platformJetBrains, nil +} + func NewInstanceCommand() *cobra.Command { cmd := &cobra.Command{ Use: "instance", @@ -33,7 +75,7 @@ func NewInstanceCommand() *cobra.Command { } func newInstanceKillCommand() *cobra.Command { - var killAll bool + var killAllCLI bool cmd := &cobra.Command{ Use: "kill
", @@ -41,11 +83,11 @@ func newInstanceKillCommand() *cobra.Command { Short: "Kill a Cline instance by address", Long: `Kill a running Cline instance and clean up its registry entry.`, Args: func(cmd *cobra.Command, args []string) error { - if killAll && len(args) > 0 { - return fmt.Errorf("cannot specify both --all flag and address argument") + if killAllCLI && len(args) > 0 { + return fmt.Errorf("cannot specify both --all-cli flag and address argument") } - if !killAll && len(args) != 1 { - return fmt.Errorf("requires exactly one address argument when --all is not specified") + if !killAllCLI && len(args) != 1 { + return fmt.Errorf("requires exactly one address argument when --all-cli is not specified") } return nil }, @@ -57,20 +99,20 @@ func newInstanceKillCommand() *cobra.Command { ctx := cmd.Context() registry := global.Clients.GetRegistry() - if killAll { - return killAllInstances(ctx, registry) + if killAllCLI { + return killAllCLIInstances(ctx, registry) } else { return global.KillInstanceByAddress(ctx, registry, args[0]) } }, } - cmd.Flags().BoolVar(&killAll, "all", false, "kill all running instances") + cmd.Flags().BoolVarP(&killAllCLI, "all-cli", "a", false, "kill all running CLI instances (excludes JetBrains)") return cmd } -func killAllInstances(ctx context.Context, registry *global.ClientRegistry) error { +func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) error { // Get all instances from registry instances, err := registry.ListInstancesCleaned(ctx) if err != nil { @@ -82,12 +124,41 @@ func killAllInstances(ctx context.Context, registry *global.ClientRegistry) erro return nil } - fmt.Printf("Killing %d instances...\n", len(instances)) + // Filter to only CLI instances + var cliInstances []*common.CoreInstanceInfo + var skippedNonCLI int + for _, instance := range instances { + if instance.Status == grpc_health_v1.HealthCheckResponse_SERVING { + platform, err := detectInstancePlatform(ctx, instance) + if err == nil { + if platform == platformCLI { + cliInstances = append(cliInstances, instance) + } else { + skippedNonCLI++ + fmt.Printf("⊘ Skipping %s instance: %s\n", platform, instance.Address) + } + } + } + } + + if len(cliInstances) == 0 { + if skippedNonCLI > 0 { + fmt.Printf("No CLI instances to kill. Skipped %d JetBrains instance(s).\n", skippedNonCLI) + } else { + fmt.Println("No CLI instances found to kill.") + } + return nil + } + + fmt.Printf("Killing %d CLI instance(s)...\n", len(cliInstances)) + if skippedNonCLI > 0 { + fmt.Printf("Skipping %d JetBrains instance(s).\n", skippedNonCLI) + } var killResults []killResult - // Kill all instances - for _, instance := range instances { + // Kill all CLI instances + for _, instance := range cliInstances { result := killInstanceProcess(ctx, registry, instance.Address) killResults = append(killResults, result) @@ -220,6 +291,7 @@ func newInstanceListCommand() *cobra.Command { version string lastSeen string pid string + platform string isDefault string } @@ -235,9 +307,11 @@ func newInstanceListCommand() *cobra.Command { lastSeen = instance.LastSeen.Format("2006-01-02") } - // Get PID via RPC if instance is healthy - pid := "N/A" + // Get PID and platform via RPC if instance is healthy + pid := platformNA + platform := platformNA if instance.Status == grpc_health_v1.HealthCheckResponse_SERVING { + // Get PID from core if client, err := registry.GetClient(ctx, instance.Address); err == nil { if processInfo, err := client.State.GetProcessInfo(ctx, &cline.EmptyRequest{}); err == nil { pid = fmt.Sprintf("%d", processInfo.ProcessId) @@ -247,6 +321,11 @@ func newInstanceListCommand() *cobra.Command { } } } + + // Get platform from host bridge + if detectedPlatform, err := detectInstancePlatform(ctx, instance); err == nil { + platform = detectedPlatform + } } rows = append(rows, instanceRow{ @@ -255,6 +334,7 @@ func newInstanceListCommand() *cobra.Command { version: instance.Version, lastSeen: lastSeen, pid: pid, + platform: platform, isDefault: isDefault, }) } @@ -263,15 +343,16 @@ func newInstanceListCommand() *cobra.Command { if global.Config.OutputFormat == "plain" { // Use tabwriter for plain output w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "ADDRESS\tSTATUS\tVERSION\tLAST SEEN\tPID\tDEFAULT") + fmt.Fprintln(w, "ADDRESS\tSTATUS\tVERSION\tLAST SEEN\tPID\tPLATFORM\tDEFAULT") for _, row := range rows { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", row.address, row.status, row.version, row.lastSeen, row.pid, + row.platform, row.isDefault, ) } @@ -280,16 +361,17 @@ func newInstanceListCommand() *cobra.Command { } else { // Use markdown table for rich output var markdown strings.Builder - markdown.WriteString("| **ADDRESS (ID)** | **STATUS** | **VERSION** | **LAST SEEN** | **PID** | **DEFAULT** |\n") - markdown.WriteString("|---------|--------|---------|-----------|-----|---------|") + markdown.WriteString("| **ADDRESS (ID)** | **STATUS** | **VERSION** | **LAST SEEN** | **PID** | **PLATFORM** | **DEFAULT** |\n") + markdown.WriteString("|---------|--------|---------|-----------|-----|----------|---------|") for _, row := range rows { - markdown.WriteString(fmt.Sprintf("\n| %s | %s | %s | %s | %s | %s |", + markdown.WriteString(fmt.Sprintf("\n| %s | %s | %s | %s | %s | %s | %s |", row.address, row.status, row.version, row.lastSeen, row.pid, + row.platform, row.isDefault, )) }