list.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package task
  6
  7import (
  8	"context"
  9	"time"
 10
 11	"git.secluded.site/go-lunatask"
 12	"git.secluded.site/lune/internal/mcp/shared"
 13	"github.com/modelcontextprotocol/go-sdk/mcp"
 14)
 15
 16// ListToolName is the name of the list tasks tool.
 17const ListToolName = "list_tasks"
 18
 19// ListToolDescription describes the list tasks tool for LLMs.
 20const ListToolDescription = `Lists tasks from Lunatask.
 21
 22Optional filters:
 23- area_id: Filter by area UUID
 24- status: Filter by status (later, next, started, waiting, completed)
 25- include_completed: Include completed tasks (default: false, only shows today's)
 26
 27Note: Due to end-to-end encryption, task names and notes are not available.
 28Only metadata (ID, status, dates, priority, etc.) is returned.
 29
 30Returns a list of tasks with their metadata and deep links.`
 31
 32// ListInput is the input schema for listing tasks.
 33type ListInput struct {
 34	AreaID           *string `json:"area_id,omitempty"`
 35	Status           *string `json:"status,omitempty"`
 36	IncludeCompleted *bool   `json:"include_completed,omitempty"`
 37}
 38
 39// ListOutput is the output schema for listing tasks.
 40type ListOutput struct {
 41	Tasks []Summary `json:"tasks"`
 42	Count int       `json:"count"`
 43}
 44
 45// Summary represents a task in list output.
 46type Summary struct {
 47	ID          string  `json:"id"`
 48	DeepLink    string  `json:"deep_link"`
 49	Status      *string `json:"status,omitempty"`
 50	Priority    *int    `json:"priority,omitempty"`
 51	ScheduledOn *string `json:"scheduled_on,omitempty"`
 52	CreatedAt   string  `json:"created_at"`
 53	AreaID      *string `json:"area_id,omitempty"`
 54	GoalID      *string `json:"goal_id,omitempty"`
 55}
 56
 57// HandleList lists tasks.
 58func (h *Handler) HandleList(
 59	ctx context.Context,
 60	_ *mcp.CallToolRequest,
 61	input ListInput,
 62) (*mcp.CallToolResult, ListOutput, error) {
 63	if input.AreaID != nil {
 64		if err := lunatask.ValidateUUID(*input.AreaID); err != nil {
 65			return shared.ErrorResult("invalid area_id: expected UUID"), ListOutput{}, nil
 66		}
 67	}
 68
 69	if input.Status != nil {
 70		if _, err := lunatask.ParseTaskStatus(*input.Status); err != nil {
 71			return shared.ErrorResult("invalid status: must be later, next, started, waiting, or completed"), ListOutput{}, nil
 72		}
 73	}
 74
 75	tasks, err := h.client.ListTasks(ctx, nil)
 76	if err != nil {
 77		return shared.ErrorResult(err.Error()), ListOutput{}, nil
 78	}
 79
 80	opts := &lunatask.TaskFilterOptions{
 81		AreaID:           input.AreaID,
 82		IncludeCompleted: input.IncludeCompleted != nil && *input.IncludeCompleted,
 83		Today:            time.Now(),
 84	}
 85
 86	if input.Status != nil {
 87		s := lunatask.TaskStatus(*input.Status)
 88		opts.Status = &s
 89	}
 90
 91	filtered := lunatask.FilterTasks(tasks, opts)
 92	summaries := buildSummaries(filtered)
 93
 94	return nil, ListOutput{
 95		Tasks: summaries,
 96		Count: len(summaries),
 97	}, nil
 98}
 99
100func buildSummaries(tasks []lunatask.Task) []Summary {
101	summaries := make([]Summary, 0, len(tasks))
102
103	for _, task := range tasks {
104		summary := Summary{
105			ID:        task.ID,
106			CreatedAt: task.CreatedAt.Format(time.RFC3339),
107			AreaID:    task.AreaID,
108			GoalID:    task.GoalID,
109		}
110
111		summary.DeepLink, _ = lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)
112
113		if task.Status != nil {
114			s := string(*task.Status)
115			summary.Status = &s
116		}
117
118		if task.Priority != nil {
119			p := int(*task.Priority)
120			summary.Priority = &p
121		}
122
123		if task.ScheduledOn != nil {
124			s := task.ScheduledOn.Format("2006-01-02")
125			summary.ScheduledOn = &s
126		}
127
128		summaries = append(summaries, summary)
129	}
130
131	return summaries
132}