/ go / internal / cli / tasks.go
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  }