@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,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
|
+
}
|