@caretive/caret-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmrc.tmp +2 -0
- package/README.md +72 -0
- package/cmd/cline/main.go +348 -0
- package/cmd/cline-host/main.go +71 -0
- package/e2e/default_update_test.go +154 -0
- package/e2e/helpers_test.go +378 -0
- package/e2e/main_test.go +47 -0
- package/e2e/mixed_stress_test.go +120 -0
- package/e2e/sqlite_helper.go +161 -0
- package/e2e/start_list_test.go +178 -0
- package/go.mod +64 -0
- package/go.sum +162 -0
- package/man/cline.1 +331 -0
- package/man/cline.1.md +332 -0
- package/package.json +54 -0
- package/pkg/cli/auth/auth_cline_provider.go +285 -0
- package/pkg/cli/auth/auth_menu.go +323 -0
- package/pkg/cli/auth/auth_subscription.go +130 -0
- package/pkg/cli/auth/byo_quick_setup.go +247 -0
- package/pkg/cli/auth/models_cline.go +141 -0
- package/pkg/cli/auth/models_list_fetch.go +156 -0
- package/pkg/cli/auth/models_list_static.go +69 -0
- package/pkg/cli/auth/providers_byo.go +184 -0
- package/pkg/cli/auth/providers_list.go +517 -0
- package/pkg/cli/auth/update_api_configurations.go +647 -0
- package/pkg/cli/auth/wizard_byo.go +764 -0
- package/pkg/cli/auth/wizard_byo_bedrock.go +193 -0
- package/pkg/cli/auth/wizard_byo_oca.go +366 -0
- package/pkg/cli/auth.go +43 -0
- package/pkg/cli/clerror/cline_error.go +187 -0
- package/pkg/cli/config/manager.go +208 -0
- package/pkg/cli/config/settings_renderer.go +198 -0
- package/pkg/cli/config.go +152 -0
- package/pkg/cli/display/ansi.go +27 -0
- package/pkg/cli/display/banner.go +211 -0
- package/pkg/cli/display/deduplicator.go +95 -0
- package/pkg/cli/display/markdown_renderer.go +139 -0
- package/pkg/cli/display/renderer.go +304 -0
- package/pkg/cli/display/segment_streamer.go +212 -0
- package/pkg/cli/display/streaming.go +134 -0
- package/pkg/cli/display/system_renderer.go +269 -0
- package/pkg/cli/display/tool_renderer.go +455 -0
- package/pkg/cli/display/tool_result_parser.go +371 -0
- package/pkg/cli/display/typewriter.go +210 -0
- package/pkg/cli/doctor.go +65 -0
- package/pkg/cli/global/cline-clients.go +501 -0
- package/pkg/cli/global/global.go +113 -0
- package/pkg/cli/global/registry.go +304 -0
- package/pkg/cli/handlers/ask_handlers.go +339 -0
- package/pkg/cli/handlers/handler.go +130 -0
- package/pkg/cli/handlers/say_handlers.go +521 -0
- package/pkg/cli/instances.go +506 -0
- package/pkg/cli/logs.go +382 -0
- package/pkg/cli/output/coordinator.go +167 -0
- package/pkg/cli/output/input_model.go +497 -0
- package/pkg/cli/sqlite/locks.go +366 -0
- package/pkg/cli/task/history_handler.go +72 -0
- package/pkg/cli/task/input_handler.go +577 -0
- package/pkg/cli/task/manager.go +1283 -0
- package/pkg/cli/task/settings_parser.go +754 -0
- package/pkg/cli/task/stream_coordinator.go +60 -0
- package/pkg/cli/task.go +675 -0
- package/pkg/cli/terminal/keyboard.go +695 -0
- package/pkg/cli/tui/HELP_WANTED.md +1 -0
- package/pkg/cli/types/history.go +17 -0
- package/pkg/cli/types/messages.go +329 -0
- package/pkg/cli/types/state.go +59 -0
- package/pkg/cli/updater/updater.go +409 -0
- package/pkg/cli/version.go +43 -0
- package/pkg/common/constants.go +6 -0
- package/pkg/common/schema.go +54 -0
- package/pkg/common/types.go +54 -0
- package/pkg/common/utils.go +185 -0
- package/pkg/generated/field_overrides.go +39 -0
- package/pkg/generated/providers.go +1584 -0
- package/pkg/hostbridge/diff.go +351 -0
- package/pkg/hostbridge/disabled/watch.go +39 -0
- package/pkg/hostbridge/disabled/window.go +63 -0
- package/pkg/hostbridge/disabled/workspace.go +66 -0
- package/pkg/hostbridge/env.go +166 -0
- package/pkg/hostbridge/grpc_server.go +113 -0
- package/pkg/hostbridge/simple.go +43 -0
- package/pkg/hostbridge/simple_workspace.go +85 -0
- package/pkg/hostbridge/window.go +129 -0
- package/scripts/publish-caret-cli.sh +39 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"strings"
|
|
8
|
+
"syscall"
|
|
9
|
+
"text/tabwriter"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
"github.com/cline/cli/pkg/cli/display"
|
|
13
|
+
"github.com/cline/cli/pkg/cli/global"
|
|
14
|
+
"github.com/cline/cli/pkg/common"
|
|
15
|
+
client2 "github.com/cline/grpc-go/client"
|
|
16
|
+
"github.com/cline/grpc-go/cline"
|
|
17
|
+
"github.com/spf13/cobra"
|
|
18
|
+
"google.golang.org/grpc/health/grpc_health_v1"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const (
|
|
22
|
+
platformCLI = "CLI"
|
|
23
|
+
platformJetBrains = "JetBrains"
|
|
24
|
+
platformNA = "N/A"
|
|
25
|
+
hostPlatformCLI = "Caret CLI" // Value returned by host bridge for CLI instances (CARET MODIFICATION)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
// detectInstancePlatform connects to an instance's host bridge and determines its platform
|
|
29
|
+
func detectInstancePlatform(ctx context.Context, instance *common.CoreInstanceInfo) (string, error) {
|
|
30
|
+
hostTarget, err := common.NormalizeAddressForGRPC(instance.HostServiceAddress)
|
|
31
|
+
if err != nil {
|
|
32
|
+
return platformNA, err
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
hostClient, err := client2.NewClineClient(hostTarget)
|
|
36
|
+
if err != nil {
|
|
37
|
+
return platformNA, err
|
|
38
|
+
}
|
|
39
|
+
defer hostClient.Disconnect()
|
|
40
|
+
|
|
41
|
+
if err := hostClient.Connect(ctx); err != nil {
|
|
42
|
+
return platformNA, err
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
hostVersion, err := hostClient.Env.GetHostVersion(ctx, &cline.EmptyRequest{})
|
|
46
|
+
if err != nil {
|
|
47
|
+
return platformNA, err
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if hostVersion.Platform == nil {
|
|
51
|
+
return platformNA, fmt.Errorf("host returned nil platform")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
platformStr := *hostVersion.Platform
|
|
55
|
+
if platformStr == hostPlatformCLI {
|
|
56
|
+
return platformCLI, nil
|
|
57
|
+
}
|
|
58
|
+
return platformJetBrains, nil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func NewInstanceCommand() *cobra.Command {
|
|
62
|
+
cmd := &cobra.Command{
|
|
63
|
+
Use: "instance",
|
|
64
|
+
Aliases: []string{"i"},
|
|
65
|
+
Short: "Manage Caret instances", // CARET MODIFICATION
|
|
66
|
+
Long: `List and manage multiple Caret instances similar to kubectl contexts.`, // CARET MODIFICATION
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cmd.AddCommand(newInstanceListCommand())
|
|
70
|
+
cmd.AddCommand(newInstanceDefaultCommand())
|
|
71
|
+
cmd.AddCommand(newInstanceNewCommand())
|
|
72
|
+
cmd.AddCommand(newInstanceKillCommand())
|
|
73
|
+
|
|
74
|
+
return cmd
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func newInstanceKillCommand() *cobra.Command {
|
|
78
|
+
var killAllCLI bool
|
|
79
|
+
|
|
80
|
+
cmd := &cobra.Command{
|
|
81
|
+
Use: "kill <address>",
|
|
82
|
+
Aliases: []string{"k"},
|
|
83
|
+
Short: "Kill a Cline instance by address",
|
|
84
|
+
Long: `Kill a running Cline instance and clean up its registry entry.`,
|
|
85
|
+
Args: func(cmd *cobra.Command, args []string) error {
|
|
86
|
+
if killAllCLI && len(args) > 0 {
|
|
87
|
+
return fmt.Errorf("cannot specify both --all-cli flag and address argument")
|
|
88
|
+
}
|
|
89
|
+
if !killAllCLI && len(args) != 1 {
|
|
90
|
+
return fmt.Errorf("requires exactly one address argument when --all-cli is not specified")
|
|
91
|
+
}
|
|
92
|
+
return nil
|
|
93
|
+
},
|
|
94
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
95
|
+
if global.Clients == nil {
|
|
96
|
+
return fmt.Errorf("clients not initialized")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ctx := cmd.Context()
|
|
100
|
+
registry := global.Clients.GetRegistry()
|
|
101
|
+
|
|
102
|
+
if killAllCLI {
|
|
103
|
+
return killAllCLIInstances(ctx, registry)
|
|
104
|
+
} else {
|
|
105
|
+
return global.KillInstanceByAddress(ctx, registry, args[0])
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
cmd.Flags().BoolVarP(&killAllCLI, "all-cli", "a", false, "kill all running CLI instances (excludes JetBrains)")
|
|
111
|
+
|
|
112
|
+
return cmd
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) error {
|
|
116
|
+
// Get all instances from registry
|
|
117
|
+
instances, err := registry.ListInstancesCleaned(ctx)
|
|
118
|
+
if err != nil {
|
|
119
|
+
return fmt.Errorf("failed to list instances: %w", err)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if len(instances) == 0 {
|
|
123
|
+
fmt.Println("No Cline instances found to kill.")
|
|
124
|
+
return nil
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Filter to only CLI instances
|
|
128
|
+
var cliInstances []*common.CoreInstanceInfo
|
|
129
|
+
var skippedNonCLI int
|
|
130
|
+
for _, instance := range instances {
|
|
131
|
+
if instance.Status == grpc_health_v1.HealthCheckResponse_SERVING {
|
|
132
|
+
platform, err := detectInstancePlatform(ctx, instance)
|
|
133
|
+
if err == nil {
|
|
134
|
+
if platform == platformCLI {
|
|
135
|
+
cliInstances = append(cliInstances, instance)
|
|
136
|
+
} else {
|
|
137
|
+
skippedNonCLI++
|
|
138
|
+
fmt.Printf("⊘ Skipping %s instance: %s\n", platform, instance.Address)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if len(cliInstances) == 0 {
|
|
145
|
+
if skippedNonCLI > 0 {
|
|
146
|
+
fmt.Printf("No CLI instances to kill. Skipped %d JetBrains instance(s).\n", skippedNonCLI)
|
|
147
|
+
} else {
|
|
148
|
+
fmt.Println("No CLI instances found to kill.")
|
|
149
|
+
}
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fmt.Printf("Killing %d CLI instance(s)...\n", len(cliInstances))
|
|
154
|
+
if skippedNonCLI > 0 {
|
|
155
|
+
fmt.Printf("Skipping %d JetBrains instance(s).\n", skippedNonCLI)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
var killResults []killResult
|
|
159
|
+
killedAddresses := make(map[string]bool)
|
|
160
|
+
|
|
161
|
+
// Kill all CLI instances
|
|
162
|
+
for _, instance := range cliInstances {
|
|
163
|
+
result := killInstanceProcess(ctx, registry, instance.Address)
|
|
164
|
+
killResults = append(killResults, result)
|
|
165
|
+
|
|
166
|
+
if result.err != nil {
|
|
167
|
+
fmt.Printf("✗ Failed to kill %s: %v\n", instance.Address, result.err)
|
|
168
|
+
} else if result.alreadyDead {
|
|
169
|
+
fmt.Printf("⚠ Instance %s appears to be already dead\n", instance.Address)
|
|
170
|
+
} else {
|
|
171
|
+
fmt.Printf("✓ Killed %s (PID %d)\n", instance.Address, result.pid)
|
|
172
|
+
killedAddresses[instance.Address] = true
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Wait for killed instances to clean up their registry entries
|
|
177
|
+
if len(killedAddresses) > 0 {
|
|
178
|
+
fmt.Printf("Waiting for instances to clean up registry entries...\n")
|
|
179
|
+
|
|
180
|
+
maxWaitTime := 10 // seconds
|
|
181
|
+
for i := 0; i < maxWaitTime; i++ {
|
|
182
|
+
time.Sleep(1 * time.Second)
|
|
183
|
+
|
|
184
|
+
remainingInstances, err := registry.ListInstancesCleaned(ctx)
|
|
185
|
+
if err != nil {
|
|
186
|
+
fmt.Printf("Warning: failed to check registry status: %v\n", err)
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check if any of the killed instances are still in the registry
|
|
191
|
+
stillPresent := []string{}
|
|
192
|
+
for _, remaining := range remainingInstances {
|
|
193
|
+
if killedAddresses[remaining.Address] {
|
|
194
|
+
stillPresent = append(stillPresent, remaining.Address)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if len(stillPresent) == 0 {
|
|
199
|
+
fmt.Printf("✓ All killed instances successfully removed from registry.\n")
|
|
200
|
+
break
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if i == maxWaitTime-1 {
|
|
204
|
+
fmt.Printf("⚠ %d killed instance(s) still in registry after %d seconds\n", len(stillPresent), maxWaitTime)
|
|
205
|
+
for _, addr := range stillPresent {
|
|
206
|
+
fmt.Printf(" - %s\n", addr)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Print summary
|
|
213
|
+
successful := 0
|
|
214
|
+
failed := 0
|
|
215
|
+
alreadyDead := 0
|
|
216
|
+
|
|
217
|
+
for _, result := range killResults {
|
|
218
|
+
if result.err != nil {
|
|
219
|
+
failed++
|
|
220
|
+
} else if result.alreadyDead {
|
|
221
|
+
alreadyDead++
|
|
222
|
+
} else {
|
|
223
|
+
successful++
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fmt.Printf("\nSummary: ")
|
|
228
|
+
if successful > 0 {
|
|
229
|
+
fmt.Printf("Successfully killed %d instances. ", successful)
|
|
230
|
+
}
|
|
231
|
+
if alreadyDead > 0 {
|
|
232
|
+
fmt.Printf("%d were already dead. ", alreadyDead)
|
|
233
|
+
}
|
|
234
|
+
if failed > 0 {
|
|
235
|
+
fmt.Printf("%d failures.", failed)
|
|
236
|
+
return fmt.Errorf("failed to kill %d out of %d instances", failed, len(instances))
|
|
237
|
+
}
|
|
238
|
+
fmt.Println()
|
|
239
|
+
|
|
240
|
+
return nil
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
type killResult struct {
|
|
244
|
+
address string
|
|
245
|
+
pid int
|
|
246
|
+
alreadyDead bool
|
|
247
|
+
err error
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
func killInstanceProcess(ctx context.Context, registry *global.ClientRegistry, address string) killResult {
|
|
251
|
+
// Get gRPC client and process info
|
|
252
|
+
client, err := registry.GetClient(ctx, address)
|
|
253
|
+
if err != nil {
|
|
254
|
+
return killResult{address: address, alreadyDead: true, err: nil}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
processInfo, err := client.State.GetProcessInfo(ctx, &cline.EmptyRequest{})
|
|
258
|
+
if err != nil {
|
|
259
|
+
return killResult{address: address, alreadyDead: true, err: nil}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
pid := int(processInfo.ProcessId)
|
|
263
|
+
|
|
264
|
+
// Kill the process
|
|
265
|
+
if err := syscall.Kill(pid, syscall.SIGTERM); err != nil {
|
|
266
|
+
return killResult{address: address, pid: pid, err: err}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return killResult{address: address, pid: pid, err: nil}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
func newInstanceListCommand() *cobra.Command {
|
|
273
|
+
cmd := &cobra.Command{
|
|
274
|
+
Use: "list",
|
|
275
|
+
Aliases: []string{"l"},
|
|
276
|
+
Short: "List all registered Cline instances",
|
|
277
|
+
Long: `List all registered Cline instances with their status and connection details.`,
|
|
278
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
279
|
+
if global.Clients == nil {
|
|
280
|
+
return fmt.Errorf("clients not initialized")
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
ctx := cmd.Context()
|
|
284
|
+
registry := global.Clients.GetRegistry()
|
|
285
|
+
|
|
286
|
+
// Load, cleanup stale local entries, and update health
|
|
287
|
+
instances, err := registry.ListInstancesCleaned(ctx)
|
|
288
|
+
if err != nil {
|
|
289
|
+
return fmt.Errorf("failed to list instances: %w", err)
|
|
290
|
+
}
|
|
291
|
+
defaultInstance := registry.GetDefaultInstance()
|
|
292
|
+
|
|
293
|
+
if len(instances) == 0 {
|
|
294
|
+
fmt.Println("No Cline instances found.")
|
|
295
|
+
fmt.Println("Run 'cline instance new' to start a new instance, or 'cline task new \"...\"' to auto-start one.")
|
|
296
|
+
return nil
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Build instance data
|
|
300
|
+
type instanceRow struct {
|
|
301
|
+
address string
|
|
302
|
+
status string
|
|
303
|
+
version string
|
|
304
|
+
lastSeen string
|
|
305
|
+
pid string
|
|
306
|
+
platform string
|
|
307
|
+
isDefault string
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
var rows []instanceRow
|
|
311
|
+
for _, instance := range instances {
|
|
312
|
+
isDefault := ""
|
|
313
|
+
if instance.Address == defaultInstance {
|
|
314
|
+
isDefault = "✓"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
lastSeen := instance.LastSeen.Format("15:04:05")
|
|
318
|
+
if time.Since(instance.LastSeen) > 24*time.Hour {
|
|
319
|
+
lastSeen = instance.LastSeen.Format("2006-01-02")
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Get PID and platform via RPC if instance is healthy
|
|
323
|
+
pid := platformNA
|
|
324
|
+
platform := platformNA
|
|
325
|
+
if instance.Status == grpc_health_v1.HealthCheckResponse_SERVING {
|
|
326
|
+
// Get PID from core
|
|
327
|
+
if client, err := registry.GetClient(ctx, instance.Address); err == nil {
|
|
328
|
+
if processInfo, err := client.State.GetProcessInfo(ctx, &cline.EmptyRequest{}); err == nil {
|
|
329
|
+
pid = fmt.Sprintf("%d", processInfo.ProcessId)
|
|
330
|
+
// Update version from RPC if available
|
|
331
|
+
if processInfo.Version != nil && *processInfo.Version != "" && *processInfo.Version != "unknown" {
|
|
332
|
+
instance.Version = *processInfo.Version
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Get platform from host bridge
|
|
338
|
+
if detectedPlatform, err := detectInstancePlatform(ctx, instance); err == nil {
|
|
339
|
+
platform = detectedPlatform
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
rows = append(rows, instanceRow{
|
|
344
|
+
address: instance.Address,
|
|
345
|
+
status: instance.Status.String(),
|
|
346
|
+
version: instance.Version,
|
|
347
|
+
lastSeen: lastSeen,
|
|
348
|
+
pid: pid,
|
|
349
|
+
platform: platform,
|
|
350
|
+
isDefault: isDefault,
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check output format
|
|
355
|
+
if global.Config.OutputFormat == "plain" {
|
|
356
|
+
// Use tabwriter for plain output
|
|
357
|
+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
358
|
+
fmt.Fprintln(w, "ADDRESS\tSTATUS\tVERSION\tLAST SEEN\tPID\tPLATFORM\tDEFAULT")
|
|
359
|
+
|
|
360
|
+
for _, row := range rows {
|
|
361
|
+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
|
|
362
|
+
row.address,
|
|
363
|
+
row.status,
|
|
364
|
+
row.version,
|
|
365
|
+
row.lastSeen,
|
|
366
|
+
row.pid,
|
|
367
|
+
row.platform,
|
|
368
|
+
row.isDefault,
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
w.Flush()
|
|
373
|
+
} else {
|
|
374
|
+
// Use markdown table for rich output
|
|
375
|
+
var markdown strings.Builder
|
|
376
|
+
markdown.WriteString("| **ADDRESS (ID)** | **STATUS** | **VERSION** | **LAST SEEN** | **PID** | **PLATFORM** | **DEFAULT** |\n")
|
|
377
|
+
markdown.WriteString("|---------|--------|---------|-----------|-----|----------|---------|")
|
|
378
|
+
|
|
379
|
+
for _, row := range rows {
|
|
380
|
+
markdown.WriteString(fmt.Sprintf("\n| %s | %s | %s | %s | %s | %s | %s |",
|
|
381
|
+
row.address,
|
|
382
|
+
row.status,
|
|
383
|
+
row.version,
|
|
384
|
+
row.lastSeen,
|
|
385
|
+
row.pid,
|
|
386
|
+
row.platform,
|
|
387
|
+
row.isDefault,
|
|
388
|
+
))
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Render the markdown table with terminal width for nice table layout
|
|
392
|
+
mdRenderer, err := display.NewMarkdownRendererForTerminal()
|
|
393
|
+
if err != nil {
|
|
394
|
+
// Fallback to plain table if markdown renderer fails
|
|
395
|
+
fmt.Println(markdown.String())
|
|
396
|
+
} else {
|
|
397
|
+
rendered, err := mdRenderer.Render(markdown.String())
|
|
398
|
+
if err != nil {
|
|
399
|
+
fmt.Println(markdown.String())
|
|
400
|
+
} else {
|
|
401
|
+
// Post-process to colorize status values
|
|
402
|
+
colorRenderer := display.NewRenderer(global.Config.OutputFormat)
|
|
403
|
+
rendered = strings.ReplaceAll(rendered, "SERVING", colorRenderer.Green("SERVING"))
|
|
404
|
+
rendered = strings.ReplaceAll(rendered, "✓", colorRenderer.Green("✓"))
|
|
405
|
+
rendered = strings.ReplaceAll(rendered, "NOT_SERVING", colorRenderer.Red("NOT_SERVING"))
|
|
406
|
+
rendered = strings.ReplaceAll(rendered, "UNKNOWN", colorRenderer.Yellow("UNKNOWN"))
|
|
407
|
+
|
|
408
|
+
fmt.Print(strings.TrimLeft(rendered, "\n"))
|
|
409
|
+
}
|
|
410
|
+
fmt.Println("\n")
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return nil
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return cmd
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
func newInstanceDefaultCommand() *cobra.Command {
|
|
422
|
+
cmd := &cobra.Command{
|
|
423
|
+
Use: "default <address>",
|
|
424
|
+
Aliases: []string{"d"},
|
|
425
|
+
Short: "Set the default Cline instance",
|
|
426
|
+
Long: `Set the default Cline instance to use for subsequent commands.`,
|
|
427
|
+
Args: cobra.ExactArgs(1),
|
|
428
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
429
|
+
address := args[0]
|
|
430
|
+
|
|
431
|
+
if global.Clients == nil {
|
|
432
|
+
return fmt.Errorf("clients not initialized")
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
registry := global.Clients.GetRegistry()
|
|
436
|
+
|
|
437
|
+
// Verify the instance exists
|
|
438
|
+
_, err := registry.GetInstance(address)
|
|
439
|
+
if err != nil {
|
|
440
|
+
return fmt.Errorf("instance %s not found. Run 'cline instance list' to see available instances", address)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Set as default
|
|
444
|
+
if err := registry.SetDefaultInstance(address); err != nil {
|
|
445
|
+
return fmt.Errorf("failed to set default instance: %w", err)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
fmt.Printf("Switched to instance: %s\n", address)
|
|
449
|
+
return nil
|
|
450
|
+
},
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return cmd
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
func newInstanceNewCommand() *cobra.Command {
|
|
457
|
+
var setDefault bool
|
|
458
|
+
|
|
459
|
+
cmd := &cobra.Command{
|
|
460
|
+
Use: "new",
|
|
461
|
+
Aliases: []string{"n"},
|
|
462
|
+
Short: "Create a new Cline instance",
|
|
463
|
+
Long: `Create a new Cline instance with automatically assigned ports.`,
|
|
464
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
465
|
+
ctx := cmd.Context()
|
|
466
|
+
|
|
467
|
+
if global.Clients == nil {
|
|
468
|
+
return fmt.Errorf("clients not initialized")
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
fmt.Println("Starting new Cline instance...")
|
|
472
|
+
|
|
473
|
+
instance, err := global.Clients.StartNewInstance(ctx)
|
|
474
|
+
if err != nil {
|
|
475
|
+
return fmt.Errorf("failed to start instance: %w", err)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
fmt.Printf("Successfully started new instance:\n")
|
|
479
|
+
fmt.Printf(" Address: %s\n", instance.Address)
|
|
480
|
+
fmt.Printf(" Core Port: %d\n", instance.CorePort())
|
|
481
|
+
fmt.Printf(" Host Bridge Port: %d\n", instance.HostPort())
|
|
482
|
+
|
|
483
|
+
registry := global.Clients.GetRegistry()
|
|
484
|
+
|
|
485
|
+
// If --default flag provided, set this instance as the default
|
|
486
|
+
if setDefault {
|
|
487
|
+
if err := registry.SetDefaultInstance(instance.Address); err != nil {
|
|
488
|
+
fmt.Printf("Warning: Failed to set as default: %v\n", err)
|
|
489
|
+
} else {
|
|
490
|
+
fmt.Printf(" Status: Set as default instance\n")
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
// Otherwise, check if EnsureDefaultInstance already set it as default
|
|
494
|
+
if registry.GetDefaultInstance() == instance.Address {
|
|
495
|
+
fmt.Printf(" Status: Default instance\n")
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return nil
|
|
500
|
+
},
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
cmd.Flags().BoolVarP(&setDefault, "default", "d", false, "set as default instance")
|
|
504
|
+
|
|
505
|
+
return cmd
|
|
506
|
+
}
|