@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.
Files changed (85) hide show
  1. package/.npmrc.tmp +2 -0
  2. package/README.md +72 -0
  3. package/cmd/cline/main.go +348 -0
  4. package/cmd/cline-host/main.go +71 -0
  5. package/e2e/default_update_test.go +154 -0
  6. package/e2e/helpers_test.go +378 -0
  7. package/e2e/main_test.go +47 -0
  8. package/e2e/mixed_stress_test.go +120 -0
  9. package/e2e/sqlite_helper.go +161 -0
  10. package/e2e/start_list_test.go +178 -0
  11. package/go.mod +64 -0
  12. package/go.sum +162 -0
  13. package/man/cline.1 +331 -0
  14. package/man/cline.1.md +332 -0
  15. package/package.json +54 -0
  16. package/pkg/cli/auth/auth_cline_provider.go +285 -0
  17. package/pkg/cli/auth/auth_menu.go +323 -0
  18. package/pkg/cli/auth/auth_subscription.go +130 -0
  19. package/pkg/cli/auth/byo_quick_setup.go +247 -0
  20. package/pkg/cli/auth/models_cline.go +141 -0
  21. package/pkg/cli/auth/models_list_fetch.go +156 -0
  22. package/pkg/cli/auth/models_list_static.go +69 -0
  23. package/pkg/cli/auth/providers_byo.go +184 -0
  24. package/pkg/cli/auth/providers_list.go +517 -0
  25. package/pkg/cli/auth/update_api_configurations.go +647 -0
  26. package/pkg/cli/auth/wizard_byo.go +764 -0
  27. package/pkg/cli/auth/wizard_byo_bedrock.go +193 -0
  28. package/pkg/cli/auth/wizard_byo_oca.go +366 -0
  29. package/pkg/cli/auth.go +43 -0
  30. package/pkg/cli/clerror/cline_error.go +187 -0
  31. package/pkg/cli/config/manager.go +208 -0
  32. package/pkg/cli/config/settings_renderer.go +198 -0
  33. package/pkg/cli/config.go +152 -0
  34. package/pkg/cli/display/ansi.go +27 -0
  35. package/pkg/cli/display/banner.go +211 -0
  36. package/pkg/cli/display/deduplicator.go +95 -0
  37. package/pkg/cli/display/markdown_renderer.go +139 -0
  38. package/pkg/cli/display/renderer.go +304 -0
  39. package/pkg/cli/display/segment_streamer.go +212 -0
  40. package/pkg/cli/display/streaming.go +134 -0
  41. package/pkg/cli/display/system_renderer.go +269 -0
  42. package/pkg/cli/display/tool_renderer.go +455 -0
  43. package/pkg/cli/display/tool_result_parser.go +371 -0
  44. package/pkg/cli/display/typewriter.go +210 -0
  45. package/pkg/cli/doctor.go +65 -0
  46. package/pkg/cli/global/cline-clients.go +501 -0
  47. package/pkg/cli/global/global.go +113 -0
  48. package/pkg/cli/global/registry.go +304 -0
  49. package/pkg/cli/handlers/ask_handlers.go +339 -0
  50. package/pkg/cli/handlers/handler.go +130 -0
  51. package/pkg/cli/handlers/say_handlers.go +521 -0
  52. package/pkg/cli/instances.go +506 -0
  53. package/pkg/cli/logs.go +382 -0
  54. package/pkg/cli/output/coordinator.go +167 -0
  55. package/pkg/cli/output/input_model.go +497 -0
  56. package/pkg/cli/sqlite/locks.go +366 -0
  57. package/pkg/cli/task/history_handler.go +72 -0
  58. package/pkg/cli/task/input_handler.go +577 -0
  59. package/pkg/cli/task/manager.go +1283 -0
  60. package/pkg/cli/task/settings_parser.go +754 -0
  61. package/pkg/cli/task/stream_coordinator.go +60 -0
  62. package/pkg/cli/task.go +675 -0
  63. package/pkg/cli/terminal/keyboard.go +695 -0
  64. package/pkg/cli/tui/HELP_WANTED.md +1 -0
  65. package/pkg/cli/types/history.go +17 -0
  66. package/pkg/cli/types/messages.go +329 -0
  67. package/pkg/cli/types/state.go +59 -0
  68. package/pkg/cli/updater/updater.go +409 -0
  69. package/pkg/cli/version.go +43 -0
  70. package/pkg/common/constants.go +6 -0
  71. package/pkg/common/schema.go +54 -0
  72. package/pkg/common/types.go +54 -0
  73. package/pkg/common/utils.go +185 -0
  74. package/pkg/generated/field_overrides.go +39 -0
  75. package/pkg/generated/providers.go +1584 -0
  76. package/pkg/hostbridge/diff.go +351 -0
  77. package/pkg/hostbridge/disabled/watch.go +39 -0
  78. package/pkg/hostbridge/disabled/window.go +63 -0
  79. package/pkg/hostbridge/disabled/workspace.go +66 -0
  80. package/pkg/hostbridge/env.go +166 -0
  81. package/pkg/hostbridge/grpc_server.go +113 -0
  82. package/pkg/hostbridge/simple.go +43 -0
  83. package/pkg/hostbridge/simple_workspace.go +85 -0
  84. package/pkg/hostbridge/window.go +129 -0
  85. 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
+ }