cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package main
2
3import (
4 "fmt"
5 "strconv"
6 "strings"
7
8 "github.com/charmbracelet/log"
9
10 "github.com/spf13/cobra"
11 "github.com/stormlightlabs/noteleaf/internal/handlers"
12 "github.com/stormlightlabs/noteleaf/internal/ui"
13)
14
15func rootCmd() *cobra.Command {
16 return &cobra.Command{
17 Use: "noteleaf",
18 Long: ui.Georgia.ColoredInViewport(),
19 Short: "A TaskWarrior-inspired CLI with notes, media queues and reading lists",
20 RunE: func(cmd *cobra.Command, args []string) error {
21 if len(args) == 0 {
22 return cmd.Help()
23 }
24
25 output := strings.Join(args, " ")
26 fmt.Println(output)
27 return nil
28 },
29 }
30}
31
32func todoCmd() *cobra.Command {
33 root := &cobra.Command{
34 Use: "todo",
35 Short: "task management",
36 }
37
38 root.AddCommand(&cobra.Command{
39 Use: "add [description]",
40 Short: "Add a new task",
41 Aliases: []string{"create", "new"},
42 Args: cobra.MinimumNArgs(1),
43 RunE: func(cmd *cobra.Command, args []string) error {
44 return handlers.CreateTask(cmd.Context(), args)
45 },
46 })
47
48 root.AddCommand(&cobra.Command{
49 Use: "list",
50 Short: "List tasks",
51 Aliases: []string{"ls"},
52 RunE: func(cmd *cobra.Command, args []string) error {
53 return handlers.ListTasks(cmd.Context(), args)
54 },
55 })
56
57 root.AddCommand(&cobra.Command{
58 Use: "view [task-id]",
59 Short: "View task by ID",
60 Args: cobra.ExactArgs(1),
61 RunE: func(cmd *cobra.Command, args []string) error {
62 return handlers.ViewTask(cmd.Context(), args)
63 },
64 })
65
66 root.AddCommand(&cobra.Command{
67 Use: "update [task-id] [options...]",
68 Short: "Update task properties",
69 Args: cobra.MinimumNArgs(1),
70 RunE: func(cmd *cobra.Command, args []string) error {
71 return handlers.UpdateTask(cmd.Context(), args)
72 },
73 })
74
75 root.AddCommand(&cobra.Command{
76 Use: "delete [task-id]",
77 Short: "Delete a task",
78 Args: cobra.ExactArgs(1),
79 RunE: func(cmd *cobra.Command, args []string) error {
80 return handlers.DeleteTask(cmd.Context(), args)
81 },
82 })
83
84 root.AddCommand(&cobra.Command{
85 Use: "projects",
86 Short: "List projects",
87 Aliases: []string{"proj"},
88 RunE: func(cmd *cobra.Command, args []string) error {
89 fmt.Println("Listing projects...")
90 return nil
91 },
92 })
93
94 root.AddCommand(&cobra.Command{
95 Use: "tags",
96 Short: "List tags",
97 Aliases: []string{"t"},
98 RunE: func(cmd *cobra.Command, args []string) error {
99 fmt.Println("Listing tags...")
100 return nil
101 },
102 })
103
104 root.AddCommand(&cobra.Command{
105 Use: "contexts",
106 Short: "List contexts (locations)",
107 Aliases: []string{"loc", "ctx", "locations"},
108 RunE: func(cmd *cobra.Command, args []string) error {
109 fmt.Println("Listing task contexts...")
110 return nil
111 },
112 })
113
114 root.AddCommand(&cobra.Command{
115 Use: "done [task-id]",
116 Short: "Mark task as completed",
117 Aliases: []string{"complete"},
118 Args: cobra.ExactArgs(1),
119 RunE: func(cmd *cobra.Command, args []string) error {
120 return handlers.DoneTask(cmd.Context(), args)
121 },
122 })
123
124 return root
125}
126
127func movieCmd() *cobra.Command {
128 root := &cobra.Command{
129 Use: "movie",
130 Short: "Manage movie watch queue",
131 }
132
133 root.AddCommand(&cobra.Command{
134 Use: "add [title]",
135 Short: "Add movie to watch queue",
136 Args: cobra.MinimumNArgs(1),
137 RunE: func(cmd *cobra.Command, args []string) error {
138 title := args[0]
139 fmt.Printf("Adding movie: %s\n", title)
140 // TODO: Implement movie addition
141 return nil
142 },
143 })
144
145 root.AddCommand(&cobra.Command{
146 Use: "list",
147 Short: "List movies in queue",
148 RunE: func(cmd *cobra.Command, args []string) error {
149 fmt.Println("Listing movies...")
150 // TODO: Implement movie listing
151 return nil
152 },
153 })
154
155 return root
156}
157
158func tvCmd() *cobra.Command {
159 root := &cobra.Command{
160 Use: "tv",
161 Short: "Manage TV show watch queue",
162 }
163
164 root.AddCommand(&cobra.Command{
165 Use: "add [title]",
166 Short: "Add TV show to watch queue",
167 Args: cobra.MinimumNArgs(1),
168 RunE: func(cmd *cobra.Command, args []string) error {
169 title := args[0]
170 fmt.Printf("Adding TV show: %s\n", title)
171 // TODO: Implement TV show addition
172 return nil
173 },
174 })
175
176 root.AddCommand(&cobra.Command{
177 Use: "list",
178 Short: "List TV shows in queue",
179 RunE: func(cmd *cobra.Command, args []string) error {
180 fmt.Println("Listing TV shows...")
181 // TODO: Implement TV show listing
182 return nil
183 },
184 })
185
186 return root
187}
188
189func bookCmd() *cobra.Command {
190 root := &cobra.Command{
191 Use: "book",
192 Short: "Manage reading list",
193 }
194
195 // book add - Search and add book to reading list
196 addCmd := &cobra.Command{
197 Use: "add [search query...]",
198 Short: "Search and add book to reading list",
199 Long: `Search for books and add them to your reading list.
200
201By default, shows search results in a simple list format where you can select by number.
202Use the -i flag for an interactive interface with navigation keys.`,
203 RunE: func(cmd *cobra.Command, args []string) error {
204 interactive, _ := cmd.Flags().GetBool("interactive")
205 return handlers.SearchAndAddWithOptions(cmd.Context(), args, interactive)
206 },
207 }
208 addCmd.Flags().BoolP("interactive", "i", false, "Use interactive interface for book selection")
209 root.AddCommand(addCmd)
210
211 // book list - Show reading queue with progress
212 root.AddCommand(&cobra.Command{
213 Use: "list [--all|--reading|--finished|--queued]",
214 Short: "Show reading queue with progress",
215 RunE: func(cmd *cobra.Command, args []string) error {
216 return handlers.ListBooks(cmd.Context(), args)
217 },
218 })
219
220 // book reading - Mark book as currently reading (alias for update status)
221 root.AddCommand(&cobra.Command{
222 Use: "reading <id>",
223 Short: "Mark book as currently reading",
224 Args: cobra.ExactArgs(1),
225 RunE: func(cmd *cobra.Command, args []string) error {
226 return handlers.UpdateBookStatus(cmd.Context(), []string{args[0], "reading"})
227 },
228 })
229
230 // book finished - Mark book as completed
231 root.AddCommand(&cobra.Command{
232 Use: "finished <id>",
233 Short: "Mark book as completed",
234 Aliases: []string{"read"},
235 Args: cobra.ExactArgs(1),
236 RunE: func(cmd *cobra.Command, args []string) error {
237 return handlers.UpdateBookStatus(cmd.Context(), []string{args[0], "finished"})
238 },
239 })
240
241 // book remove - Remove from reading list
242 root.AddCommand(&cobra.Command{
243 Use: "remove <id>",
244 Short: "Remove from reading list",
245 Aliases: []string{"rm"},
246 Args: cobra.ExactArgs(1),
247 RunE: func(cmd *cobra.Command, args []string) error {
248 return handlers.UpdateBookStatus(cmd.Context(), []string{args[0], "removed"})
249 },
250 })
251
252 // book progress - Update reading progress percentage
253 root.AddCommand(&cobra.Command{
254 Use: "progress <id> <percentage>",
255 Short: "Update reading progress percentage (0-100)",
256 Args: cobra.ExactArgs(2),
257 RunE: func(cmd *cobra.Command, args []string) error {
258 return handlers.UpdateBookProgress(cmd.Context(), args)
259 },
260 })
261
262 // book update - Update book status
263 root.AddCommand(&cobra.Command{
264 Use: "update <id> <status>",
265 Short: "Update book status (queued|reading|finished|removed)",
266 Args: cobra.ExactArgs(2),
267 RunE: func(cmd *cobra.Command, args []string) error {
268 return handlers.UpdateBookStatus(cmd.Context(), args)
269 },
270 })
271
272 return root
273}
274
275func noteCmd() *cobra.Command {
276 root := &cobra.Command{
277 Use: "note",
278 Short: "Manage notes",
279 }
280
281 handler, err := handlers.NewNoteHandler()
282 if err != nil {
283 log.Fatalf("failed to instantiate note handler: %v", err)
284 }
285
286 createCmd := &cobra.Command{
287 Use: "create [title] [content...]",
288 Short: "Create a new note",
289 Aliases: []string{"new"},
290 RunE: func(cmd *cobra.Command, args []string) error {
291 interactive, _ := cmd.Flags().GetBool("interactive")
292 filePath, _ := cmd.Flags().GetString("file")
293
294 var title, content string
295 if len(args) > 0 {
296 title = args[0]
297 }
298 if len(args) > 1 {
299 content = strings.Join(args[1:], " ")
300 }
301
302 if err != nil {
303 return err
304 }
305 defer handler.Close()
306 return handler.Create(cmd.Context(), title, content, filePath, interactive)
307 },
308 }
309 createCmd.Flags().BoolP("interactive", "i", false, "Open interactive editor")
310 createCmd.Flags().StringP("file", "f", "", "Create note from markdown file")
311 root.AddCommand(createCmd)
312
313 listCmd := &cobra.Command{
314 Use: "list [--archived] [--tags=tag1,tag2]",
315 Short: "Opens interactive TUI browser for navigating and viewing notes",
316 Aliases: []string{"ls"},
317 RunE: func(cmd *cobra.Command, args []string) error {
318 archived, _ := cmd.Flags().GetBool("archived")
319 tagsStr, _ := cmd.Flags().GetString("tags")
320
321 var tags []string
322 if tagsStr != "" {
323 tags = strings.Split(tagsStr, ",")
324 for i := range tags {
325 tags[i] = strings.TrimSpace(tags[i])
326 }
327 }
328
329 handler, err := handlers.NewNoteHandler()
330 if err != nil {
331 return err
332 }
333 defer handler.Close()
334 return handler.List(cmd.Context(), false, archived, tags)
335 },
336 }
337 listCmd.Flags().BoolP("archived", "a", false, "Show archived notes")
338 listCmd.Flags().String("tags", "", "Filter by tags (comma-separated)")
339 root.AddCommand(listCmd)
340
341 root.AddCommand(&cobra.Command{
342 Use: "read [note-id]",
343 Short: "Display formatted note content with syntax highlighting",
344 Aliases: []string{"view"},
345 Args: cobra.ExactArgs(1),
346 RunE: func(cmd *cobra.Command, args []string) error {
347 noteID, err := strconv.ParseInt(args[0], 10, 64)
348 if err != nil {
349 return fmt.Errorf("invalid note ID: %s", args[0])
350 }
351 handler, err := handlers.NewNoteHandler()
352 if err != nil {
353 return err
354 }
355 defer handler.Close()
356 return handler.View(cmd.Context(), noteID)
357 },
358 })
359
360 root.AddCommand(&cobra.Command{
361 Use: "edit [note-id]",
362 Short: "Edit note in configured editor",
363 Args: cobra.ExactArgs(1),
364 RunE: func(cmd *cobra.Command, args []string) error {
365 noteID, err := strconv.ParseInt(args[0], 10, 64)
366 if err != nil {
367 return fmt.Errorf("invalid note ID: %s", args[0])
368 }
369 handler, err := handlers.NewNoteHandler()
370 if err != nil {
371 return err
372 }
373 defer handler.Close()
374 return handler.Edit(cmd.Context(), noteID)
375 },
376 })
377
378 root.AddCommand(&cobra.Command{
379 Use: "remove [note-id]",
380 Short: "Permanently removes the note file and metadata",
381 Aliases: []string{"rm", "delete", "del"},
382 Args: cobra.ExactArgs(1),
383 RunE: func(cmd *cobra.Command, args []string) error {
384 noteID, err := strconv.ParseInt(args[0], 10, 64)
385 if err != nil {
386 return fmt.Errorf("invalid note ID: %s", args[0])
387 }
388 handler, err := handlers.NewNoteHandler()
389 if err != nil {
390 return err
391 }
392 defer handler.Close()
393 return handler.Delete(cmd.Context(), noteID)
394 },
395 })
396
397 return root
398}
399
400func statusCmd() *cobra.Command {
401 return &cobra.Command{
402 Use: "status",
403 Short: "Show application status and configuration",
404 RunE: func(cmd *cobra.Command, args []string) error {
405 return handlers.Status(cmd.Context(), args)
406 },
407 }
408}
409
410func resetCmd() *cobra.Command {
411 return &cobra.Command{
412 Use: "reset",
413 Short: "Reset the application (removes all data)",
414 RunE: func(cmd *cobra.Command, args []string) error {
415 return handlers.Reset(cmd.Context(), args)
416 },
417 }
418}
419
420func setupCmd() *cobra.Command {
421 return &cobra.Command{
422 Use: "setup",
423 Short: "Initialize the application database and configuration",
424 RunE: func(cmd *cobra.Command, args []string) error {
425 return handlers.Setup(cmd.Context(), args)
426 },
427 }
428}
429
430func confCmd() *cobra.Command {
431 return &cobra.Command{
432 Use: "config [key] [value]",
433 Short: "Manage configuration",
434 Args: cobra.ExactArgs(2),
435 RunE: func(cmd *cobra.Command, args []string) error {
436 key, value := args[0], args[1]
437 fmt.Printf("Setting config %s = %s\n", key, value)
438 // TODO: Implement config management
439 return nil
440 },
441 }
442}