tasks.go
1 package cli 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/spf13/cobra" 14 "github.com/TransformerOS/kamaji-go/internal/style" 15 ) 16 17 type Task struct { 18 ID int `json:"id"` 19 Description string `json:"description"` 20 Priority string `json:"priority"` 21 Status string `json:"status"` 22 Tags []string `json:"tags"` 23 CreatedAt time.Time `json:"created_at"` 24 UpdatedAt time.Time `json:"updated_at"` 25 } 26 27 type TaskList struct { 28 Tasks []Task `json:"tasks"` 29 LastUpdated time.Time `json:"last_updated"` 30 } 31 32 var tasksCmd = &cobra.Command{ 33 Use: "tasks", 34 Short: "Show the task list", 35 Long: "Display all tasks in the internal task list", 36 RunE: runTasks, 37 } 38 39 var queueCmd = &cobra.Command{ 40 Use: "queue [tasks...]", 41 Short: "Add tasks to the task list", 42 Long: "Add comma-separated tasks to the internal task list", 43 RunE: runQueue, 44 } 45 46 var doCmd = &cobra.Command{ 47 Use: "do", 48 Short: "Select and execute a task from the list", 49 Long: "Interactively select and work on a task from the task list", 50 RunE: runDo, 51 } 52 53 func getTaskFile() string { 54 homeDir, _ := os.UserHomeDir() 55 kamajiDir := filepath.Join(homeDir, ".kamaji") 56 os.MkdirAll(kamajiDir, 0755) 57 return filepath.Join(kamajiDir, "tasks.json") 58 } 59 60 func loadTasks() (*TaskList, error) { 61 taskFile := getTaskFile() 62 63 if _, err := os.Stat(taskFile); os.IsNotExist(err) { 64 return &TaskList{Tasks: []Task{}, LastUpdated: time.Now()}, nil 65 } 66 67 data, err := os.ReadFile(taskFile) 68 if err != nil { 69 return nil, err 70 } 71 72 var taskList TaskList 73 if err := json.Unmarshal(data, &taskList); err != nil { 74 return nil, err 75 } 76 77 return &taskList, nil 78 } 79 80 func saveTasks(taskList *TaskList) error { 81 taskFile := getTaskFile() 82 taskList.LastUpdated = time.Now() 83 84 data, err := json.MarshalIndent(taskList, "", " ") 85 if err != nil { 86 return err 87 } 88 89 return os.WriteFile(taskFile, data, 0644) 90 } 91 92 func runTasks(cmd *cobra.Command, args []string) error { 93 taskList, err := loadTasks() 94 if err != nil { 95 return fmt.Errorf("failed to load tasks: %w", err) 96 } 97 98 if len(taskList.Tasks) == 0 { 99 fmt.Printf("\n%s\n", strings.Repeat("=", 60)) 100 fmt.Printf("%s\n", style.Info("📋 Task List")) 101 fmt.Printf("%s\n", strings.Repeat("=", 60)) 102 fmt.Printf("\n%s\n", style.Warning("No tasks in the list")) 103 fmt.Printf("\n%s\n", style.Info("💡 Add tasks with: kamaji queue \"task1, task2, task3\"")) 104 fmt.Printf("%s\n", strings.Repeat("=", 60)) 105 return nil 106 } 107 108 fmt.Printf("\n%s\n", strings.Repeat("=", 60)) 109 fmt.Printf("%s\n", style.Fire("📋 Task List")) 110 fmt.Printf("%s\n", strings.Repeat("=", 60)) 111 112 for i, task := range taskList.Tasks { 113 statusIcon := "⏳" 114 statusColor := style.Warning 115 if task.Status == "completed" { 116 statusIcon = "✅" 117 statusColor = style.Success 118 } else if task.Status == "in_progress" { 119 statusIcon = "🔄" 120 statusColor = style.Info 121 } 122 123 fmt.Printf("\n%d. %s %s\n", i+1, statusIcon, statusColor(task.Description)) 124 fmt.Printf(" Priority: %s | Status: %s\n", 125 getPriorityIcon(task.Priority), task.Status) 126 127 if len(task.Tags) > 0 { 128 fmt.Printf(" Tags: %s\n", strings.Join(task.Tags, ", ")) 129 } 130 131 fmt.Printf(" Created: %s\n", task.CreatedAt.Format("2006-01-02 15:04")) 132 } 133 134 fmt.Printf("\n%s\n", strings.Repeat("=", 60)) 135 fmt.Printf("Total: %d tasks\n", len(taskList.Tasks)) 136 fmt.Printf("\n%s\n", style.Info("💡 Use 'kamaji do' to work on a task")) 137 fmt.Printf("%s\n", strings.Repeat("=", 60)) 138 139 return nil 140 } 141 142 func runQueue(cmd *cobra.Command, args []string) error { 143 if len(args) == 0 { 144 return fmt.Errorf("please provide tasks to add") 145 } 146 147 taskList, err := loadTasks() 148 if err != nil { 149 return fmt.Errorf("failed to load tasks: %w", err) 150 } 151 152 // Join all arguments and split by comma 153 tasksStr := strings.Join(args, " ") 154 taskDescriptions := strings.Split(tasksStr, ",") 155 156 nextID := 1 157 if len(taskList.Tasks) > 0 { 158 for _, task := range taskList.Tasks { 159 if task.ID >= nextID { 160 nextID = task.ID + 1 161 } 162 } 163 } 164 165 addedCount := 0 166 for _, desc := range taskDescriptions { 167 desc = strings.TrimSpace(desc) 168 if desc == "" { 169 continue 170 } 171 172 task := Task{ 173 ID: nextID, 174 Description: desc, 175 Priority: "medium", 176 Status: "pending", 177 Tags: []string{}, 178 CreatedAt: time.Now(), 179 UpdatedAt: time.Now(), 180 } 181 182 taskList.Tasks = append(taskList.Tasks, task) 183 nextID++ 184 addedCount++ 185 } 186 187 if err := saveTasks(taskList); err != nil { 188 return fmt.Errorf("failed to save tasks: %w", err) 189 } 190 191 fmt.Printf("\n%s\n", style.Success(fmt.Sprintf("✅ Added %d task(s) to the list", addedCount))) 192 fmt.Printf("\n%s\n", style.Info("📋 Use 'kamaji tasks' to view all tasks")) 193 fmt.Printf("%s\n", style.Info("🛠️ Use 'kamaji do' to work on a task")) 194 195 return nil 196 } 197 198 func runDo(cmd *cobra.Command, args []string) error { 199 taskList, err := loadTasks() 200 if err != nil { 201 return fmt.Errorf("failed to load tasks: %w", err) 202 } 203 204 // Filter pending tasks 205 pendingTasks := []Task{} 206 for _, task := range taskList.Tasks { 207 if task.Status == "pending" { 208 pendingTasks = append(pendingTasks, task) 209 } 210 } 211 212 if len(pendingTasks) == 0 { 213 fmt.Printf("\n%s\n", style.Warning("📋 No pending tasks available")) 214 fmt.Printf("\n%s\n", style.Info("💡 Add tasks with: kamaji queue \"task description\"")) 215 return nil 216 } 217 218 // Show pending tasks 219 fmt.Printf("\n%s\n", strings.Repeat("=", 60)) 220 fmt.Printf("%s\n", style.Fire("📋 Select a Task to Work On")) 221 fmt.Printf("%s\n", strings.Repeat("=", 60)) 222 223 for i, task := range pendingTasks { 224 fmt.Printf("\n%d. %s %s\n", i+1, getPriorityIcon(task.Priority), task.Description) 225 fmt.Printf(" Priority: %s | Created: %s\n", 226 task.Priority, task.CreatedAt.Format("2006-01-02 15:04")) 227 } 228 229 fmt.Printf("\n%s\n", strings.Repeat("=", 60)) 230 fmt.Printf("Select task (1-%d) or 'q' to quit: ", len(pendingTasks)) 231 232 reader := bufio.NewReader(os.Stdin) 233 input, err := reader.ReadString('\n') 234 if err != nil { 235 return fmt.Errorf("failed to read input: %w", err) 236 } 237 238 input = strings.TrimSpace(input) 239 if input == "q" || input == "quit" { 240 fmt.Printf("%s\n", style.Warning("❌ Cancelled")) 241 return nil 242 } 243 244 taskNum, err := strconv.Atoi(input) 245 if err != nil || taskNum < 1 || taskNum > len(pendingTasks) { 246 return fmt.Errorf("invalid task number: %s", input) 247 } 248 249 selectedTask := pendingTasks[taskNum-1] 250 251 fmt.Printf("\n%s\n", style.Success(fmt.Sprintf("🎯 Selected: %s", selectedTask.Description))) 252 fmt.Printf("\n%s\n", style.Info("🛠️ Starting work mode...")) 253 254 // Mark task as in progress 255 for i := range taskList.Tasks { 256 if taskList.Tasks[i].ID == selectedTask.ID { 257 taskList.Tasks[i].Status = "in_progress" 258 taskList.Tasks[i].UpdatedAt = time.Now() 259 break 260 } 261 } 262 263 if err := saveTasks(taskList); err != nil { 264 return fmt.Errorf("failed to update task status: %w", err) 265 } 266 267 // Start work mode with the selected task 268 return runWork(cmd, []string{selectedTask.Description}) 269 } 270 271 func getPriorityIcon(priority string) string { 272 switch priority { 273 case "high": 274 return "🔴" 275 case "medium": 276 return "🟡" 277 case "low": 278 return "🟢" 279 default: 280 return "⚪" 281 } 282 }