@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,304 @@
1
+ package global
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "log"
7
+ "os"
8
+ "path/filepath"
9
+ "time"
10
+
11
+ "github.com/cline/cli/pkg/cli/sqlite"
12
+ "github.com/cline/cli/pkg/common"
13
+ "github.com/cline/grpc-go/client"
14
+ "github.com/cline/grpc-go/cline"
15
+ "github.com/cline/grpc-go/host"
16
+ "google.golang.org/grpc"
17
+ "google.golang.org/grpc/credentials/insecure"
18
+ "google.golang.org/grpc/health/grpc_health_v1"
19
+ )
20
+
21
+ // ClientRegistry manages Cline client connections using direct SQLite operations
22
+ type ClientRegistry struct {
23
+ lockManager *sqlite.LockManager
24
+ configPath string
25
+ }
26
+
27
+ // NewClientRegistry creates a new client registry
28
+ func NewClientRegistry(configPath string) *ClientRegistry {
29
+ lockManager, err := sqlite.NewLockManager(configPath)
30
+ if err != nil {
31
+ // Log error but continue - we can still function without SQLite
32
+ log.Fatalf("Warning: Failed to initialize SQLite lock manager: %v\n", err)
33
+ }
34
+
35
+ return &ClientRegistry{
36
+ lockManager: lockManager,
37
+ configPath: configPath,
38
+ }
39
+ }
40
+
41
+ // GetDefaultInstance returns the default instance address from settings file
42
+ func (r *ClientRegistry) GetDefaultInstance() string {
43
+ defaultAddr, err := sqlite.GetDefaultInstance(r.configPath)
44
+ if err != nil {
45
+ return ""
46
+ }
47
+ return defaultAddr
48
+ }
49
+
50
+ // SetDefaultInstance sets the default instance (writes default.json)
51
+ func (r *ClientRegistry) SetDefaultInstance(address string) error {
52
+ // Verify the instance exists in SQLite
53
+ if r.lockManager != nil {
54
+ exists, err := r.lockManager.HasInstanceAtAddress(address)
55
+ if err != nil {
56
+ return fmt.Errorf("failed to check instance existence: %w", err)
57
+ }
58
+ if !exists {
59
+ return fmt.Errorf("instance %s not found in registry", address)
60
+ }
61
+ }
62
+
63
+ return sqlite.SetDefaultInstance(r.configPath, address)
64
+ }
65
+
66
+ // GetInstance returns instance information directly from SQLite
67
+ func (r *ClientRegistry) GetInstance(address string) (*common.CoreInstanceInfo, error) {
68
+ if r.lockManager == nil {
69
+ return nil, fmt.Errorf("lock manager not available")
70
+ }
71
+
72
+ return r.lockManager.GetInstanceInfo(address)
73
+ }
74
+
75
+ // GetClient returns a connected client for the given address (created on-demand)
76
+ func (r *ClientRegistry) GetClient(ctx context.Context, address string) (*client.ClineClient, error) {
77
+ // Verify instance exists in SQLite
78
+ if r.lockManager != nil {
79
+ exists, err := r.lockManager.HasInstanceAtAddress(address)
80
+ if err != nil {
81
+ return nil, fmt.Errorf("failed to check instance existence: %w", err)
82
+ }
83
+ if !exists {
84
+ return nil, fmt.Errorf("instance %s not found", address)
85
+ }
86
+ }
87
+
88
+ // Create client on-demand (no caching)
89
+ target, err := common.NormalizeAddressForGRPC(address)
90
+ if err != nil {
91
+ return nil, fmt.Errorf("invalid address %s: %w", address, err)
92
+ }
93
+
94
+ cl, err := client.NewClineClient(target)
95
+ if err != nil {
96
+ return nil, fmt.Errorf("failed to create client for %s: %w", target, err)
97
+ }
98
+
99
+ if err := cl.Connect(ctx); err != nil {
100
+ return nil, fmt.Errorf("failed to connect to %s: %w", target, err)
101
+ }
102
+
103
+ return cl, nil
104
+ }
105
+
106
+ // GetDefaultClient returns a client for the default instance
107
+ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineClient, error) {
108
+ defaultAddr := r.GetDefaultInstance()
109
+ if defaultAddr == "" {
110
+ return nil, fmt.Errorf("no default instance configured")
111
+ }
112
+
113
+ // Check if the default instance actually exists in the database
114
+ if r.lockManager != nil {
115
+ exists, err := r.lockManager.HasInstanceAtAddress(defaultAddr)
116
+ if err != nil {
117
+ // Database is unavailable - Return error instead of attempting cleanup
118
+ return nil, fmt.Errorf("cannot verify default instance: database unavailable: %w", err)
119
+ }
120
+
121
+ if !exists {
122
+ // Instance doesn't exist in database but config file references it
123
+ // This is a stale config - remove it and try to find another instance
124
+ settingsPath := filepath.Join(r.configPath, common.SETTINGS_SUBFOLDER, "settings", "cli-default-instance.json")
125
+ if removeErr := os.Remove(settingsPath); removeErr != nil && !os.IsNotExist(removeErr) {
126
+ fmt.Printf("Warning: Failed to remove stale default instance config: %v\n", removeErr)
127
+ } else {
128
+ fmt.Printf("Removed stale default instance config (instance %s not found in database)\n", defaultAddr)
129
+ }
130
+
131
+ // Try to find and set a new default instance
132
+ instances := r.ListInstances()
133
+ if len(instances) > 0 {
134
+ if err := r.EnsureDefaultInstance(instances); err != nil {
135
+ return nil, fmt.Errorf("failed to set new default instance: %w", err)
136
+ }
137
+
138
+ // Retry with the new default
139
+ newDefaultAddr := r.GetDefaultInstance()
140
+ if newDefaultAddr != "" {
141
+ fmt.Printf("Set new default instance: %s\n", newDefaultAddr)
142
+ return r.GetClient(ctx, newDefaultAddr)
143
+ }
144
+ }
145
+
146
+ return nil, fmt.Errorf("no default instance configured")
147
+ }
148
+ }
149
+
150
+ return r.GetClient(ctx, defaultAddr)
151
+ }
152
+
153
+ // ListInstances returns all registered instances directly from SQLite
154
+ func (r *ClientRegistry) ListInstances() []*common.CoreInstanceInfo {
155
+ if r.lockManager == nil {
156
+ return []*common.CoreInstanceInfo{}
157
+ }
158
+
159
+ // Use context with timeout for health checks
160
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
161
+ defer cancel()
162
+
163
+ instances, err := r.lockManager.ListInstancesWithHealthCheck(ctx)
164
+ if err != nil {
165
+ fmt.Printf("Warning: Failed to list instances: %v\n", err)
166
+ return []*common.CoreInstanceInfo{}
167
+ }
168
+
169
+ return instances
170
+ }
171
+
172
+ // HasInstanceAtAddress checks if an instance exists at the given address (delegates to SQLite)
173
+ func (r *ClientRegistry) HasInstanceAtAddress(address string) bool {
174
+ if r.lockManager == nil {
175
+ return false
176
+ }
177
+
178
+ exists, err := r.lockManager.HasInstanceAtAddress(address)
179
+ if err != nil {
180
+ fmt.Printf("Warning: Failed to check instance existence: %v\n", err)
181
+ return false
182
+ }
183
+
184
+ return exists
185
+ }
186
+
187
+ // CleanupStaleInstances removes stale instances using direct SQLite operations
188
+ func (r *ClientRegistry) CleanupStaleInstances(ctx context.Context) error {
189
+ if r.lockManager == nil {
190
+ return nil
191
+ }
192
+
193
+ // Get all instances with health checks
194
+ instances, err := r.lockManager.ListInstancesWithHealthCheck(ctx)
195
+ if err != nil {
196
+ return fmt.Errorf("failed to list instances for cleanup: %w", err)
197
+ }
198
+
199
+ // Clean up all stale instances
200
+ for _, instance := range instances {
201
+ if instance.Status != grpc_health_v1.HealthCheckResponse_SERVING {
202
+ // Try to gracefully shutdown the paired host process before cleanup
203
+
204
+ fmt.Printf("Attempting to shutdown dangling host service %s for stale cline core instance %s\n",
205
+ instance.HostServiceAddress, instance.Address)
206
+ r.tryShutdownHostProcess(instance.HostServiceAddress)
207
+
208
+ // Remove from SQLite database
209
+ if err := r.lockManager.RemoveInstanceLock(instance.Address); err != nil {
210
+ return fmt.Errorf("failed to remove stale instance %s: %w", instance.Address, err)
211
+ }
212
+
213
+ fmt.Printf("Removed stale instance: %s\n", instance.Address)
214
+ }
215
+ }
216
+
217
+ return nil
218
+ }
219
+
220
+ // tryShutdownHostProcess attempts to gracefully shutdown a host process via RPC
221
+ // Best effort, don't throw errors i guess
222
+ func (r *ClientRegistry) tryShutdownHostProcess(hostServiceAddress string) {
223
+ err := common.RetryOperation(3, 2*time.Second, func() error {
224
+ // Create context with timeout
225
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
226
+ defer cancel()
227
+
228
+ // Create gRPC connection to host bridge
229
+ conn, err := grpc.DialContext(ctx, hostServiceAddress,
230
+ grpc.WithTransportCredentials(insecure.NewCredentials()),
231
+ grpc.WithBlock())
232
+ if err != nil {
233
+ return fmt.Errorf("connection failed: %w", err)
234
+ }
235
+ defer conn.Close()
236
+
237
+ // Create env service client and call shutdown
238
+ envClient := host.NewEnvServiceClient(conn)
239
+ _, err = envClient.Shutdown(ctx, &cline.EmptyRequest{})
240
+ if err != nil {
241
+ return fmt.Errorf("RPC failed: %w", err)
242
+ }
243
+
244
+ return nil
245
+ })
246
+
247
+ if err != nil {
248
+ fmt.Printf("Warning: Failed to request host bridge shutdown on port %s: %v\n", hostServiceAddress, err)
249
+ } else {
250
+ fmt.Printf("Host bridge shutdown requested successfully on port %s\n", hostServiceAddress)
251
+ }
252
+ }
253
+
254
+ // ListInstancesCleaned performs cleanup and returns instances with health checks
255
+ func (r *ClientRegistry) ListInstancesCleaned(ctx context.Context) ([]*common.CoreInstanceInfo, error) {
256
+ // 1. Clean up stale entries (best-effort)
257
+ _ = r.CleanupStaleInstances(ctx)
258
+
259
+ // 2. Get all instances with real-time health checks
260
+ instances := r.ListInstances()
261
+
262
+ // 3. Ensure default is set if instances exist
263
+ if err := r.EnsureDefaultInstance(instances); err != nil {
264
+ fmt.Printf("Warning: Failed to ensure default instance: %v\n", err)
265
+ }
266
+
267
+ return instances, nil
268
+ }
269
+
270
+ // EnsureDefaultInstance ensures a default instance is set if instances exist but no default is configured
271
+ func (r *ClientRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceInfo) error {
272
+ currentDefault := r.GetDefaultInstance()
273
+
274
+ // If we have no instances, clear any stale default and remove settings file
275
+ if len(instances) == 0 {
276
+ if currentDefault != "" {
277
+ // Remove the settings file since no instances exist
278
+ settingsPath := filepath.Join(r.configPath, common.SETTINGS_SUBFOLDER, "settings", "cli-default-instance.json")
279
+ _ = os.Remove(settingsPath)
280
+ }
281
+ return nil
282
+ }
283
+
284
+ // If we have instances but no default, pick the first one
285
+ if currentDefault == "" {
286
+ return sqlite.SetDefaultInstance(r.configPath, instances[0].Address)
287
+ }
288
+
289
+ // Validate current default still exists in the instances
290
+ defaultExists := false
291
+ for _, instance := range instances {
292
+ if instance.Address == currentDefault {
293
+ defaultExists = true
294
+ break
295
+ }
296
+ }
297
+
298
+ if !defaultExists {
299
+ // Current default doesn't exist, pick a new one from available instances
300
+ return sqlite.SetDefaultInstance(r.configPath, instances[0].Address)
301
+ }
302
+
303
+ return nil
304
+ }
@@ -0,0 +1,339 @@
1
+ package handlers
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "strings"
7
+
8
+ "github.com/cline/cli/pkg/cli/clerror"
9
+ "github.com/cline/cli/pkg/cli/output"
10
+ "github.com/cline/cli/pkg/cli/types"
11
+ )
12
+
13
+ // AskHandler handles ASK type messages
14
+ type AskHandler struct {
15
+ *BaseHandler
16
+ }
17
+
18
+ // NewAskHandler creates a new ASK handler
19
+ func NewAskHandler() *AskHandler {
20
+ return &AskHandler{
21
+ BaseHandler: NewBaseHandler("ask", PriorityHigh),
22
+ }
23
+ }
24
+
25
+ // CanHandle returns true if this is an ASK message
26
+ func (h *AskHandler) CanHandle(msg *types.ClineMessage) bool {
27
+ return msg.IsAsk()
28
+ }
29
+
30
+ func (h *AskHandler) Handle(msg *types.ClineMessage, dc *DisplayContext) error {
31
+ // Always display approval messages so user can see what they're approving
32
+ // The input handler will show the approval prompt form after the content is displayed
33
+
34
+ switch msg.Ask {
35
+ case string(types.AskTypeFollowup):
36
+ return h.handleFollowup(msg, dc)
37
+ case string(types.AskTypePlanModeRespond):
38
+ return h.handlePlanModeRespond(msg, dc)
39
+ case string(types.AskTypeCommand):
40
+ return h.handleCommand(msg, dc)
41
+ case string(types.AskTypeCommandOutput):
42
+ return h.handleCommandOutput(msg, dc)
43
+ case string(types.AskTypeCompletionResult):
44
+ return h.handleCompletionResult(msg, dc)
45
+ case string(types.AskTypeTool):
46
+ return h.handleTool(msg, dc)
47
+ case string(types.AskTypeAPIReqFailed):
48
+ return h.handleAPIReqFailed(msg, dc)
49
+ case string(types.AskTypeResumeTask):
50
+ return h.handleResumeTask(msg, dc)
51
+ case string(types.AskTypeResumeCompletedTask):
52
+ return h.handleResumeCompletedTask(msg, dc)
53
+ case string(types.AskTypeMistakeLimitReached):
54
+ return h.handleMistakeLimitReached(msg, dc)
55
+ case string(types.AskTypeBrowserActionLaunch):
56
+ return h.handleBrowserActionLaunch(msg, dc)
57
+ case string(types.AskTypeUseMcpServer):
58
+ return h.handleUseMcpServer(msg, dc)
59
+ case string(types.AskTypeNewTask):
60
+ return h.handleNewTask(msg, dc)
61
+ case string(types.AskTypeCondense):
62
+ return h.handleCondense(msg, dc)
63
+ case string(types.AskTypeReportBug):
64
+ return h.handleReportBug(msg, dc)
65
+ default:
66
+ return h.handleDefault(msg, dc)
67
+ }
68
+ }
69
+
70
+ // handleFollowup handles followup questions
71
+ func (h *AskHandler) handleFollowup(msg *types.ClineMessage, dc *DisplayContext) error {
72
+ body := dc.ToolRenderer.GenerateAskFollowupBody(msg.Text)
73
+
74
+ if body == "" {
75
+ return nil
76
+ }
77
+
78
+ if dc.IsStreamingMode {
79
+ // In streaming mode, header was already shown by partial stream
80
+ // Just render the body content
81
+ output.Print(body)
82
+ } else {
83
+ // Non-streaming mode: render header + body together
84
+ header := dc.ToolRenderer.GenerateAskFollowupHeader()
85
+ rendered := dc.Renderer.RenderMarkdown(header)
86
+ output.Print("\n")
87
+ output.Print(rendered)
88
+ output.Print("\n")
89
+ output.Print(body)
90
+ }
91
+
92
+ return nil
93
+ }
94
+
95
+ // handlePlanModeRespond handles plan mode responses
96
+ func (h *AskHandler) handlePlanModeRespond(msg *types.ClineMessage, dc *DisplayContext) error {
97
+ if dc.IsStreamingMode {
98
+ // In streaming mode, header was already shown by partial stream
99
+ // Just render the body content
100
+ body := dc.ToolRenderer.GeneratePlanModeRespondBody(msg.Text)
101
+ if body != "" {
102
+ output.Print(body)
103
+ }
104
+ } else {
105
+ // In non-streaming mode, render header + body together
106
+ header := dc.ToolRenderer.GeneratePlanModeRespondHeader()
107
+ body := dc.ToolRenderer.GeneratePlanModeRespondBody(msg.Text)
108
+
109
+ if body == "" {
110
+ return nil
111
+ }
112
+
113
+ // Render header
114
+ rendered := dc.Renderer.RenderMarkdown(header)
115
+ output.Print("\n")
116
+ output.Print(rendered)
117
+ output.Print("\n")
118
+
119
+ // Render body
120
+ output.Print(body)
121
+ }
122
+
123
+ return nil
124
+ }
125
+
126
+ // showApprovalHint displays a hint in non-interactive mode about how to approve/deny
127
+ func (h *AskHandler) showApprovalHint(dc *DisplayContext) {
128
+ if !dc.IsInteractive {
129
+ output.Printf("\n%s\n", dc.Renderer.Dim("Cline is requesting approval to use this tool"))
130
+ output.Printf("%s\n", dc.Renderer.Dim("Use cline task send --approve or --deny to respond"))
131
+ }
132
+ }
133
+
134
+ // handleCommand handles command execution requests
135
+ func (h *AskHandler) handleCommand(msg *types.ClineMessage, dc *DisplayContext) error {
136
+ if msg.Text == "" {
137
+ return nil
138
+ }
139
+
140
+ // Check if this command was flagged despite auto-approval settings
141
+ autoApprovalConflict := strings.HasSuffix(msg.Text, "REQ_APP")
142
+
143
+ // Use unified ToolRenderer
144
+ rendered := dc.ToolRenderer.RenderCommandApprovalRequest(msg.Text, autoApprovalConflict)
145
+ output.Print(rendered)
146
+
147
+ h.showApprovalHint(dc)
148
+ return nil
149
+ }
150
+
151
+ // handleCommandOutput handles command output requests
152
+ func (h *AskHandler) handleCommandOutput(msg *types.ClineMessage, dc *DisplayContext) error {
153
+ if msg.Text == "" {
154
+ return nil
155
+ }
156
+
157
+ commandOutput := msg.Text
158
+
159
+ markdown := fmt.Sprintf("```\n%s\n```", commandOutput)
160
+ rendered := dc.Renderer.RenderMarkdown(markdown)
161
+
162
+ fmt.Printf("%s", rendered)
163
+
164
+ return nil
165
+ }
166
+
167
+ // handleCompletionResult handles completion result requests
168
+ func (h *AskHandler) handleCompletionResult(msg *types.ClineMessage, dc *DisplayContext) error {
169
+ return nil
170
+ }
171
+
172
+ // handleTool handles tool execution requests
173
+ func (h *AskHandler) handleTool(msg *types.ClineMessage, dc *DisplayContext) error {
174
+ // Parse tool message
175
+ var tool types.ToolMessage
176
+ if err := json.Unmarshal([]byte(msg.Text), &tool); err != nil {
177
+ // Fallback to simple display
178
+ return dc.Renderer.RenderMessage("TOOL", msg.Text, true)
179
+ }
180
+
181
+ if dc.IsStreamingMode {
182
+ // In streaming mode, header was already shown by partial stream
183
+ // Just render the content preview
184
+ contentPreview := dc.ToolRenderer.GenerateToolContentPreview(&tool)
185
+ if contentPreview != "" {
186
+ output.Print("\n")
187
+ output.Print(contentPreview)
188
+ }
189
+ } else {
190
+ // Non-streaming mode: render full approval (header + preview)
191
+ rendered := dc.ToolRenderer.RenderToolApprovalRequest(&tool)
192
+ output.Print(rendered)
193
+ }
194
+
195
+ h.showApprovalHint(dc)
196
+ return nil
197
+ }
198
+
199
+ // handleAPIReqFailed handles API request failures
200
+ func (h *AskHandler) handleAPIReqFailed(msg *types.ClineMessage, dc *DisplayContext) error {
201
+ // Try to parse as ClineError for better error display
202
+ clineErr, _ := clerror.ParseClineError(msg.Text)
203
+ if clineErr != nil {
204
+ if dc.SystemRenderer != nil {
205
+ // Render the error with system renderer
206
+ switch clineErr.GetErrorType() {
207
+ case clerror.ErrorTypeBalance:
208
+ dc.SystemRenderer.RenderBalanceError(clineErr)
209
+ case clerror.ErrorTypeAuth:
210
+ dc.SystemRenderer.RenderAuthError(clineErr)
211
+ case clerror.ErrorTypeRateLimit:
212
+ dc.SystemRenderer.RenderRateLimitError(clineErr)
213
+ default:
214
+ dc.SystemRenderer.RenderAPIError(clineErr)
215
+ }
216
+ return nil
217
+ }
218
+ // Fallback: render with basic renderer using parsed message
219
+ return dc.Renderer.RenderMessage("ERROR", fmt.Sprintf("API Request Failed: %s. Approve to retry request.", clineErr.Message), true)
220
+ }
221
+ // Last resort: display raw text if parsing completely failed
222
+ return dc.Renderer.RenderMessage("ERROR", fmt.Sprintf("API Request Failed: %s. Approve to retry request.", msg.Text), true)
223
+ }
224
+
225
+ // handleResumeTask handles resume task requests
226
+ func (h *AskHandler) handleResumeTask(msg *types.ClineMessage, dc *DisplayContext) error {
227
+ // Don't render - this is metadata only, user already knows they're resuming
228
+ return nil
229
+ }
230
+
231
+ // handleResumeCompletedTask handles resume completed task requests
232
+ func (h *AskHandler) handleResumeCompletedTask(msg *types.ClineMessage, dc *DisplayContext) error {
233
+ // Don't render - this is metadata only, user already knows they're resuming
234
+ return nil
235
+ }
236
+
237
+ // handleMistakeLimitReached handles mistake limit reached
238
+ func (h *AskHandler) handleMistakeLimitReached(msg *types.ClineMessage, dc *DisplayContext) error {
239
+ if dc.SystemRenderer != nil {
240
+ details := make(map[string]string)
241
+ if msg.Text != "" {
242
+ details["details"] = msg.Text
243
+ }
244
+ dc.SystemRenderer.RenderError(
245
+ "critical",
246
+ "Mistake Limit Reached",
247
+ "Cline has made too many consecutive mistakes and needs your guidance to proceed.",
248
+ details,
249
+ )
250
+ fmt.Printf("\n**Approval required to continue.**\n")
251
+ return nil
252
+ }
253
+ return dc.Renderer.RenderMessage("ERROR", fmt.Sprintf("Mistake Limit Reached: %s. Approval required.", msg.Text), true)
254
+ }
255
+
256
+ // handleBrowserActionLaunch handles browser action launch requests
257
+ func (h *AskHandler) handleBrowserActionLaunch(msg *types.ClineMessage, dc *DisplayContext) error {
258
+ url := strings.TrimSpace(msg.Text)
259
+ err := dc.Renderer.RenderMessage("BROWSER", fmt.Sprintf("Cline wants to launch browser and navigate to: %s. Approval required.", url), true)
260
+ h.showApprovalHint(dc)
261
+ return err
262
+ }
263
+
264
+ // handleUseMcpServer handles MCP server usage requests
265
+ func (h *AskHandler) handleUseMcpServer(msg *types.ClineMessage, dc *DisplayContext) error {
266
+ // Parse MCP server usage request
267
+ type McpServerRequest struct {
268
+ ServerName string `json:"serverName"`
269
+ Type string `json:"type"`
270
+ ToolName string `json:"toolName,omitempty"`
271
+ Arguments string `json:"arguments,omitempty"`
272
+ URI string `json:"uri,omitempty"`
273
+ }
274
+
275
+ var mcpReq McpServerRequest
276
+ if err := json.Unmarshal([]byte(msg.Text), &mcpReq); err != nil {
277
+ return dc.Renderer.RenderMessage("MCP", msg.Text, true)
278
+ }
279
+
280
+ var operation string
281
+ if mcpReq.Type == "access_mcp_resource" {
282
+ operation = "access a resource"
283
+ } else {
284
+ operation = fmt.Sprintf("use a tool (%s)", mcpReq.ToolName)
285
+ if mcpReq.Arguments != "" {
286
+ operation = fmt.Sprintf("%s with args (%s)", operation, mcpReq.Arguments)
287
+ }
288
+ }
289
+
290
+ err := dc.Renderer.RenderMessage("MCP",
291
+ fmt.Sprintf("Cline wants to %s on the %s MCP server", operation, mcpReq.ServerName), true)
292
+
293
+ h.showApprovalHint(dc)
294
+ return err
295
+ }
296
+
297
+ // handleNewTask handles new task creation requests
298
+ func (h *AskHandler) handleNewTask(msg *types.ClineMessage, dc *DisplayContext) error {
299
+ return dc.Renderer.RenderMessage("NEW TASK", fmt.Sprintf("Cline wants to start a new task: %s. Approval required.", msg.Text), true)
300
+ }
301
+
302
+ // handleCondense handles conversation condensing requests
303
+ func (h *AskHandler) handleCondense(msg *types.ClineMessage, dc *DisplayContext) error {
304
+ return dc.Renderer.RenderMessage("CONDENSE", fmt.Sprintf("Cline wants to condense the conversation: %s. Approval required.", msg.Text), true)
305
+ }
306
+
307
+ // handleReportBug handles bug report requests
308
+ func (h *AskHandler) handleReportBug(msg *types.ClineMessage, dc *DisplayContext) error {
309
+ var bugData struct {
310
+ Title string `json:"title"`
311
+ WhatHappened string `json:"what_happened"`
312
+ StepsToReproduce string `json:"steps_to_reproduce"`
313
+ APIRequestOutput string `json:"api_request_output"`
314
+ AdditionalContext string `json:"additional_context"`
315
+ }
316
+
317
+ if err := json.Unmarshal([]byte(msg.Text), &bugData); err != nil {
318
+ return dc.Renderer.RenderMessage("BUG REPORT", fmt.Sprintf("Cline wants to create a GitHub issue: %s. Approval required.", msg.Text), true)
319
+ }
320
+
321
+ err := dc.Renderer.RenderMessage("BUG REPORT", "Cline wants to create a GitHub issue:", true)
322
+ if err != nil {
323
+ return fmt.Errorf("failed to render handleReportBug: %w", err)
324
+ }
325
+
326
+ fmt.Printf("\n**Title**: %s\n", bugData.Title)
327
+ fmt.Printf("**What Happened**: %s\n", bugData.WhatHappened)
328
+ fmt.Printf("**Steps to Reproduce**: %s\n", bugData.StepsToReproduce)
329
+ fmt.Printf("**API Request Output**: %s\n", bugData.APIRequestOutput)
330
+ fmt.Printf("**Additional Context**: %s\n", bugData.AdditionalContext)
331
+ fmt.Printf("\nApprove to create a GitHub issue.\n")
332
+
333
+ return nil
334
+ }
335
+
336
+ // handleDefault handles unknown ASK message types
337
+ func (h *AskHandler) handleDefault(msg *types.ClineMessage, dc *DisplayContext) error {
338
+ return dc.Renderer.RenderMessage("ASK", msg.Text, true)
339
+ }