@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,382 @@
1
+ package cli
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "sort"
8
+ "strings"
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/spf13/cobra"
15
+ )
16
+
17
+ type logFileInfo struct {
18
+ name string
19
+ path string
20
+ size int64
21
+ created time.Time
22
+ }
23
+
24
+ func NewLogsCommand() *cobra.Command {
25
+ cmd := &cobra.Command{
26
+ Use: "logs",
27
+ Aliases: []string{"log", "l"},
28
+ Short: "Manage Cline log files",
29
+ Long: `List and manage log files created by Cline instances.`,
30
+ }
31
+
32
+ cmd.AddCommand(newLogsListCommand())
33
+ cmd.AddCommand(newLogsCleanCommand())
34
+ cmd.AddCommand(newLogsPathCommand())
35
+
36
+ return cmd
37
+ }
38
+
39
+ func newLogsListCommand() *cobra.Command {
40
+ cmd := &cobra.Command{
41
+ Use: "list",
42
+ Aliases: []string{"l", "ls"},
43
+ Short: "List all log files",
44
+ Long: `List all log files in the Cline logs directory with their sizes and ages.`,
45
+ RunE: func(cmd *cobra.Command, args []string) error {
46
+ if global.Config == nil {
47
+ return fmt.Errorf("config not initialized")
48
+ }
49
+
50
+ logsDir := filepath.Join(global.Config.ConfigPath, "logs")
51
+ logs, err := listLogFiles(logsDir)
52
+ if err != nil {
53
+ return fmt.Errorf("failed to list log files: %w", err)
54
+ }
55
+
56
+ if len(logs) == 0 {
57
+ fmt.Println("No log files found.")
58
+ fmt.Printf("Log files will be created in: %s\n", logsDir)
59
+ return nil
60
+ }
61
+
62
+ return renderLogsTable(logs, false)
63
+ },
64
+ }
65
+
66
+ return cmd
67
+ }
68
+
69
+ func newLogsCleanCommand() *cobra.Command {
70
+ var olderThan int
71
+ var all bool
72
+ var dryRun bool
73
+
74
+ cmd := &cobra.Command{
75
+ Use: "clean",
76
+ Aliases: []string{"c"},
77
+ Short: "Delete old log files",
78
+ Long: `Delete log files older than a specified number of days.`,
79
+ RunE: func(cmd *cobra.Command, args []string) error {
80
+ if global.Config == nil {
81
+ return fmt.Errorf("config not initialized")
82
+ }
83
+
84
+ logsDir := filepath.Join(global.Config.ConfigPath, "logs")
85
+ logs, err := listLogFiles(logsDir)
86
+ if err != nil {
87
+ return fmt.Errorf("failed to list log files: %w", err)
88
+ }
89
+
90
+ var toDelete []logFileInfo
91
+ if all {
92
+ toDelete = logs
93
+ } else {
94
+ toDelete = filterOldLogs(logs, olderThan)
95
+ }
96
+
97
+ if len(toDelete) == 0 {
98
+ if all {
99
+ fmt.Println("No log files to delete.")
100
+ } else {
101
+ fmt.Printf("No log files older than %d days found.\n", olderThan)
102
+ }
103
+ return nil
104
+ }
105
+
106
+ // Calculate total size
107
+ var totalSize int64
108
+ for _, log := range toDelete {
109
+ totalSize += log.size
110
+ }
111
+
112
+ if dryRun {
113
+ fmt.Println("The following log files will be deleted:\n")
114
+ if err := renderLogsTable(toDelete, true); err != nil {
115
+ return err
116
+ }
117
+ fileWord := "files"
118
+ if len(toDelete) == 1 {
119
+ fileWord = "file"
120
+ }
121
+ fmt.Printf("\nSummary: %d %s will be deleted (%s freed)\n", len(toDelete), fileWord, formatFileSize(totalSize))
122
+ fmt.Println("\nRun without --dry-run to actually delete these files.")
123
+ return nil
124
+ }
125
+
126
+ // Actually delete the files
127
+ count, bytesFreed, err := deleteLogFiles(toDelete)
128
+ if err != nil {
129
+ return fmt.Errorf("failed to delete log files: %w", err)
130
+ }
131
+
132
+ fileWord := "files"
133
+ if count == 1 {
134
+ fileWord = "file"
135
+ }
136
+ fmt.Printf("Deleted %d log %s (%s freed)\n", count, fileWord, formatFileSize(bytesFreed))
137
+ return nil
138
+ },
139
+ }
140
+
141
+ cmd.Flags().IntVar(&olderThan, "older-than", 7, "delete logs older than N days")
142
+ cmd.Flags().BoolVar(&all, "all", false, "delete all log files")
143
+ cmd.Flags().BoolVar(&dryRun, "dry-run", false, "show what would be deleted without deleting")
144
+
145
+ return cmd
146
+ }
147
+
148
+ func newLogsPathCommand() *cobra.Command {
149
+ cmd := &cobra.Command{
150
+ Use: "path",
151
+ Short: "Print the logs directory path",
152
+ Long: `Print the absolute path to the Cline logs directory.`,
153
+ RunE: func(cmd *cobra.Command, args []string) error {
154
+ if global.Config == nil {
155
+ return fmt.Errorf("config not initialized")
156
+ }
157
+
158
+ logsDir := filepath.Join(global.Config.ConfigPath, "logs")
159
+ fmt.Println(logsDir)
160
+ return nil
161
+ },
162
+ }
163
+
164
+ return cmd
165
+ }
166
+
167
+ // Helper functions
168
+
169
+ func listLogFiles(logsDir string) ([]logFileInfo, error) {
170
+ // Check if logs directory exists
171
+ if _, err := os.Stat(logsDir); os.IsNotExist(err) {
172
+ return []logFileInfo{}, nil
173
+ }
174
+
175
+ entries, err := os.ReadDir(logsDir)
176
+ if err != nil {
177
+ return nil, err
178
+ }
179
+
180
+ var logs []logFileInfo
181
+ for _, entry := range entries {
182
+ if entry.IsDir() {
183
+ continue
184
+ }
185
+
186
+ // Only process .log files
187
+ if !strings.HasSuffix(entry.Name(), ".log") {
188
+ continue
189
+ }
190
+
191
+ // Parse timestamp from filename
192
+ created, err := parseTimestampFromFilename(entry.Name())
193
+ if err != nil {
194
+ // Skip files we can't parse
195
+ continue
196
+ }
197
+
198
+ info, err := entry.Info()
199
+ if err != nil {
200
+ continue
201
+ }
202
+
203
+ logs = append(logs, logFileInfo{
204
+ name: entry.Name(),
205
+ path: filepath.Join(logsDir, entry.Name()),
206
+ size: info.Size(),
207
+ created: created,
208
+ })
209
+ }
210
+
211
+ // Sort by created time (oldest first)
212
+ sort.Slice(logs, func(i, j int) bool {
213
+ return logs[i].created.Before(logs[j].created)
214
+ })
215
+
216
+ return logs, nil
217
+ }
218
+
219
+ func parseTimestampFromFilename(filename string) (time.Time, error) {
220
+ // Expected format: cline-core-2025-10-12-21-30-45-localhost-51051.log
221
+ // or: cline-host-2025-10-12-21-30-45-localhost-52051.log
222
+
223
+ parts := strings.Split(filename, "-")
224
+ if len(parts) < 8 {
225
+ return time.Time{}, fmt.Errorf("invalid filename format")
226
+ }
227
+
228
+ // Extract timestamp parts: YYYY-MM-DD-HH-mm-ss
229
+ // They should be at indices 2-7
230
+ timestampStr := strings.Join(parts[2:8], "-")
231
+
232
+ // Parse as local time since the filename timestamp is created in local time
233
+ parsedTime, err := time.ParseInLocation("2006-01-02-15-04-05", timestampStr, time.Local)
234
+ if err != nil {
235
+ return time.Time{}, err
236
+ }
237
+
238
+ return parsedTime, nil
239
+ }
240
+
241
+ func filterOldLogs(logs []logFileInfo, olderThanDays int) []logFileInfo {
242
+ cutoff := time.Now().AddDate(0, 0, -olderThanDays)
243
+ var filtered []logFileInfo
244
+
245
+ for _, log := range logs {
246
+ if log.created.Before(cutoff) {
247
+ filtered = append(filtered, log)
248
+ }
249
+ }
250
+
251
+ return filtered
252
+ }
253
+
254
+ func deleteLogFiles(files []logFileInfo) (int, int64, error) {
255
+ var count int
256
+ var bytesFreed int64
257
+
258
+ for _, file := range files {
259
+ if err := os.Remove(file.path); err != nil {
260
+ return count, bytesFreed, err
261
+ }
262
+ count++
263
+ bytesFreed += file.size
264
+ }
265
+
266
+ return count, bytesFreed, nil
267
+ }
268
+
269
+ func formatFileSize(bytes int64) string {
270
+ const unit = 1024
271
+ if bytes < unit {
272
+ return fmt.Sprintf("%d B", bytes)
273
+ }
274
+ div, exp := int64(unit), 0
275
+ for n := bytes / unit; n >= unit; n /= unit {
276
+ div *= unit
277
+ exp++
278
+ }
279
+ return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
280
+ }
281
+
282
+ func formatAge(t time.Time) string {
283
+ duration := time.Since(t)
284
+
285
+ if duration < time.Hour {
286
+ minutes := int(duration.Minutes())
287
+ return fmt.Sprintf("%dm ago", minutes)
288
+ }
289
+
290
+ if duration < 24*time.Hour {
291
+ hours := int(duration.Hours())
292
+ return fmt.Sprintf("%dh ago", hours)
293
+ }
294
+
295
+ if duration < 7*24*time.Hour {
296
+ days := int(duration.Hours() / 24)
297
+ return fmt.Sprintf("%dd ago", days)
298
+ }
299
+
300
+ weeks := int(duration.Hours() / 24 / 7)
301
+ return fmt.Sprintf("%dw ago", weeks)
302
+ }
303
+
304
+ func renderLogsTable(logs []logFileInfo, markForDeletion bool) error {
305
+ // Build table data
306
+ type tableRow struct {
307
+ filename string
308
+ size string
309
+ created string
310
+ age string
311
+ }
312
+
313
+ var rows []tableRow
314
+ for _, log := range logs {
315
+ rows = append(rows, tableRow{
316
+ filename: log.name,
317
+ size: formatFileSize(log.size),
318
+ created: log.created.Format("2006-01-02 15:04:05"),
319
+ age: formatAge(log.created),
320
+ })
321
+ }
322
+
323
+ // Check output format
324
+ if global.Config.OutputFormat == "plain" {
325
+ // Use tabwriter for plain output
326
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
327
+ fmt.Fprintln(w, "FILENAME\tSIZE\tCREATED\tAGE")
328
+
329
+ for _, row := range rows {
330
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
331
+ row.filename,
332
+ row.size,
333
+ row.created,
334
+ row.age,
335
+ )
336
+ }
337
+
338
+ w.Flush()
339
+ return nil
340
+ }
341
+
342
+ // Use markdown table for rich output
343
+ colorRenderer := display.NewRenderer(global.Config.OutputFormat)
344
+ var markdown strings.Builder
345
+ markdown.WriteString("| **FILENAME** | **SIZE** | **CREATED** | **AGE** |\n")
346
+ markdown.WriteString("|--------------|----------|-------------|---------|")
347
+
348
+ for _, row := range rows {
349
+ line := fmt.Sprintf("\n| %s | %s | %s | %s |",
350
+ row.filename,
351
+ row.size,
352
+ row.created,
353
+ row.age,
354
+ )
355
+
356
+ // If marking for deletion, wrap in red
357
+ if markForDeletion {
358
+ line = colorRenderer.Red(line)
359
+ }
360
+
361
+ markdown.WriteString(line)
362
+ }
363
+
364
+ // Render the markdown table
365
+ renderer, err := display.NewMarkdownRendererForTerminal()
366
+ if err != nil {
367
+ // Fallback to plain markdown if renderer fails
368
+ fmt.Println(markdown.String())
369
+ return nil
370
+ }
371
+
372
+ rendered, err := renderer.Render(markdown.String())
373
+ if err != nil {
374
+ fmt.Println(markdown.String())
375
+ return nil
376
+ }
377
+
378
+ fmt.Print(strings.TrimLeft(rendered, "\n"))
379
+ fmt.Println()
380
+
381
+ return nil
382
+ }
@@ -0,0 +1,167 @@
1
+ package output
2
+
3
+ import (
4
+ "fmt"
5
+ "sync"
6
+ "sync/atomic"
7
+ "time"
8
+
9
+ tea "github.com/charmbracelet/bubbletea"
10
+ )
11
+
12
+ // SuspendInputMsg tells the input model to suspend and hide
13
+ type SuspendInputMsg struct{}
14
+
15
+ // ResumeInputMsg tells the input model to resume and show
16
+ type ResumeInputMsg struct{}
17
+
18
+ // OutputCoordinator manages terminal output and coordinates with interactive input
19
+ type OutputCoordinator struct {
20
+ mu sync.Mutex
21
+ program *tea.Program
22
+ inputVisible atomic.Bool
23
+ inputModel *InputModel // Reference to current input model for state restoration
24
+ restartCallback func(*InputModel) // Callback to restart the program with preserved state
25
+ }
26
+
27
+ var (
28
+ globalCoordinator *OutputCoordinator
29
+ coordinatorMu sync.Mutex
30
+ )
31
+
32
+ // GetCoordinator returns the global output coordinator instance
33
+ func GetCoordinator() *OutputCoordinator {
34
+ coordinatorMu.Lock()
35
+ defer coordinatorMu.Unlock()
36
+
37
+ if globalCoordinator == nil {
38
+ globalCoordinator = &OutputCoordinator{}
39
+ }
40
+ return globalCoordinator
41
+ }
42
+
43
+ // SetProgram sets the bubbletea program for input coordination
44
+ func (oc *OutputCoordinator) SetProgram(program *tea.Program) {
45
+ oc.mu.Lock()
46
+ defer oc.mu.Unlock()
47
+ oc.program = program
48
+ }
49
+
50
+ // SetInputModel sets the current input model reference for state preservation
51
+ func (oc *OutputCoordinator) SetInputModel(model *InputModel) {
52
+ oc.mu.Lock()
53
+ defer oc.mu.Unlock()
54
+ oc.inputModel = model
55
+ }
56
+
57
+ // SetRestartCallback sets the callback for restarting the program
58
+ func (oc *OutputCoordinator) SetRestartCallback(callback func(*InputModel)) {
59
+ oc.mu.Lock()
60
+ defer oc.mu.Unlock()
61
+ oc.restartCallback = callback
62
+ }
63
+
64
+ // SetInputVisible sets whether input is currently visible
65
+ func (oc *OutputCoordinator) SetInputVisible(visible bool) {
66
+ oc.inputVisible.Store(visible)
67
+ }
68
+
69
+ // IsInputVisible returns whether input is currently visible
70
+ func (oc *OutputCoordinator) IsInputVisible() bool {
71
+ return oc.inputVisible.Load()
72
+ }
73
+
74
+ // Printf prints formatted output, suspending input if necessary
75
+ func (oc *OutputCoordinator) Printf(format string, args ...interface{}) {
76
+ oc.mu.Lock()
77
+ prog := oc.program
78
+ model := oc.inputModel
79
+ restart := oc.restartCallback
80
+ visible := oc.inputVisible.Load()
81
+ oc.mu.Unlock()
82
+
83
+ if visible && prog != nil && restart != nil && model != nil {
84
+ // Kill/restart approach: completely stop the program, print, restart with state
85
+
86
+ // 1. Save the current input state (text, cursor position, etc.)
87
+ savedModel := model.Clone()
88
+
89
+ // 2. Manually clear the form from terminal BEFORE quitting
90
+ clearCodes := model.ClearScreen()
91
+ if clearCodes != "" {
92
+ fmt.Print(clearCodes)
93
+ }
94
+
95
+ // 3. Quit the program
96
+ prog.Send(Quit())
97
+
98
+ // Small delay to let program actually quit
99
+ time.Sleep(20 * time.Millisecond)
100
+
101
+ // 4. Print the output
102
+ fmt.Printf(format, args...)
103
+
104
+ // 5. Restart with preserved state
105
+ restart(savedModel)
106
+ } else {
107
+ // No input showing, just print normally
108
+ fmt.Printf(format, args...)
109
+ }
110
+ }
111
+
112
+ // Println prints a line with newline, suspending input if necessary
113
+ func (oc *OutputCoordinator) Println(args ...interface{}) {
114
+ oc.Printf("%s\n", fmt.Sprint(args...))
115
+ }
116
+
117
+ // Print prints output, suspending input if necessary
118
+ func (oc *OutputCoordinator) Print(args ...interface{}) {
119
+ oc.Printf("%s", fmt.Sprint(args...))
120
+ }
121
+
122
+ // Package-level convenience functions
123
+
124
+ // Printf prints formatted output via the global coordinator
125
+ func Printf(format string, args ...interface{}) {
126
+ GetCoordinator().Printf(format, args...)
127
+ }
128
+
129
+ // Println prints a line with newline via the global coordinator
130
+ func Println(args ...interface{}) {
131
+ GetCoordinator().Println(args...)
132
+ }
133
+
134
+ // Print prints output via the global coordinator
135
+ func Print(args ...interface{}) {
136
+ GetCoordinator().Print(args...)
137
+ }
138
+
139
+ // SetProgram sets the bubbletea program on the global coordinator
140
+ func SetProgram(program *tea.Program) {
141
+ GetCoordinator().SetProgram(program)
142
+ }
143
+
144
+ // SetInputVisible sets input visibility on the global coordinator
145
+ func SetInputVisible(visible bool) {
146
+ GetCoordinator().SetInputVisible(visible)
147
+ }
148
+
149
+ // IsInputVisible checks input visibility on the global coordinator
150
+ func IsInputVisible() bool {
151
+ return GetCoordinator().IsInputVisible()
152
+ }
153
+
154
+ // SetInputModel sets the input model on the global coordinator
155
+ func SetInputModel(model *InputModel) {
156
+ GetCoordinator().SetInputModel(model)
157
+ }
158
+
159
+ // SetRestartCallback sets the restart callback on the global coordinator
160
+ func SetRestartCallback(callback func(*InputModel)) {
161
+ GetCoordinator().SetRestartCallback(callback)
162
+ }
163
+
164
+ // Quit returns a Bubble Tea quit message
165
+ func Quit() tea.Msg {
166
+ return tea.Quit()
167
+ }