@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,371 @@
1
+ package display
2
+
3
+ import (
4
+ "fmt"
5
+ "path/filepath"
6
+ "strings"
7
+
8
+ "github.com/cline/cli/pkg/cli/types"
9
+ )
10
+
11
+ // ToolResultParser handles parsing and formatting tool results for display
12
+ type ToolResultParser struct {
13
+ maxPreviewLines int
14
+ maxPreviewChars int
15
+ mdRenderer *MarkdownRenderer
16
+ }
17
+
18
+ // NewToolResultParser creates a new tool result parser
19
+ func NewToolResultParser(mdRenderer *MarkdownRenderer) *ToolResultParser {
20
+ return &ToolResultParser{
21
+ maxPreviewLines: 15,
22
+ maxPreviewChars: 500,
23
+ mdRenderer: mdRenderer,
24
+ }
25
+ }
26
+
27
+ // ParseReadFile formats readFile tool results with smart preview
28
+ func (p *ToolResultParser) ParseReadFile(content, path string) string {
29
+ lines := strings.Split(content, "\n")
30
+ totalLines := len(lines)
31
+
32
+ // Get file extension for syntax highlighting
33
+ ext := filepath.Ext(path)
34
+ lang := p.detectLanguage(ext)
35
+
36
+ var preview strings.Builder
37
+
38
+ // Show header with line count
39
+ preview.WriteString(fmt.Sprintf("*%d lines*\n\n", totalLines))
40
+
41
+ // Show preview of content
42
+ previewLines := p.maxPreviewLines
43
+ if totalLines < previewLines {
44
+ previewLines = totalLines
45
+ }
46
+
47
+ preview.WriteString(fmt.Sprintf("```%s\n", lang))
48
+ for i := 0; i < previewLines; i++ {
49
+ preview.WriteString(lines[i])
50
+ preview.WriteString("\n")
51
+ }
52
+
53
+ if totalLines > previewLines {
54
+ preview.WriteString("...\n")
55
+ }
56
+ preview.WriteString("```\n")
57
+
58
+ if totalLines > previewLines {
59
+ preview.WriteString(fmt.Sprintf("\n*[Content truncated - showing %d of %d lines]*", previewLines, totalLines))
60
+ }
61
+
62
+ return preview.String()
63
+ }
64
+
65
+ // ParseListFiles formats listFiles tool results with directory tree
66
+ func (p *ToolResultParser) ParseListFiles(content, path string) string {
67
+ if content == "" || content == "No files found." {
68
+ return "*No files found*"
69
+ }
70
+
71
+ lines := strings.Split(strings.TrimSpace(content), "\n")
72
+
73
+ // Check for truncation message
74
+ var truncationMsg string
75
+ lastLine := lines[len(lines)-1]
76
+ if strings.Contains(lastLine, "File list truncated") {
77
+ truncationMsg = lastLine
78
+ lines = lines[:len(lines)-1]
79
+ }
80
+
81
+ totalFiles := len(lines)
82
+
83
+ var result strings.Builder
84
+ result.WriteString(fmt.Sprintf("*%d %s*\n\n", totalFiles, p.pluralize(totalFiles, "file", "files")))
85
+
86
+ // Show up to 20 files in tree format
87
+ maxShow := 20
88
+ if totalFiles < maxShow {
89
+ maxShow = totalFiles
90
+ }
91
+
92
+ result.WriteString("```\n")
93
+ for i := 0; i < maxShow; i++ {
94
+ line := lines[i]
95
+ // Add tree characters for better visualization
96
+ if strings.HasPrefix(line, "🔒 ") {
97
+ result.WriteString("├── 🔒 ")
98
+ result.WriteString(strings.TrimPrefix(line, "🔒 "))
99
+ } else {
100
+ result.WriteString("├── ")
101
+ result.WriteString(line)
102
+ }
103
+ result.WriteString("\n")
104
+ }
105
+
106
+ if totalFiles > maxShow {
107
+ result.WriteString("└── ...\n")
108
+ }
109
+ result.WriteString("```\n")
110
+
111
+ if totalFiles > maxShow {
112
+ result.WriteString(fmt.Sprintf("\n*[Showing %d of %d files]*", maxShow, totalFiles))
113
+ }
114
+
115
+ if truncationMsg != "" {
116
+ result.WriteString(fmt.Sprintf("\n\n*%s*", truncationMsg))
117
+ }
118
+
119
+ return result.String()
120
+ }
121
+
122
+ // ParseSearchFiles formats searchFiles tool results with context
123
+ func (p *ToolResultParser) ParseSearchFiles(content string) string {
124
+ if content == "" || content == "Found 0 results." {
125
+ return "*No results found*"
126
+ }
127
+
128
+ lines := strings.Split(content, "\n")
129
+ if len(lines) == 0 {
130
+ return "*No results found*"
131
+ }
132
+
133
+ // Extract result count from first line
134
+ firstLine := lines[0]
135
+
136
+ var result strings.Builder
137
+ result.WriteString(fmt.Sprintf("*%s*\n\n", firstLine))
138
+
139
+ // Parse and group results by file
140
+ var currentFile string
141
+ var fileResults []string
142
+ filesShown := 0
143
+ maxFiles := 5
144
+ matchesShown := 0
145
+ maxMatches := 15
146
+
147
+ for i := 1; i < len(lines) && filesShown < maxFiles && matchesShown < maxMatches; i++ {
148
+ line := lines[i]
149
+
150
+ if line == "" {
151
+ continue
152
+ }
153
+
154
+ // Check if this is a file path (doesn't start with whitespace or line number)
155
+ if !strings.HasPrefix(line, " ") && !strings.HasPrefix(line, "\t") && strings.Contains(line, ":") {
156
+ // Save previous file results
157
+ if currentFile != "" && len(fileResults) > 0 {
158
+ result.WriteString(p.formatFileMatches(currentFile, fileResults))
159
+ filesShown++
160
+ }
161
+
162
+ currentFile = line
163
+ fileResults = []string{}
164
+ } else if currentFile != "" {
165
+ // This is a match line
166
+ fileResults = append(fileResults, strings.TrimSpace(line))
167
+ matchesShown++
168
+ }
169
+ }
170
+
171
+ // Add last file's results
172
+ if currentFile != "" && len(fileResults) > 0 && filesShown < maxFiles {
173
+ result.WriteString(p.formatFileMatches(currentFile, fileResults))
174
+ filesShown++
175
+ }
176
+
177
+ // Add truncation notice
178
+ totalMatches := strings.Count(content, "\n") - 1 // Rough estimate
179
+ if matchesShown < totalMatches {
180
+ result.WriteString(fmt.Sprintf("\n*[Showing %d results - see full output for all matches]*", matchesShown))
181
+ }
182
+
183
+ return result.String()
184
+ }
185
+
186
+ // formatFileMatches formats matches for a single file
187
+ func (p *ToolResultParser) formatFileMatches(file string, matches []string) string {
188
+ var result strings.Builder
189
+
190
+ // Parse file path and extension for syntax highlighting
191
+ ext := filepath.Ext(file)
192
+ lang := p.detectLanguage(ext)
193
+
194
+ result.WriteString(fmt.Sprintf("**%s** (%d %s)\n", file, len(matches), p.pluralize(len(matches), "match", "matches")))
195
+ result.WriteString(fmt.Sprintf("```%s\n", lang))
196
+
197
+ maxMatches := 5
198
+ for i, match := range matches {
199
+ if i >= maxMatches {
200
+ result.WriteString("...\n")
201
+ break
202
+ }
203
+ result.WriteString(match)
204
+ result.WriteString("\n")
205
+ }
206
+
207
+ result.WriteString("```\n\n")
208
+
209
+ return result.String()
210
+ }
211
+
212
+ // ParseCodeDefinitions formats listCodeDefinitionNames tool results
213
+ func (p *ToolResultParser) ParseCodeDefinitions(content string) string {
214
+ if content == "" || content == "No source code definitions found." {
215
+ return "*No code definitions found*"
216
+ }
217
+
218
+ // Return the full content as-is
219
+ return content
220
+ }
221
+
222
+ // ParseWebFetch formats webFetch tool results with content preview
223
+ func (p *ToolResultParser) ParseWebFetch(content, url string) string {
224
+ if content == "" {
225
+ return fmt.Sprintf("*Fetched content from %s (empty response)*", url)
226
+ }
227
+
228
+ lines := strings.Split(content, "\n")
229
+
230
+ var result strings.Builder
231
+
232
+ // Try to extract title
233
+ var title string
234
+ for _, line := range lines {
235
+ trimmed := strings.TrimSpace(line)
236
+ if strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "##") {
237
+ title = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
238
+ break
239
+ }
240
+ }
241
+
242
+ if title != "" {
243
+ result.WriteString(fmt.Sprintf("**Title:** %s\n\n", title))
244
+ }
245
+
246
+ // Show preview of content
247
+ result.WriteString("**Preview:**\n")
248
+
249
+ charCount := 0
250
+ maxChars := 500
251
+ previewLines := []string{}
252
+
253
+ for _, line := range lines {
254
+ // Skip markdown headers
255
+ if strings.HasPrefix(strings.TrimSpace(line), "#") {
256
+ continue
257
+ }
258
+
259
+ trimmed := strings.TrimSpace(line)
260
+ if trimmed == "" {
261
+ continue
262
+ }
263
+
264
+ if charCount+len(trimmed) > maxChars {
265
+ break
266
+ }
267
+
268
+ previewLines = append(previewLines, trimmed)
269
+ charCount += len(trimmed)
270
+ }
271
+
272
+ result.WriteString(strings.Join(previewLines, " "))
273
+ result.WriteString("...\n\n")
274
+
275
+ // Extract sections
276
+ sections := []string{}
277
+ for _, line := range lines {
278
+ trimmed := strings.TrimSpace(line)
279
+ if strings.HasPrefix(trimmed, "##") {
280
+ section := strings.TrimSpace(strings.TrimPrefix(trimmed, "##"))
281
+ sections = append(sections, section)
282
+ if len(sections) >= 5 {
283
+ break
284
+ }
285
+ }
286
+ }
287
+
288
+ if len(sections) > 0 {
289
+ result.WriteString("**Sections Found:**\n")
290
+ for _, section := range sections {
291
+ result.WriteString(fmt.Sprintf("- %s\n", section))
292
+ }
293
+ result.WriteString("\n")
294
+ }
295
+
296
+ // Word count estimate
297
+ wordCount := len(strings.Fields(content))
298
+ result.WriteString(fmt.Sprintf("*[Full content: ~%s]*", p.formatWordCount(wordCount)))
299
+
300
+ return result.String()
301
+ }
302
+
303
+ // detectLanguage returns syntax highlighting language based on file extension
304
+ func (p *ToolResultParser) detectLanguage(ext string) string {
305
+ langMap := map[string]string{
306
+ ".ts": "typescript",
307
+ ".tsx": "tsx",
308
+ ".js": "javascript",
309
+ ".jsx": "jsx",
310
+ ".go": "go",
311
+ ".py": "python",
312
+ ".rb": "ruby",
313
+ ".java": "java",
314
+ ".c": "c",
315
+ ".cpp": "cpp",
316
+ ".cs": "csharp",
317
+ ".php": "php",
318
+ ".sh": "bash",
319
+ ".bash": "bash",
320
+ ".zsh": "bash",
321
+ ".json": "json",
322
+ ".yaml": "yaml",
323
+ ".yml": "yaml",
324
+ ".xml": "xml",
325
+ ".html": "html",
326
+ ".css": "css",
327
+ ".scss": "scss",
328
+ ".md": "markdown",
329
+ ".sql": "sql",
330
+ ".rs": "rust",
331
+ }
332
+
333
+ if lang, ok := langMap[ext]; ok {
334
+ return lang
335
+ }
336
+ return ""
337
+ }
338
+
339
+ // pluralize returns the correct plural form
340
+ func (p *ToolResultParser) pluralize(count int, singular, plural string) string {
341
+ if count == 1 {
342
+ return singular
343
+ }
344
+ return plural
345
+ }
346
+
347
+ // formatWordCount formats word count with appropriate unit
348
+ func (p *ToolResultParser) formatWordCount(count int) string {
349
+ if count < 1000 {
350
+ return fmt.Sprintf("%d words", count)
351
+ }
352
+ return fmt.Sprintf("%.1fk words", float64(count)/1000.0)
353
+ }
354
+
355
+ // ParseToolResult is the main entry point for parsing tool results
356
+ func (p *ToolResultParser) ParseToolResult(tool *types.ToolMessage) string {
357
+ switch tool.Tool {
358
+ case "readFile":
359
+ return p.ParseReadFile(tool.Content, tool.Path)
360
+ case "listFilesTopLevel", "listFilesRecursive":
361
+ return p.ParseListFiles(tool.Content, tool.Path)
362
+ case "searchFiles":
363
+ return p.ParseSearchFiles(tool.Content)
364
+ case "listCodeDefinitionNames":
365
+ return p.ParseCodeDefinitions(tool.Content)
366
+ case "webFetch":
367
+ return p.ParseWebFetch(tool.Content, tool.Path)
368
+ default:
369
+ return tool.Content
370
+ }
371
+ }
@@ -0,0 +1,210 @@
1
+ package display
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "time"
7
+ )
8
+
9
+ // TypewriterConfig holds configuration for the typewriter effect
10
+ type TypewriterConfig struct {
11
+ BaseDelay time.Duration // Base delay between characters
12
+ FastDelay time.Duration // Faster delay for common characters
13
+ SlowDelay time.Duration // Slower delay for punctuation
14
+ PauseDelay time.Duration // Pause after sentences
15
+ Enabled bool // Whether typewriter effect is enabled
16
+ RandomFactor float64 // Randomness factor (0.0 to 1.0)
17
+ }
18
+
19
+ // DefaultTypewriterConfig returns the default typewriter configuration
20
+ func DefaultTypewriterConfig() *TypewriterConfig {
21
+ return &TypewriterConfig{
22
+ BaseDelay: 15 * time.Millisecond,
23
+ FastDelay: 8 * time.Millisecond,
24
+ SlowDelay: 25 * time.Millisecond,
25
+ PauseDelay: 150 * time.Millisecond,
26
+ Enabled: false,
27
+ RandomFactor: 0.3,
28
+ }
29
+ }
30
+
31
+ // TypewriterPrinter handles typewriter-style output
32
+ type TypewriterPrinter struct {
33
+ config *TypewriterConfig
34
+ }
35
+
36
+ // NewTypewriterPrinter creates a new typewriter printer
37
+ func NewTypewriterPrinter(config *TypewriterConfig) *TypewriterPrinter {
38
+ if config == nil {
39
+ config = DefaultTypewriterConfig()
40
+ }
41
+ return &TypewriterPrinter{
42
+ config: config,
43
+ }
44
+ }
45
+
46
+ // Print prints text with typewriter effect
47
+ func (tp *TypewriterPrinter) Print(text string) {
48
+ if !tp.config.Enabled {
49
+ fmt.Print(text)
50
+ return
51
+ }
52
+
53
+ tp.typewriterPrint(text)
54
+ }
55
+
56
+ // Printf prints formatted text with typewriter effect
57
+ func (tp *TypewriterPrinter) Printf(format string, args ...interface{}) {
58
+ text := fmt.Sprintf(format, args...)
59
+ tp.Print(text)
60
+ }
61
+
62
+ // Println prints text with typewriter effect and adds a newline
63
+ func (tp *TypewriterPrinter) Println(text string) {
64
+ tp.Print(text + "\n")
65
+ }
66
+
67
+ // PrintfLn prints formatted text with typewriter effect and adds a newline
68
+ func (tp *TypewriterPrinter) PrintfLn(format string, args ...interface{}) {
69
+ text := fmt.Sprintf(format, args...)
70
+ tp.Println(text)
71
+ }
72
+
73
+ // PrintInstant prints text immediately without typewriter effect
74
+ func (tp *TypewriterPrinter) PrintInstant(text string) {
75
+ fmt.Print(text)
76
+ }
77
+
78
+ // PrintfInstant prints formatted text immediately without typewriter effect
79
+ func (tp *TypewriterPrinter) PrintfInstant(format string, args ...interface{}) {
80
+ fmt.Printf(format, args...)
81
+ }
82
+
83
+ // typewriterPrint displays text with a typewriter animation effect
84
+ func (tp *TypewriterPrinter) typewriterPrint(text string) {
85
+ // Convert string to runes to handle Unicode properly
86
+ runes := []rune(text)
87
+
88
+ for i, r := range runes {
89
+ // Print the character
90
+ fmt.Print(string(r))
91
+ os.Stdout.Sync() // Force immediate output
92
+
93
+ // Don't add delay after the last character
94
+ if i == len(runes)-1 {
95
+ break
96
+ }
97
+
98
+ // Determine delay based on character type
99
+ delay := tp.getDelayForCharacter(r, i)
100
+
101
+ // Sleep for the calculated delay
102
+ time.Sleep(delay)
103
+ }
104
+ }
105
+
106
+ // getDelayForCharacter returns the appropriate delay for a character
107
+ func (tp *TypewriterPrinter) getDelayForCharacter(r rune, position int) time.Duration {
108
+ var baseDelay time.Duration
109
+
110
+ switch {
111
+ case r == '.' || r == '!' || r == '?':
112
+ // Longer pause after sentence endings
113
+ baseDelay = tp.config.PauseDelay
114
+ case r == ',' || r == ';' || r == ':':
115
+ // Medium pause after punctuation
116
+ baseDelay = tp.config.SlowDelay
117
+ case r == ' ':
118
+ // Slightly faster for spaces
119
+ baseDelay = tp.config.FastDelay
120
+ case r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z':
121
+ // Fast for common letters
122
+ baseDelay = tp.config.FastDelay
123
+ case r == '\n':
124
+ // No delay for newlines
125
+ return 0
126
+ default:
127
+ // Base delay for other characters
128
+ baseDelay = tp.config.BaseDelay
129
+ }
130
+
131
+ // Add randomness to make it feel more natural
132
+ if tp.config.RandomFactor > 0 {
133
+ // Simple pseudo-random based on position to ensure consistency
134
+ randomFactor := 0.7 + (tp.config.RandomFactor * float64(position%7) / 6.0)
135
+ baseDelay = time.Duration(float64(baseDelay) * randomFactor)
136
+ }
137
+
138
+ return baseDelay
139
+ }
140
+
141
+ // SetEnabled enables or disables the typewriter effect
142
+ func (tp *TypewriterPrinter) SetEnabled(enabled bool) {
143
+ tp.config.Enabled = enabled
144
+ }
145
+
146
+ // IsEnabled returns whether the typewriter effect is enabled
147
+ func (tp *TypewriterPrinter) IsEnabled() bool {
148
+ return tp.config.Enabled
149
+ }
150
+
151
+ // SetSpeed adjusts the typewriter speed (multiplier: 0.1 = very slow, 1.0 = normal, 2.0 = fast)
152
+ func (tp *TypewriterPrinter) SetSpeed(multiplier float64) {
153
+ if multiplier <= 0 {
154
+ multiplier = 1.0
155
+ }
156
+
157
+ tp.config.BaseDelay = time.Duration(float64(15*time.Millisecond) / multiplier)
158
+ tp.config.FastDelay = time.Duration(float64(8*time.Millisecond) / multiplier)
159
+ tp.config.SlowDelay = time.Duration(float64(25*time.Millisecond) / multiplier)
160
+ tp.config.PauseDelay = time.Duration(float64(150*time.Millisecond) / multiplier)
161
+ }
162
+
163
+ func (tp *TypewriterPrinter) PrintMessageLine(prefix, text string) {
164
+ tp.PrintfInstant("%s: ", prefix)
165
+ tp.Println(text)
166
+ }
167
+
168
+ // Global typewriter printer instance
169
+ var globalTypewriter = NewTypewriterPrinter(DefaultTypewriterConfig())
170
+
171
+ // Global convenience functions that use the global typewriter instance
172
+
173
+ // TypewriterPrint prints text with typewriter effect using the global instance
174
+ func TypewriterPrint(text string) {
175
+ globalTypewriter.Print(text)
176
+ }
177
+
178
+ // TypewriterPrintf prints formatted text with typewriter effect using the global instance
179
+ func TypewriterPrintf(format string, args ...interface{}) {
180
+ globalTypewriter.Printf(format, args...)
181
+ }
182
+
183
+ // TypewriterPrintln prints text with typewriter effect and newline using the global instance
184
+ func TypewriterPrintln(text string) {
185
+ globalTypewriter.Println(text)
186
+ }
187
+
188
+ // TypewriterPrintfLn prints formatted text with typewriter effect and newline using the global instance
189
+ func TypewriterPrintfLn(format string, args ...interface{}) {
190
+ globalTypewriter.PrintfLn(format, args...)
191
+ }
192
+
193
+ func TypewriterPrintMessageLine(prefix, text string) {
194
+ globalTypewriter.PrintMessageLine(prefix, text)
195
+ }
196
+
197
+ // SetGlobalTypewriterEnabled enables or disables the global typewriter effect
198
+ func SetGlobalTypewriterEnabled(enabled bool) {
199
+ globalTypewriter.SetEnabled(enabled)
200
+ }
201
+
202
+ // SetGlobalTypewriterSpeed sets the speed of the global typewriter effect
203
+ func SetGlobalTypewriterSpeed(multiplier float64) {
204
+ globalTypewriter.SetSpeed(multiplier)
205
+ }
206
+
207
+ // GetGlobalTypewriter returns the global typewriter instance
208
+ func GetGlobalTypewriter() *TypewriterPrinter {
209
+ return globalTypewriter
210
+ }
@@ -0,0 +1,65 @@
1
+ package cli
2
+
3
+ import (
4
+ "fmt"
5
+
6
+ "github.com/cline/cli/pkg/cli/display"
7
+ "github.com/cline/cli/pkg/cli/global"
8
+ "github.com/cline/cli/pkg/cli/terminal"
9
+ "github.com/cline/cli/pkg/cli/updater"
10
+ "github.com/spf13/cobra"
11
+ )
12
+
13
+ // NewDoctorCommand creates the doctor command
14
+ func NewDoctorCommand() *cobra.Command {
15
+ cmd := &cobra.Command{
16
+ Use: "doctor",
17
+ Aliases: []string{"d"},
18
+ Short: "Check system health and diagnose problems",
19
+ Long: `Check the health of your Cline CLI installation and diagnose problems.
20
+
21
+ Currently this command performs the following checks and fixes:
22
+
23
+ Terminal Configuration:
24
+ - Detects your terminal emulator (VS Code, Cursor, Ghostty, Kitty, WezTerm, Alacritty)
25
+ - Configures shift+enter to insert newlines in multiline input
26
+ - Creates backups before modifying configuration files
27
+ - Supported terminals: VS Code, Cursor, Ghostty, Kitty, WezTerm, Alacritty
28
+ - iTerm2 works by default, Terminal.app requires manual setup
29
+
30
+ CLI Updates:
31
+ - Checks npm registry for the latest version
32
+ - Automatically installs updates via npm if available
33
+ - Respects NO_AUTO_UPDATE environment variable
34
+ - Skipped in CI environments
35
+
36
+ Note: Future versions will include additional health checks for Node.js version,
37
+ npm availability, Cline Core connectivity, database integrity, and more.`,
38
+ RunE: func(cmd *cobra.Command, args []string) error {
39
+ return runDoctorChecks()
40
+ },
41
+ }
42
+
43
+ return cmd
44
+ }
45
+
46
+ // runDoctorChecks performs all doctor diagnostics and configuration
47
+ func runDoctorChecks() error {
48
+ renderer := display.NewRenderer(global.Config.OutputFormat)
49
+
50
+ fmt.Printf("\n%s\n\n", renderer.Bold("Cline Doctor - System Health Check"))
51
+
52
+ // Configure terminal keybindings (terminal.go prints its own status)
53
+ fmt.Printf("%s\n\n", renderer.Dim("━━━ Terminal Configuration ━━━"))
54
+ terminal.SetupKeyboardSync()
55
+
56
+ // Check for updates (updater.go prints its own status)
57
+ fmt.Printf("\n%s\n\n", renderer.Dim("━━━ CLI Updates ━━━"))
58
+ updater.CheckAndUpdateSync(global.Config.Verbose, true)
59
+
60
+ // Summary
61
+ fmt.Printf("\n%s\n", renderer.Dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"))
62
+ fmt.Printf("\n%s\n\n", renderer.SuccessWithCheckmark("Health check complete"))
63
+
64
+ return nil
65
+ }