@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,675 @@
1
+ package cli
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "fmt"
7
+ "io"
8
+ "os"
9
+ "slices"
10
+ "strconv"
11
+ "strings"
12
+
13
+ "github.com/cline/cli/pkg/cli/config"
14
+ "github.com/cline/cli/pkg/cli/global"
15
+ "github.com/cline/cli/pkg/cli/task"
16
+ "github.com/cline/cli/pkg/cli/updater"
17
+ "github.com/cline/grpc-go/cline"
18
+ "github.com/spf13/cobra"
19
+ )
20
+
21
+ // TaskOptions contains options for creating a task
22
+ type TaskOptions struct {
23
+ Images []string
24
+ Files []string
25
+ Mode string
26
+ Settings []string
27
+ Yolo bool
28
+ Address string
29
+ Verbose bool
30
+ }
31
+
32
+ func NewTaskCommand() *cobra.Command {
33
+ cmd := &cobra.Command{
34
+ Use: "task",
35
+ Aliases: []string{"t"},
36
+ Short: "Manage Cline tasks",
37
+ Long: `Create, monitor, and manage Cline AI tasks.`,
38
+ }
39
+
40
+ cmd.AddCommand(newTaskNewCommand())
41
+ cmd.AddCommand(newTaskPauseCommand())
42
+ cmd.AddCommand(newTaskChatCommand())
43
+ cmd.AddCommand(newTaskSendCommand())
44
+ cmd.AddCommand(newTaskViewCommand())
45
+ cmd.AddCommand(newTaskListCommand())
46
+ cmd.AddCommand(newTaskOpenCommand())
47
+ cmd.AddCommand(newTaskRestoreCommand())
48
+
49
+ return cmd
50
+ }
51
+
52
+ var taskManager *task.Manager
53
+
54
+ func ensureTaskManager(ctx context.Context, address string) error {
55
+ if taskManager == nil || (address != "" && taskManager.GetCurrentInstance() != address) {
56
+ var err error
57
+ var instanceAddress string
58
+
59
+ if address != "" {
60
+ // Ensure instance exists at the specified address
61
+ if err := ensureInstanceAtAddress(ctx, address); err != nil {
62
+ return fmt.Errorf("failed to ensure instance at address %s: %w", address, err)
63
+ }
64
+ taskManager, err = task.NewManagerForAddress(ctx, address)
65
+ instanceAddress = address
66
+ } else {
67
+ // Ensure default instance exists
68
+ if err := global.EnsureDefaultInstance(ctx); err != nil {
69
+ return fmt.Errorf("failed to ensure default instance: %w", err)
70
+ }
71
+ taskManager, err = task.NewManagerForDefault(ctx)
72
+ if err == nil {
73
+ instanceAddress = taskManager.GetCurrentInstance()
74
+ }
75
+ }
76
+
77
+ if err != nil {
78
+ return fmt.Errorf("failed to create task manager: %w", err)
79
+ }
80
+
81
+ // Always set the instance we're using as the default
82
+ registry := global.Clients.GetRegistry()
83
+ if err := registry.SetDefaultInstance(instanceAddress); err != nil {
84
+ // Log warning but don't fail - this is not critical
85
+ fmt.Printf("Warning: failed to set default instance: %v\n", err)
86
+ }
87
+ }
88
+ return nil
89
+ }
90
+
91
+ // ensureInstanceAtAddress ensures an instance exists at the given address
92
+ func ensureInstanceAtAddress(ctx context.Context, address string) error {
93
+ if global.Clients == nil {
94
+ return fmt.Errorf("global clients not initialized")
95
+ }
96
+ return global.Clients.EnsureInstanceAtAddress(ctx, address)
97
+ }
98
+
99
+ func newTaskNewCommand() *cobra.Command {
100
+ var (
101
+ images []string
102
+ files []string
103
+ address string
104
+ mode string
105
+ settings []string
106
+ yolo bool
107
+ )
108
+
109
+ cmd := &cobra.Command{
110
+ Use: "new <prompt>",
111
+ Aliases: []string{"n"},
112
+ Short: "Create a new task",
113
+ Long: `Create a new Cline task with the specified prompt. If no Cline instance exists at the specified address, a new one will be started automatically.`,
114
+ Args: cobra.MinimumNArgs(0),
115
+ RunE: func(cmd *cobra.Command, args []string) error {
116
+ ctx := cmd.Context()
117
+
118
+ // Check if an instance exists when no address specified
119
+ if address == "" && global.Clients.GetRegistry().GetDefaultInstance() == "" {
120
+ fmt.Println("No instances available for creating tasks")
121
+ return nil
122
+ }
123
+
124
+ // Get content from both args and stdin
125
+ prompt, err := getContentFromStdinAndArgs(args)
126
+ if err != nil {
127
+ return fmt.Errorf("failed to read prompt: %w", err)
128
+ }
129
+
130
+ // Validate that prompt is passed in call
131
+ if prompt == "" {
132
+ return fmt.Errorf("prompt required: provide as argument or pipe via stdin")
133
+ }
134
+
135
+ // Ensure task manager is initialized
136
+ if err := ensureTaskManager(ctx, address); err != nil {
137
+ return err
138
+ }
139
+
140
+ // Set mode if provided
141
+ if mode != "" {
142
+ if err := taskManager.SetMode(ctx, mode, nil, nil, nil); err != nil {
143
+ return fmt.Errorf("failed to set mode: %w", err)
144
+ }
145
+ if global.Config.Verbose {
146
+ fmt.Printf("Mode set to: %s\n", mode)
147
+ }
148
+ }
149
+
150
+ // Inject yolo_mode_toggled setting if --yolo flag is set
151
+
152
+ // Will append to the -s settings to be parsed by the settings parser logic.
153
+ // If the yoloMode is also set in the settings, this will override that, since it will be set last.
154
+ if yolo {
155
+ settings = append(settings, "yolo_mode_toggled=true")
156
+ }
157
+
158
+ // Create the task
159
+ taskID, err := taskManager.CreateTask(ctx, prompt, images, files, settings)
160
+ if err != nil {
161
+ return fmt.Errorf("failed to create task: %w", err)
162
+ }
163
+
164
+ if global.Config.Verbose {
165
+ fmt.Printf("Task created successfully with ID: %s\n", taskID)
166
+ }
167
+
168
+ return nil
169
+ },
170
+ }
171
+
172
+ cmd.Flags().StringSliceVarP(&images, "image", "i", nil, "attach image files")
173
+ cmd.Flags().StringSliceVarP(&files, "file", "f", nil, "attach files")
174
+ cmd.Flags().StringVar(&address, "address", "", "specific Cline instance address to use")
175
+ cmd.Flags().StringVarP(&mode, "mode", "m", "", "mode (act|plan)")
176
+ cmd.Flags().StringSliceVarP(&settings, "setting", "s", nil, "task settings (key=value format, e.g., -s aws-region=us-west-2 -s mode=act)")
177
+ cmd.Flags().BoolVarP(&yolo, "yolo", "y", false, "enable yolo mode (non-interactive)")
178
+ cmd.Flags().BoolVar(&yolo, "no-interactive", false, "enable yolo mode (non-interactive)")
179
+
180
+ return cmd
181
+ }
182
+
183
+ func newTaskPauseCommand() *cobra.Command {
184
+ var address string
185
+
186
+ cmd := &cobra.Command{
187
+ Use: "pause",
188
+ Aliases: []string{"p"},
189
+ Short: "Pause the current task",
190
+ RunE: func(cmd *cobra.Command, args []string) error {
191
+ ctx := cmd.Context()
192
+
193
+ if err := ensureTaskManager(ctx, address); err != nil {
194
+ return err
195
+ }
196
+
197
+ if err := taskManager.CancelTask(ctx); err != nil {
198
+ return err
199
+ }
200
+
201
+ fmt.Println("Task paused successfully")
202
+ fmt.Printf("Instance: %s\n", taskManager.GetCurrentInstance())
203
+ return nil
204
+ },
205
+ }
206
+
207
+ cmd.Flags().StringVar(&address, "address", "", "specific Cline instance address to use")
208
+ return cmd
209
+ }
210
+
211
+ func newTaskSendCommand() *cobra.Command {
212
+ var (
213
+ images []string
214
+ files []string
215
+ address string
216
+ mode string
217
+ approve bool
218
+ deny bool
219
+ yolo bool
220
+ )
221
+
222
+ cmd := &cobra.Command{
223
+ Use: "send [message]",
224
+ Aliases: []string{"s"},
225
+ Short: "Send a followup message to the current task and/or update mode/approve",
226
+ Long: `Send a followup message to continue the conversation with the current task and/or update mode/approve.`,
227
+ Args: cobra.MinimumNArgs(0),
228
+ RunE: func(cmd *cobra.Command, args []string) error {
229
+ ctx := cmd.Context()
230
+
231
+ // Check if an instance exists when no address specified
232
+ if address == "" && global.Clients.GetRegistry().GetDefaultInstance() == "" {
233
+ fmt.Println("No instances available for sending messages")
234
+ return nil
235
+ }
236
+
237
+ // Get content from both args and stdin
238
+ message, err := getContentFromStdinAndArgs(args)
239
+ if err != nil {
240
+ return fmt.Errorf("failed to read message: %w", err)
241
+ }
242
+
243
+ if message == "" && len(images) == 0 && len(files) == 0 && mode == "" && !approve && !deny {
244
+ return fmt.Errorf("content (message, files, images) required unless using --mode, --approve, or --deny flags")
245
+ }
246
+
247
+ if approve && deny {
248
+ return fmt.Errorf("cannot use both --approve and --deny flags")
249
+ }
250
+
251
+ if (approve || deny) && mode != "" {
252
+ return fmt.Errorf("cannot use --approve/--deny and --mode together")
253
+ }
254
+
255
+ // Ensure task manager is initialized
256
+ if err := ensureTaskManager(ctx, address); err != nil {
257
+ return err
258
+ }
259
+
260
+ // Check if we can send a message
261
+ err = taskManager.CheckSendEnabled(ctx)
262
+ if err != nil {
263
+ // Handle specific error cases
264
+ if errors.Is(err, task.ErrNoActiveTask) {
265
+ fmt.Println("Cannot send message: no active task")
266
+ return nil
267
+ }
268
+ if errors.Is(err, task.ErrTaskBusy) {
269
+ fmt.Println("Cannot send message: task is currently busy")
270
+ return nil
271
+ }
272
+ // All other errors are unexpected
273
+ return fmt.Errorf("failed to check if message can be sent: %w", err)
274
+ }
275
+
276
+ // Process yolo flag and apply settings
277
+ if yolo {
278
+ settings := []string{"yolo_mode_toggled=true"}
279
+ parsedSettings, secrets, err := task.ParseTaskSettings(settings)
280
+ if err != nil {
281
+ return fmt.Errorf("failed to parse settings: %w", err)
282
+ }
283
+
284
+ configManager, err := config.NewManager(ctx, taskManager.GetCurrentInstance())
285
+ if err != nil {
286
+ return fmt.Errorf("failed to create config manager: %w", err)
287
+ }
288
+
289
+ if err := configManager.UpdateSettings(ctx, parsedSettings, secrets); err != nil {
290
+ return fmt.Errorf("failed to apply settings: %w", err)
291
+ }
292
+ }
293
+
294
+ if mode != "" {
295
+ if err := taskManager.SetModeAndSendMessage(ctx, mode, message, images, files); err != nil {
296
+ return fmt.Errorf("failed to set mode and send message: %w", err)
297
+ }
298
+ fmt.Printf("Mode set to %s and message sent successfully.\n", mode)
299
+
300
+ } else {
301
+ // Convert approve/deny booleans to string
302
+ approveStr := ""
303
+ if approve {
304
+ approveStr = "true"
305
+ }
306
+ if deny {
307
+ approveStr = "false"
308
+ }
309
+
310
+ if err := taskManager.SendMessage(ctx, message, images, files, approveStr); err != nil {
311
+ return err
312
+ }
313
+ fmt.Printf("Message sent successfully.\n")
314
+ }
315
+
316
+ fmt.Printf("Instance: %s\n", taskManager.GetCurrentInstance())
317
+ return nil
318
+ },
319
+ }
320
+
321
+ cmd.Flags().StringSliceVarP(&images, "image", "i", nil, "attach image files")
322
+ cmd.Flags().StringSliceVarP(&files, "file", "f", nil, "attach files")
323
+ cmd.Flags().StringVar(&address, "address", "", "specific Cline instance address to use")
324
+ cmd.Flags().StringVarP(&mode, "mode", "m", "", "mode (act|plan)")
325
+ cmd.Flags().BoolVarP(&approve, "approve", "a", false, "approve pending request")
326
+ cmd.Flags().BoolVarP(&deny, "deny", "d", false, "deny pending request")
327
+ cmd.Flags().BoolVarP(&yolo, "yolo", "y", false, "enable yolo mode (non-interactive)")
328
+ cmd.Flags().BoolVar(&yolo, "no-interactive", false, "enable yolo mode (non-interactive)")
329
+
330
+ return cmd
331
+ }
332
+
333
+ func newTaskChatCommand() *cobra.Command {
334
+ var address string
335
+
336
+ cmd := &cobra.Command{
337
+ Use: "chat",
338
+ Aliases: []string{"c"},
339
+ Short: "Chat with the current task in interactive mode",
340
+ Long: `Chat with the current task, displaying messages in real-time with interactive input enabled.`,
341
+ Args: cobra.NoArgs,
342
+ RunE: func(cmd *cobra.Command, args []string) error {
343
+ ctx := cmd.Context()
344
+
345
+ if err := ensureTaskManager(ctx, address); err != nil {
346
+ return err
347
+ }
348
+
349
+ // Check if there's an active task before entering follow mode
350
+ err := taskManager.CheckSendEnabled(ctx)
351
+ if err != nil {
352
+ // Handle specific error cases
353
+ if errors.Is(err, task.ErrNoActiveTask) {
354
+ fmt.Println("No active task found. Use 'cline task new' to create a task first.")
355
+ return nil
356
+ }
357
+ // For other errors (like task busy), we can still enter follow mode
358
+ // as the user may want to observe the task
359
+ }
360
+
361
+ return taskManager.FollowConversation(ctx, taskManager.GetCurrentInstance(), true)
362
+ },
363
+ }
364
+
365
+ cmd.Flags().StringVar(&address, "address", "", "specific Cline instance address to use")
366
+
367
+ return cmd
368
+ }
369
+
370
+ func newTaskViewCommand() *cobra.Command {
371
+ var (
372
+ follow bool
373
+ followComplete bool
374
+ address string
375
+ )
376
+
377
+ cmd := &cobra.Command{
378
+ Use: "view",
379
+ Aliases: []string{"v"},
380
+ Short: "View task conversation",
381
+ Long: `Output conversation snapshot by default, or follow with flags.`,
382
+ Args: cobra.NoArgs,
383
+ RunE: func(cmd *cobra.Command, args []string) error {
384
+ ctx := cmd.Context()
385
+
386
+ if err := ensureTaskManager(ctx, address); err != nil {
387
+ return err
388
+ }
389
+
390
+ fmt.Printf("Using instance: %s\n", taskManager.GetCurrentInstance())
391
+
392
+ if follow {
393
+ // Follow conversation forever (non-interactive)
394
+ return taskManager.FollowConversation(ctx, taskManager.GetCurrentInstance(), false)
395
+ } else if followComplete {
396
+ // Follow until completion
397
+ return taskManager.FollowConversationUntilCompletion(ctx)
398
+ } else {
399
+ // Default: show snapshot
400
+ return taskManager.ShowConversation(ctx)
401
+ }
402
+ },
403
+ }
404
+
405
+ cmd.Flags().BoolVarP(&follow, "follow", "f", false, "follow conversation forever")
406
+ cmd.Flags().BoolVarP(&followComplete, "follow-complete", "c", false, "follow until completion")
407
+ cmd.Flags().StringVar(&address, "address", "", "specific Cline instance address to use")
408
+
409
+ return cmd
410
+ }
411
+
412
+ func newTaskListCommand() *cobra.Command {
413
+ cmd := &cobra.Command{
414
+ Use: "list",
415
+ Aliases: []string{"l"},
416
+ Short: "List recent task history",
417
+ Long: `Display recent tasks from task history.`,
418
+ Args: cobra.NoArgs,
419
+ RunE: func(cmd *cobra.Command, args []string) error {
420
+ // Read directly from disk
421
+ return task.ListTasksFromDisk()
422
+ },
423
+ }
424
+
425
+ return cmd
426
+ }
427
+
428
+ func newTaskOpenCommand() *cobra.Command {
429
+ var (
430
+ address string
431
+ mode string
432
+ settings []string
433
+ yolo bool
434
+ )
435
+
436
+ cmd := &cobra.Command{
437
+ Use: "open <task-id>",
438
+ Aliases: []string{"o"},
439
+ Short: "Open a task by ID",
440
+ Long: `Open an existing task by ID and optionally update settings or mode.`,
441
+ Args: cobra.ExactArgs(1),
442
+ RunE: func(cmd *cobra.Command, args []string) error {
443
+ ctx := cmd.Context()
444
+ taskID := args[0]
445
+
446
+ // Ensure task manager is initialized
447
+ if err := ensureTaskManager(ctx, address); err != nil {
448
+ return err
449
+ }
450
+
451
+ fmt.Printf("Using instance: %s\n", taskManager.GetCurrentInstance())
452
+
453
+ // Resume the task
454
+ if err := taskManager.ResumeTask(ctx, taskID); err != nil {
455
+ return err
456
+ }
457
+
458
+ // Apply mode if provided
459
+ if mode != "" {
460
+ if err := taskManager.SetMode(ctx, mode, nil, nil, nil); err != nil {
461
+ return fmt.Errorf("failed to set mode: %w", err)
462
+ }
463
+ if global.Config.Verbose {
464
+ fmt.Printf("Mode set to: %s\n", mode)
465
+ }
466
+ }
467
+
468
+ // Process yolo flag and apply settings
469
+ if yolo {
470
+ settings = append(settings, "yolo_mode_toggled=true")
471
+ }
472
+
473
+ if len(settings) > 0 {
474
+ // Parse settings using existing parser
475
+ parsedSettings, secrets, err := task.ParseTaskSettings(settings)
476
+ if err != nil {
477
+ return fmt.Errorf("failed to parse settings: %w", err)
478
+ }
479
+
480
+ // Apply task-specific settings using UpdateTaskSettings RPC
481
+ if parsedSettings != nil {
482
+ _, err = taskManager.GetClient().State.UpdateTaskSettings(ctx, &cline.UpdateTaskSettingsRequest{
483
+ Settings: parsedSettings,
484
+ TaskId: &taskID,
485
+ })
486
+ if err != nil {
487
+ return fmt.Errorf("failed to apply task settings: %w", err)
488
+ }
489
+ if global.Config.Verbose {
490
+ fmt.Println("Task-specific settings applied successfully")
491
+ }
492
+ }
493
+
494
+ // Handle secrets separately if provided (they must go to global config)
495
+ if secrets != nil {
496
+ // Secrets are always global, not task-specific
497
+ configManager, err := config.NewManager(ctx, taskManager.GetCurrentInstance())
498
+ if err != nil {
499
+ return fmt.Errorf("failed to create config manager: %w", err)
500
+ }
501
+
502
+ if err := configManager.UpdateSettings(ctx, nil, secrets); err != nil {
503
+ return fmt.Errorf("failed to apply secrets: %w", err)
504
+ }
505
+ if global.Config.Verbose {
506
+ fmt.Println("Global secrets applied successfully")
507
+ }
508
+ }
509
+ }
510
+
511
+ return nil
512
+ },
513
+ }
514
+
515
+ cmd.Flags().StringVar(&address, "address", "", "specific Cline instance address to use")
516
+ cmd.Flags().StringVarP(&mode, "mode", "m", "", "mode (act|plan)")
517
+ cmd.Flags().StringSliceVarP(&settings, "setting", "s", nil, "task settings (key=value format, e.g., -s model=claude)")
518
+ cmd.Flags().BoolVarP(&yolo, "yolo", "y", false, "enable yolo mode (non-interactive)")
519
+ cmd.Flags().BoolVar(&yolo, "no-interactive", false, "enable yolo mode (non-interactive)")
520
+
521
+ return cmd
522
+ }
523
+
524
+ func newTaskRestoreCommand() *cobra.Command {
525
+ var (
526
+ restoreType string
527
+ address string
528
+ )
529
+
530
+ cmd := &cobra.Command{
531
+ Use: "restore <checkpoint-id>",
532
+ Short: "Restore task to a specific checkpoint",
533
+ Long: `Restore the current task to a specific checkpoint by checkpoint ID (timestamp) and by type.`,
534
+ Args: cobra.ExactArgs(1),
535
+ RunE: func(cmd *cobra.Command, args []string) error {
536
+ ctx := cmd.Context()
537
+ checkpointID := args[0]
538
+
539
+ // Convert checkpoint ID string to int64
540
+ id, err := strconv.ParseInt(checkpointID, 10, 64)
541
+ if err != nil {
542
+ return fmt.Errorf("invalid checkpoint ID '%s': must be a valid number", checkpointID)
543
+ }
544
+
545
+ validTypes := []string{"task", "workspace", "taskAndWorkspace"}
546
+ if !slices.Contains(validTypes, restoreType) {
547
+ return fmt.Errorf("invalid restore type '%s': must be one of [task, workspace, taskAndWorkspace]", restoreType)
548
+ }
549
+
550
+ // Ensure task manager is initialized
551
+ if err := ensureTaskManager(ctx, address); err != nil {
552
+ return err
553
+ }
554
+
555
+ // Validate checkpoint exists before attempting restore
556
+ if err := taskManager.ValidateCheckpointExists(ctx, id); err != nil {
557
+ return err
558
+ }
559
+
560
+ fmt.Printf("Using instance: %s\n", taskManager.GetCurrentInstance())
561
+ fmt.Printf("Restoring to checkpoint %d (type: %s)\n", id, restoreType)
562
+
563
+ if err := taskManager.RestoreCheckpoint(ctx, id, restoreType); err != nil {
564
+ return fmt.Errorf("failed to restore checkpoint: %w", err)
565
+ }
566
+
567
+ fmt.Println("Checkpoint restored successfully")
568
+ return nil
569
+ },
570
+ }
571
+
572
+ cmd.Flags().StringVarP(&restoreType, "type", "t", "task", "Restore type (task, workspace, taskAndWorkspace)")
573
+ cmd.Flags().StringVar(&address, "address", "", "specific Cline instance address to use")
574
+
575
+ return cmd
576
+ }
577
+
578
+ // getContentFromStdinAndArgs reads content from both command line args and stdin, and combines them
579
+ func getContentFromStdinAndArgs(args []string) (string, error) {
580
+ var content strings.Builder
581
+
582
+ // Add command line args first (if any)
583
+ if len(args) > 0 {
584
+ content.WriteString(strings.Join(args, " "))
585
+ }
586
+
587
+ // Check if stdin has data
588
+ stat, err := os.Stdin.Stat()
589
+ if err != nil {
590
+ return "", fmt.Errorf("failed to stat stdin: %w", err)
591
+ }
592
+
593
+ // Check if data is being piped to stdin
594
+ if (stat.Mode() & os.ModeCharDevice) == 0 {
595
+ // Only try to read if there's actually data available
596
+ if stat.Size() > 0 {
597
+ stdinBytes, err := io.ReadAll(os.Stdin)
598
+ if err != nil {
599
+ return "", fmt.Errorf("failed to read from stdin: %w", err)
600
+ }
601
+
602
+ stdinContent := strings.TrimSpace(string(stdinBytes))
603
+ if stdinContent != "" {
604
+ if content.Len() > 0 {
605
+ content.WriteString(" ")
606
+ }
607
+ content.WriteString(stdinContent)
608
+ }
609
+ }
610
+ }
611
+
612
+ return content.String(), nil
613
+ }
614
+
615
+ // CleanupTaskManager cleans up the task manager resources
616
+ func CleanupTaskManager() {
617
+ if taskManager != nil {
618
+ taskManager.Cleanup()
619
+ }
620
+ }
621
+
622
+ // NewTaskManagerForAddress is an exported wrapper around task.NewManagerForAddress
623
+ func NewTaskManagerForAddress(ctx context.Context, address string) (*task.Manager, error) {
624
+ return task.NewManagerForAddress(ctx, address)
625
+ }
626
+
627
+ // CreateAndFollowTask creates a new task and immediately follows it in interactive mode
628
+ // This is used by the root command to provide a streamlined UX
629
+ func CreateAndFollowTask(ctx context.Context, prompt string, opts TaskOptions) error {
630
+ // Initialize task manager with the provided instance address
631
+ if err := ensureTaskManager(ctx, opts.Address); err != nil {
632
+ return err
633
+ }
634
+
635
+ // Set mode to plan by default if not specified
636
+ if opts.Mode == "" {
637
+ opts.Mode = "plan"
638
+ }
639
+
640
+ // Set mode if provided
641
+ if opts.Mode != "" {
642
+ if err := taskManager.SetMode(ctx, opts.Mode, nil, nil, nil); err != nil {
643
+ return fmt.Errorf("failed to set mode: %w", err)
644
+ }
645
+ if global.Config.Verbose {
646
+ fmt.Printf("Mode set to: %s\n", opts.Mode)
647
+ }
648
+ }
649
+
650
+ // Inject yolo_mode_toggled setting if --yolo flag is set
651
+ if opts.Yolo {
652
+ opts.Settings = append(opts.Settings, "yolo_mode_toggled=true")
653
+ }
654
+
655
+ // Create the task
656
+ taskID, err := taskManager.CreateTask(ctx, prompt, opts.Images, opts.Files, opts.Settings)
657
+ if err != nil {
658
+ return fmt.Errorf("failed to create task: %w", err)
659
+ }
660
+
661
+ if global.Config.Verbose {
662
+ fmt.Printf("Task created successfully with ID: %s\n\n", taskID)
663
+ }
664
+
665
+ // Check for updates in background after task is created
666
+ updater.CheckAndUpdate(opts.Verbose)
667
+
668
+ // If yolo mode is enabled, follow until completion (non-interactive)
669
+ // Otherwise, follow in interactive mode
670
+ if opts.Yolo {
671
+ return taskManager.FollowConversationUntilCompletion(ctx)
672
+ } else {
673
+ return taskManager.FollowConversation(ctx, taskManager.GetCurrentInstance(), true)
674
+ }
675
+ }