cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package main
2
3import (
4 "strings"
5
6 "github.com/spf13/cobra"
7 "github.com/stormlightlabs/noteleaf/internal/handlers"
8)
9
10// TaskCommand implements CommandGroup for task-related commands
11type TaskCommand struct {
12 handler *handlers.TaskHandler
13}
14
15// NewTaskCommand creates a new TaskCommands with the given handler
16func NewTaskCommand(handler *handlers.TaskHandler) *TaskCommand {
17 return &TaskCommand{handler: handler}
18}
19
20func (c *TaskCommand) Create() *cobra.Command {
21 root := &cobra.Command{Use: "todo", Aliases: []string{"task"}, Short: "task management"}
22
23 for _, init := range []func(*handlers.TaskHandler) *cobra.Command{
24 addTaskCmd, listTaskCmd, viewTaskCmd, updateTaskCmd, editTaskCmd,
25 deleteTaskCmd, taskProjectsCmd, taskTagsCmd, taskContextsCmd,
26 taskCompleteCmd, taskStartCmd, taskStopCmd, timesheetViewCmd,
27 taskRecurCmd, taskDependCmd,
28 } {
29 cmd := init(c.handler)
30 root.AddCommand(cmd)
31 }
32
33 return root
34}
35
36func addTaskCmd(h *handlers.TaskHandler) *cobra.Command {
37 cmd := &cobra.Command{
38 Use: "add [description]",
39 Short: "Add a new task",
40 Aliases: []string{"create", "new"},
41 Args: cobra.MinimumNArgs(1),
42 RunE: func(c *cobra.Command, args []string) error {
43 description := strings.Join(args, " ")
44 priority, _ := c.Flags().GetString("priority")
45 project, _ := c.Flags().GetString("project")
46 context, _ := c.Flags().GetString("context")
47 due, _ := c.Flags().GetString("due")
48 recur, _ := c.Flags().GetString("recur")
49 until, _ := c.Flags().GetString("until")
50 parent, _ := c.Flags().GetString("parent")
51 dependsOn, _ := c.Flags().GetString("depends-on")
52 tags, _ := c.Flags().GetStringSlice("tags")
53
54 defer h.Close()
55 return h.Create(c.Context(), description, priority, project, context, due, recur, until, parent, dependsOn, tags)
56 },
57 }
58 addCommonTaskFlags(cmd)
59 addDueDateFlag(cmd)
60 addRecurrenceFlags(cmd)
61 addParentFlag(cmd)
62 addDependencyFlags(cmd)
63
64 return cmd
65}
66
67func listTaskCmd(h *handlers.TaskHandler) *cobra.Command {
68 cmd := &cobra.Command{
69 Use: "list",
70 Short: "List tasks",
71 Aliases: []string{"ls"},
72 Long: `List tasks with optional filtering and display modes.
73
74By default, shows tasks in an interactive TaskWarrior-like interface.
75Use --static to show a simple text list instead.
76Use --all to show all tasks, otherwise only pending tasks are shown.`,
77 RunE: func(c *cobra.Command, args []string) error {
78 static, _ := c.Flags().GetBool("static")
79 showAll, _ := c.Flags().GetBool("all")
80 status, _ := c.Flags().GetString("status")
81 priority, _ := c.Flags().GetString("priority")
82 project, _ := c.Flags().GetString("project")
83 context, _ := c.Flags().GetString("context")
84
85 defer h.Close()
86 return h.List(c.Context(), static, showAll, status, priority, project, context)
87 },
88 }
89 cmd.Flags().BoolP("interactive", "i", false, "Force interactive mode (default)")
90 cmd.Flags().Bool("static", false, "Use static text output instead of interactive")
91 cmd.Flags().BoolP("all", "a", false, "Show all tasks (default: pending only)")
92 cmd.Flags().String("status", "", "Filter by status")
93 cmd.Flags().String("priority", "", "Filter by priority")
94 cmd.Flags().String("project", "", "Filter by project")
95 cmd.Flags().String("context", "", "Filter by context")
96
97 return cmd
98}
99
100func viewTaskCmd(handler *handlers.TaskHandler) *cobra.Command {
101 viewCmd := &cobra.Command{
102 Use: "view [task-id]",
103 Short: "View task by ID",
104 Args: cobra.ExactArgs(1),
105 RunE: func(cmd *cobra.Command, args []string) error {
106 format, _ := cmd.Flags().GetString("format")
107 jsonOutput, _ := cmd.Flags().GetBool("json")
108 noMetadata, _ := cmd.Flags().GetBool("no-metadata")
109
110 defer handler.Close()
111 return handler.View(cmd.Context(), args, format, jsonOutput, noMetadata)
112 },
113 }
114 addOutputFlags(viewCmd)
115
116 return viewCmd
117}
118
119func updateTaskCmd(handler *handlers.TaskHandler) *cobra.Command {
120 updateCmd := &cobra.Command{
121 Use: "update [task-id]",
122 Short: "Update task properties",
123 Args: cobra.ExactArgs(1),
124 RunE: func(cmd *cobra.Command, args []string) error {
125 taskID := args[0]
126 description, _ := cmd.Flags().GetString("description")
127 status, _ := cmd.Flags().GetString("status")
128 priority, _ := cmd.Flags().GetString("priority")
129 project, _ := cmd.Flags().GetString("project")
130 context, _ := cmd.Flags().GetString("context")
131 due, _ := cmd.Flags().GetString("due")
132 recur, _ := cmd.Flags().GetString("recur")
133 until, _ := cmd.Flags().GetString("until")
134 parent, _ := cmd.Flags().GetString("parent")
135 addTags, _ := cmd.Flags().GetStringSlice("add-tag")
136 removeTags, _ := cmd.Flags().GetStringSlice("remove-tag")
137 addDeps, _ := cmd.Flags().GetString("add-depends")
138 removeDeps, _ := cmd.Flags().GetString("remove-depends")
139
140 defer handler.Close()
141 return handler.Update(cmd.Context(), taskID, description, status, priority, project, context, due, recur, until, parent, addTags, removeTags, addDeps, removeDeps)
142 },
143 }
144 updateCmd.Flags().String("description", "", "Update task description")
145 updateCmd.Flags().String("status", "", "Update task status")
146 addCommonTaskFlags(updateCmd)
147 addDueDateFlag(updateCmd)
148 addRecurrenceFlags(updateCmd)
149 addParentFlag(updateCmd)
150 updateCmd.Flags().StringSlice("add-tag", []string{}, "Add tags to task")
151 updateCmd.Flags().StringSlice("remove-tag", []string{}, "Remove tags from task")
152 updateCmd.Flags().String("add-depends", "", "Add task dependencies (comma-separated UUIDs)")
153 updateCmd.Flags().String("remove-depends", "", "Remove task dependencies (comma-separated UUIDs)")
154
155 return updateCmd
156}
157
158func taskProjectsCmd(h *handlers.TaskHandler) *cobra.Command {
159 cmd := &cobra.Command{
160 Use: "projects",
161 Short: "List projects",
162 Aliases: []string{"proj"},
163 RunE: func(c *cobra.Command, args []string) error {
164 static, _ := c.Flags().GetBool("static")
165 todoTxt, _ := c.Flags().GetBool("todo-txt")
166
167 defer h.Close()
168 return h.ListProjects(c.Context(), static, todoTxt)
169 },
170 }
171 cmd.Flags().Bool("static", false, "Use static text output instead of interactive")
172 cmd.Flags().Bool("todo-txt", false, "Format output with +project prefix for todo.txt compatibility")
173
174 return cmd
175}
176
177func taskTagsCmd(h *handlers.TaskHandler) *cobra.Command {
178 cmd := &cobra.Command{
179 Use: "tags",
180 Short: "List tags",
181 Aliases: []string{"t"},
182 RunE: func(c *cobra.Command, args []string) error {
183 static, _ := c.Flags().GetBool("static")
184 defer h.Close()
185 return h.ListTags(c.Context(), static)
186 },
187 }
188 cmd.Flags().Bool("static", false, "Use static text output instead of interactive")
189 return cmd
190}
191
192func taskStartCmd(h *handlers.TaskHandler) *cobra.Command {
193 cmd := &cobra.Command{
194 Use: "start [task-id]",
195 Short: "Start time tracking for a task",
196 Args: cobra.ExactArgs(1),
197 RunE: func(c *cobra.Command, args []string) error {
198 taskID := args[0]
199 description, _ := c.Flags().GetString("note")
200
201 defer h.Close()
202 return h.Start(c.Context(), taskID, description)
203 },
204 }
205 cmd.Flags().StringP("note", "n", "", "Add a note to the time entry")
206 return cmd
207}
208
209func taskStopCmd(h *handlers.TaskHandler) *cobra.Command {
210 return &cobra.Command{
211 Use: "stop [task-id]",
212 Short: "Stop time tracking for a task",
213 Args: cobra.ExactArgs(1),
214 RunE: func(c *cobra.Command, args []string) error {
215 taskID := args[0]
216 defer h.Close()
217 return h.Stop(c.Context(), taskID)
218 },
219 }
220}
221
222func timesheetViewCmd(h *handlers.TaskHandler) *cobra.Command {
223 cmd := &cobra.Command{
224 Use: "timesheet",
225 Short: "Show time tracking summary",
226 Long: `Show time tracking summary for tasks.
227
228By default shows time entries for the last 7 days.
229Use --task to show timesheet for a specific task.
230Use --days to change the date range.`,
231 RunE: func(c *cobra.Command, args []string) error {
232 days, _ := c.Flags().GetInt("days")
233 taskID, _ := c.Flags().GetString("task")
234
235 defer h.Close()
236 return h.Timesheet(c.Context(), days, taskID)
237 },
238 }
239 cmd.Flags().IntP("days", "d", 7, "Number of days to show in timesheet")
240 cmd.Flags().StringP("task", "t", "", "Show timesheet for specific task ID")
241 return cmd
242}
243
244func editTaskCmd(h *handlers.TaskHandler) *cobra.Command {
245 return &cobra.Command{
246 Use: "edit [task-id]",
247 Short: "Edit task interactively with status picker and priority toggle",
248 Aliases: []string{"e"},
249 Args: cobra.ExactArgs(1),
250 RunE: func(c *cobra.Command, args []string) error {
251 taskID := args[0]
252 defer h.Close()
253 return h.EditInteractive(c.Context(), taskID)
254 },
255 }
256}
257
258func deleteTaskCmd(h *handlers.TaskHandler) *cobra.Command {
259 return &cobra.Command{
260 Use: "delete [task-id]",
261 Short: "Delete a task",
262 Args: cobra.ExactArgs(1),
263 RunE: func(c *cobra.Command, args []string) error {
264 defer h.Close()
265 return h.Delete(c.Context(), args)
266 },
267 }
268}
269
270func taskContextsCmd(h *handlers.TaskHandler) *cobra.Command {
271 cmd := &cobra.Command{
272 Use: "contexts",
273 Short: "List contexts (locations)",
274 Aliases: []string{"con", "loc", "ctx", "locations"},
275 RunE: func(c *cobra.Command, args []string) error {
276 static, _ := c.Flags().GetBool("static")
277 todoTxt, _ := c.Flags().GetBool("todo-txt")
278
279 defer h.Close()
280 return h.ListContexts(c.Context(), static, todoTxt)
281 },
282 }
283 cmd.Flags().Bool("static", false, "Use static text output instead of interactive")
284 cmd.Flags().Bool("todo-txt", false, "Format output with @context prefix for todo.txt compatibility")
285 return cmd
286}
287
288func taskCompleteCmd(h *handlers.TaskHandler) *cobra.Command {
289 return &cobra.Command{
290 Use: "done [task-id]",
291 Short: "Mark task as completed",
292 Aliases: []string{"complete"},
293 Args: cobra.ExactArgs(1),
294 RunE: func(c *cobra.Command, args []string) error {
295 defer h.Close()
296 return h.Done(c.Context(), args)
297 },
298 }
299}
300
301func taskRecurCmd(h *handlers.TaskHandler) *cobra.Command {
302 root := &cobra.Command{
303 Use: "recur",
304 Short: "Manage task recurrence",
305 Aliases: []string{"repeat"},
306 }
307
308 setCmd := &cobra.Command{
309 Use: "set [task-id]",
310 Short: "Set recurrence rule for a task",
311 Args: cobra.ExactArgs(1),
312 RunE: func(c *cobra.Command, args []string) error {
313 rule, _ := c.Flags().GetString("rule")
314 until, _ := c.Flags().GetString("until")
315 defer h.Close()
316 return h.SetRecur(c.Context(), args[0], rule, until)
317 },
318 }
319 setCmd.Flags().String("rule", "", "Recurrence rule (e.g., FREQ=DAILY)")
320 setCmd.Flags().String("until", "", "Recurrence end date (YYYY-MM-DD)")
321
322 clearCmd := &cobra.Command{
323 Use: "clear [task-id]",
324 Short: "Clear recurrence rule from a task",
325 Args: cobra.ExactArgs(1),
326 RunE: func(c *cobra.Command, args []string) error {
327 defer h.Close()
328 return h.ClearRecur(c.Context(), args[0])
329 },
330 }
331
332 showCmd := &cobra.Command{
333 Use: "show [task-id]",
334 Short: "Show recurrence details for a task",
335 Args: cobra.ExactArgs(1),
336 RunE: func(c *cobra.Command, args []string) error {
337 defer h.Close()
338 return h.ShowRecur(c.Context(), args[0])
339 },
340 }
341
342 root.AddCommand(setCmd, clearCmd, showCmd)
343 return root
344}
345
346func taskDependCmd(h *handlers.TaskHandler) *cobra.Command {
347 root := &cobra.Command{
348 Use: "depend",
349 Short: "Manage task dependencies",
350 Aliases: []string{"dep", "deps"},
351 }
352
353 addCmd := &cobra.Command{
354 Use: "add [task-id] [depends-on-uuid]",
355 Short: "Add a dependency to a task",
356 Args: cobra.ExactArgs(2),
357 RunE: func(c *cobra.Command, args []string) error {
358 defer h.Close()
359 return h.AddDep(c.Context(), args[0], args[1])
360 },
361 }
362
363 removeCmd := &cobra.Command{
364 Use: "remove [task-id] [depends-on-uuid]",
365 Short: "Remove a dependency from a task",
366 Aliases: []string{"rm"},
367 Args: cobra.ExactArgs(2),
368 RunE: func(c *cobra.Command, args []string) error {
369 defer h.Close()
370 return h.RemoveDep(c.Context(), args[0], args[1])
371 },
372 }
373
374 listCmd := &cobra.Command{
375 Use: "list [task-id]",
376 Short: "List dependencies for a task",
377 Aliases: []string{"ls"},
378 Args: cobra.ExactArgs(1),
379 RunE: func(c *cobra.Command, args []string) error {
380 defer h.Close()
381 return h.ListDeps(c.Context(), args[0])
382 },
383 }
384
385 blockedByCmd := &cobra.Command{
386 Use: "blocked-by [task-id]",
387 Short: "Show tasks blocked by this task",
388 Args: cobra.ExactArgs(1),
389 RunE: func(c *cobra.Command, args []string) error {
390 defer h.Close()
391 return h.BlockedByDep(c.Context(), args[0])
392 },
393 }
394
395 root.AddCommand(addCmd, removeCmd, listCmd, blockedByCmd)
396 return root
397}