mirror of
https://github.com/cline/cline.git
synced 2026-09-21 05:10:09 +08:00
added instance list support for jetbrains and kill all (#6800)
* added instance list support for jetbrains and kill all * copilot reviews
This commit is contained in:
+101
-19
@@ -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 <address>",
|
||||
@@ -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,
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user