@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
package/pkg/cli/task.go
ADDED
|
@@ -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
|
+
}
|