@doist/todoist-ai 4.16.0 → 4.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/filter-helpers.d.ts +1 -1
  2. package/dist/index.d.ts +1044 -196
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +61 -81
  5. package/dist/main.js +15 -23
  6. package/dist/mcp-helpers.d.ts +5 -5
  7. package/dist/mcp-helpers.d.ts.map +1 -1
  8. package/dist/mcp-server-BADReNAy.js +3092 -0
  9. package/dist/todoist-tool.d.ts +9 -3
  10. package/dist/todoist-tool.d.ts.map +1 -1
  11. package/dist/tool-helpers.d.ts +1 -1
  12. package/dist/tools/add-comments.d.ts +69 -3
  13. package/dist/tools/add-comments.d.ts.map +1 -1
  14. package/dist/tools/add-projects.d.ts +34 -3
  15. package/dist/tools/add-projects.d.ts.map +1 -1
  16. package/dist/tools/add-sections.d.ts +14 -1
  17. package/dist/tools/add-sections.d.ts.map +1 -1
  18. package/dist/tools/add-tasks.d.ts +65 -10
  19. package/dist/tools/add-tasks.d.ts.map +1 -1
  20. package/dist/tools/complete-tasks.d.ts +20 -1
  21. package/dist/tools/complete-tasks.d.ts.map +1 -1
  22. package/dist/tools/delete-object.d.ts +16 -3
  23. package/dist/tools/delete-object.d.ts.map +1 -1
  24. package/dist/tools/fetch.d.ts +8 -1
  25. package/dist/tools/fetch.d.ts.map +1 -1
  26. package/dist/tools/find-activity.d.ts +44 -7
  27. package/dist/tools/find-activity.d.ts.map +1 -1
  28. package/dist/tools/find-comments.d.ts +69 -3
  29. package/dist/tools/find-comments.d.ts.map +1 -1
  30. package/dist/tools/find-completed-tasks.d.ts +63 -5
  31. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  32. package/dist/tools/find-project-collaborators.d.ts +33 -2
  33. package/dist/tools/find-project-collaborators.d.ts.map +1 -1
  34. package/dist/tools/find-projects.d.ts +35 -1
  35. package/dist/tools/find-projects.d.ts.map +1 -1
  36. package/dist/tools/find-sections.d.ts +15 -1
  37. package/dist/tools/find-sections.d.ts.map +1 -1
  38. package/dist/tools/find-tasks-by-date.d.ts +61 -3
  39. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  40. package/dist/tools/find-tasks.d.ts +63 -5
  41. package/dist/tools/find-tasks.d.ts.map +1 -1
  42. package/dist/tools/get-overview.d.ts +24 -1
  43. package/dist/tools/get-overview.d.ts.map +1 -1
  44. package/dist/tools/manage-assignments.d.ts +39 -2
  45. package/dist/tools/manage-assignments.d.ts.map +1 -1
  46. package/dist/tools/search.d.ts +17 -1
  47. package/dist/tools/search.d.ts.map +1 -1
  48. package/dist/tools/update-comments.d.ts +76 -3
  49. package/dist/tools/update-comments.d.ts.map +1 -1
  50. package/dist/tools/update-projects.d.ts +43 -1
  51. package/dist/tools/update-projects.d.ts.map +1 -1
  52. package/dist/tools/update-sections.d.ts +17 -3
  53. package/dist/tools/update-sections.d.ts.map +1 -1
  54. package/dist/tools/update-tasks.d.ts +79 -13
  55. package/dist/tools/update-tasks.d.ts.map +1 -1
  56. package/dist/tools/user-info.d.ts +19 -1
  57. package/dist/tools/user-info.d.ts.map +1 -1
  58. package/dist/utils/assignment-validator.d.ts +2 -2
  59. package/dist/utils/output-schemas.d.ts +233 -0
  60. package/dist/utils/output-schemas.d.ts.map +1 -0
  61. package/dist/utils/response-builders.d.ts +1 -3
  62. package/dist/utils/response-builders.d.ts.map +1 -1
  63. package/dist/utils/test-helpers.d.ts +1 -1
  64. package/dist/utils/user-resolver.d.ts +1 -1
  65. package/package.json +10 -8
  66. package/dist/filter-helpers.js +0 -79
  67. package/dist/mcp-helpers.js +0 -71
  68. package/dist/mcp-server.js +0 -142
  69. package/dist/todoist-tool.js +0 -1
  70. package/dist/tool-helpers.js +0 -125
  71. package/dist/tool-helpers.test.d.ts +0 -2
  72. package/dist/tool-helpers.test.d.ts.map +0 -1
  73. package/dist/tool-helpers.test.js +0 -223
  74. package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
  75. package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
  76. package/dist/tools/__tests__/add-comments.test.js +0 -241
  77. package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
  78. package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
  79. package/dist/tools/__tests__/add-projects.test.js +0 -174
  80. package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
  81. package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
  82. package/dist/tools/__tests__/add-sections.test.js +0 -185
  83. package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
  84. package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
  85. package/dist/tools/__tests__/add-tasks.test.js +0 -606
  86. package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
  87. package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
  88. package/dist/tools/__tests__/assignment-integration.test.js +0 -428
  89. package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
  90. package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
  91. package/dist/tools/__tests__/complete-tasks.test.js +0 -206
  92. package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
  93. package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
  94. package/dist/tools/__tests__/delete-object.test.js +0 -110
  95. package/dist/tools/__tests__/fetch.test.d.ts +0 -2
  96. package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
  97. package/dist/tools/__tests__/fetch.test.js +0 -279
  98. package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
  99. package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
  100. package/dist/tools/__tests__/find-activity.test.js +0 -229
  101. package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
  102. package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
  103. package/dist/tools/__tests__/find-comments.test.js +0 -236
  104. package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
  105. package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
  106. package/dist/tools/__tests__/find-completed-tasks.test.js +0 -423
  107. package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
  108. package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
  109. package/dist/tools/__tests__/find-projects.test.js +0 -154
  110. package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
  111. package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
  112. package/dist/tools/__tests__/find-sections.test.js +0 -313
  113. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
  114. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
  115. package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
  116. package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
  117. package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
  118. package/dist/tools/__tests__/find-tasks.test.js +0 -771
  119. package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
  120. package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
  121. package/dist/tools/__tests__/get-overview.test.js +0 -225
  122. package/dist/tools/__tests__/search.test.d.ts +0 -2
  123. package/dist/tools/__tests__/search.test.d.ts.map +0 -1
  124. package/dist/tools/__tests__/search.test.js +0 -206
  125. package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
  126. package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
  127. package/dist/tools/__tests__/update-comments.test.js +0 -294
  128. package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
  129. package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
  130. package/dist/tools/__tests__/update-projects.test.js +0 -217
  131. package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
  132. package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
  133. package/dist/tools/__tests__/update-sections.test.js +0 -169
  134. package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
  135. package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
  136. package/dist/tools/__tests__/update-tasks.test.js +0 -788
  137. package/dist/tools/__tests__/user-info.test.d.ts +0 -2
  138. package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
  139. package/dist/tools/__tests__/user-info.test.js +0 -139
  140. package/dist/tools/add-comments.js +0 -89
  141. package/dist/tools/add-projects.js +0 -63
  142. package/dist/tools/add-sections.js +0 -74
  143. package/dist/tools/add-tasks.js +0 -169
  144. package/dist/tools/complete-tasks.js +0 -68
  145. package/dist/tools/delete-object.js +0 -79
  146. package/dist/tools/fetch.js +0 -102
  147. package/dist/tools/find-activity.js +0 -221
  148. package/dist/tools/find-comments.js +0 -148
  149. package/dist/tools/find-completed-tasks.js +0 -168
  150. package/dist/tools/find-project-collaborators.js +0 -151
  151. package/dist/tools/find-projects.js +0 -101
  152. package/dist/tools/find-sections.js +0 -101
  153. package/dist/tools/find-tasks-by-date.js +0 -198
  154. package/dist/tools/find-tasks.js +0 -329
  155. package/dist/tools/get-overview.js +0 -249
  156. package/dist/tools/manage-assignments.js +0 -337
  157. package/dist/tools/search.js +0 -65
  158. package/dist/tools/update-comments.js +0 -82
  159. package/dist/tools/update-projects.js +0 -84
  160. package/dist/tools/update-sections.js +0 -70
  161. package/dist/tools/update-tasks.js +0 -179
  162. package/dist/tools/user-info.js +0 -142
  163. package/dist/utils/assignment-validator.js +0 -253
  164. package/dist/utils/constants.js +0 -45
  165. package/dist/utils/duration-parser.js +0 -96
  166. package/dist/utils/duration-parser.test.d.ts +0 -2
  167. package/dist/utils/duration-parser.test.d.ts.map +0 -1
  168. package/dist/utils/duration-parser.test.js +0 -147
  169. package/dist/utils/labels.js +0 -18
  170. package/dist/utils/priorities.js +0 -20
  171. package/dist/utils/response-builders.js +0 -210
  172. package/dist/utils/sanitize-data.js +0 -37
  173. package/dist/utils/sanitize-data.test.d.ts +0 -2
  174. package/dist/utils/sanitize-data.test.d.ts.map +0 -1
  175. package/dist/utils/sanitize-data.test.js +0 -93
  176. package/dist/utils/test-helpers.js +0 -237
  177. package/dist/utils/tool-names.js +0 -40
  178. package/dist/utils/user-resolver.js +0 -179
@@ -1,198 +0,0 @@
1
- import { addDays, formatISO } from 'date-fns';
2
- import { z } from 'zod';
3
- import { appendToQuery, buildResponsibleUserQueryFilter, RESPONSIBLE_USER_FILTERING, resolveResponsibleUser, } from '../filter-helpers.js';
4
- import { getToolOutput } from '../mcp-helpers.js';
5
- import { getTasksByFilter } from '../tool-helpers.js';
6
- import { ApiLimits } from '../utils/constants.js';
7
- import { generateLabelsFilter, LabelsSchema } from '../utils/labels.js';
8
- import { generateTaskNextSteps, getDateString, previewTasks, summarizeList, } from '../utils/response-builders.js';
9
- import { ToolNames } from '../utils/tool-names.js';
10
- const ArgsSchema = {
11
- startDate: z
12
- .string()
13
- .regex(/^(\d{4}-\d{2}-\d{2}|today)$/)
14
- .optional()
15
- .describe("The start date to get the tasks for. Format: YYYY-MM-DD or 'today'."),
16
- overdueOption: z
17
- .enum(['overdue-only', 'include-overdue', 'exclude-overdue'])
18
- .optional()
19
- .describe("How to handle overdue tasks. 'overdue-only' to get only overdue tasks, 'include-overdue' to include overdue tasks along with tasks for the specified date(s), and 'exclude-overdue' to exclude overdue tasks. Default is 'include-overdue'."),
20
- daysCount: z
21
- .number()
22
- .int()
23
- .min(1)
24
- .max(30)
25
- .default(1)
26
- .describe('The number of days to get the tasks for, starting from the start date. Default is 1 which means only tasks for the start date.'),
27
- limit: z
28
- .number()
29
- .int()
30
- .min(1)
31
- .max(ApiLimits.TASKS_MAX)
32
- .default(ApiLimits.TASKS_DEFAULT)
33
- .describe('The maximum number of tasks to return.'),
34
- cursor: z
35
- .string()
36
- .optional()
37
- .describe('The cursor to get the next page of tasks (cursor is obtained from the previous call to this tool, with the same parameters).'),
38
- responsibleUser: z
39
- .string()
40
- .optional()
41
- .describe('Find tasks assigned to this user. Can be a user ID, name, or email address.'),
42
- responsibleUserFiltering: z
43
- .enum(RESPONSIBLE_USER_FILTERING)
44
- .optional()
45
- .describe('How to filter by responsible user when responsibleUser is not provided. "assigned" = only tasks assigned to others; "unassignedOrMe" = only unassigned tasks or tasks assigned to me; "all" = all tasks regardless of assignment. Default is "unassignedOrMe".'),
46
- ...LabelsSchema,
47
- };
48
- const findTasksByDate = {
49
- name: ToolNames.FIND_TASKS_BY_DATE,
50
- description: "Get tasks by date range. Use startDate 'today' to get today's tasks including overdue items, or provide a specific date/date range.",
51
- parameters: ArgsSchema,
52
- async execute(args, client) {
53
- if (!args.startDate && args.overdueOption !== 'overdue-only') {
54
- throw new Error('Either startDate must be provided or overdueOption must be set to overdue-only');
55
- }
56
- // Resolve assignee name to user ID if provided
57
- const resolved = await resolveResponsibleUser(client, args.responsibleUser);
58
- const resolvedAssigneeId = resolved?.userId;
59
- const assigneeEmail = resolved?.email;
60
- let query = '';
61
- if (args.overdueOption === 'overdue-only') {
62
- query = 'overdue';
63
- }
64
- else if (args.startDate === 'today') {
65
- // For 'today', include overdue unless explicitly excluded
66
- // Use parentheses to ensure correct operator precedence when combining with other filters
67
- query = args.overdueOption === 'exclude-overdue' ? 'today' : '(today | overdue)';
68
- }
69
- else if (args.startDate) {
70
- // For specific dates, never include overdue tasks
71
- const startDate = args.startDate;
72
- const endDate = addDays(startDate, args.daysCount);
73
- const endDateStr = formatISO(endDate, { representation: 'date' });
74
- query = `(due after: ${startDate} | due: ${startDate}) & due before: ${endDateStr}`;
75
- }
76
- // Add labels filter
77
- const labelsFilter = generateLabelsFilter(args.labels, args.labelsOperator);
78
- if (labelsFilter.length > 0) {
79
- query = appendToQuery(query, `(${labelsFilter})`);
80
- }
81
- // Add responsible user filtering to the query (backend filtering)
82
- const responsibleUserFilter = buildResponsibleUserQueryFilter({
83
- resolvedAssigneeId,
84
- assigneeEmail,
85
- responsibleUserFiltering: args.responsibleUserFiltering,
86
- });
87
- query = appendToQuery(query, responsibleUserFilter);
88
- const result = await getTasksByFilter({
89
- client,
90
- query,
91
- cursor: args.cursor,
92
- limit: args.limit,
93
- });
94
- // No need for post-fetch filtering since it's handled in the query
95
- const filteredTasks = result.tasks;
96
- const textContent = generateTextContent({
97
- tasks: filteredTasks,
98
- args,
99
- nextCursor: result.nextCursor,
100
- assigneeEmail,
101
- });
102
- return getToolOutput({
103
- textContent,
104
- structuredContent: {
105
- tasks: filteredTasks,
106
- nextCursor: result.nextCursor,
107
- totalCount: filteredTasks.length,
108
- hasMore: Boolean(result.nextCursor),
109
- appliedFilters: args,
110
- },
111
- });
112
- },
113
- };
114
- function generateTextContent({ tasks, args, nextCursor, assigneeEmail, }) {
115
- // Generate filter description
116
- const filterHints = [];
117
- if (args.overdueOption === 'overdue-only') {
118
- filterHints.push('overdue tasks only');
119
- }
120
- else if (args.startDate === 'today') {
121
- const overdueText = args.overdueOption === 'exclude-overdue' ? '' : ' + overdue tasks';
122
- filterHints.push(`today${overdueText}${args.daysCount > 1 ? ` + ${args.daysCount - 1} more days` : ''}`);
123
- }
124
- else if (args.startDate) {
125
- const dateRange = args.daysCount > 1
126
- ? ` to ${getDateString(addDays(args.startDate, args.daysCount))}`
127
- : '';
128
- filterHints.push(`${args.startDate}${dateRange}`);
129
- }
130
- // Add label filter information
131
- if (args.labels && args.labels.length > 0) {
132
- const labelText = args.labels
133
- .map((label) => `@${label}`)
134
- .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
135
- filterHints.push(`labels: ${labelText}`);
136
- }
137
- // Add responsible user filter information
138
- if (args.responsibleUser) {
139
- const email = assigneeEmail || args.responsibleUser;
140
- filterHints.push(`assigned to: ${email}`);
141
- }
142
- // Generate subject description
143
- let subject = '';
144
- if (args.overdueOption === 'overdue-only') {
145
- subject = 'Overdue tasks';
146
- }
147
- else if (args.startDate === 'today') {
148
- subject =
149
- args.overdueOption === 'exclude-overdue' ? `Today's tasks` : `Today's tasks + overdue`;
150
- }
151
- else if (args.startDate) {
152
- subject = `Tasks for ${args.startDate}`;
153
- }
154
- else {
155
- subject = 'Tasks';
156
- }
157
- // Append responsible user to subject if provided
158
- if (args.responsibleUser) {
159
- const email = assigneeEmail || args.responsibleUser;
160
- subject += ` assigned to ${email}`;
161
- }
162
- // Generate helpful suggestions for empty results
163
- const zeroReasonHints = [];
164
- if (tasks.length === 0) {
165
- if (args.overdueOption === 'overdue-only') {
166
- zeroReasonHints.push('Great job! No overdue tasks');
167
- }
168
- else if (args.startDate === 'today') {
169
- const overdueNote = args.overdueOption === 'exclude-overdue' ? '' : ' or overdue';
170
- zeroReasonHints.push(`Great job! No tasks for today${overdueNote}`);
171
- }
172
- else {
173
- zeroReasonHints.push("Expand date range with larger 'daysCount'");
174
- zeroReasonHints.push("Check today's tasks with startDate='today'");
175
- }
176
- }
177
- // Generate contextual next steps
178
- const now = new Date();
179
- const todayStr = getDateString(now);
180
- const hasOverdue = args.overdueOption === 'overdue-only' ||
181
- args.startDate === 'today' ||
182
- tasks.some((task) => task.dueDate && new Date(task.dueDate) < now);
183
- const nextSteps = generateTaskNextSteps('listed', tasks, {
184
- hasToday: args.startDate === 'today' || tasks.some((task) => task.dueDate === todayStr),
185
- hasOverdue,
186
- });
187
- return summarizeList({
188
- subject,
189
- count: tasks.length,
190
- limit: args.limit,
191
- nextCursor: nextCursor ?? undefined,
192
- filterHints,
193
- previewLines: previewTasks(tasks, Math.min(tasks.length, args.limit)),
194
- zeroReasonHints,
195
- nextSteps,
196
- });
197
- }
198
- export { findTasksByDate };
@@ -1,329 +0,0 @@
1
- import { z } from 'zod';
2
- import { appendToQuery, filterTasksByResponsibleUser, RESPONSIBLE_USER_FILTERING, resolveResponsibleUser, } from '../filter-helpers.js';
3
- import { getToolOutput } from '../mcp-helpers.js';
4
- import { getTasksByFilter, mapTask } from '../tool-helpers.js';
5
- import { ApiLimits } from '../utils/constants.js';
6
- import { generateLabelsFilter, LabelsSchema } from '../utils/labels.js';
7
- import { generateTaskNextSteps, getDateString, previewTasks, summarizeList, } from '../utils/response-builders.js';
8
- import { ToolNames } from '../utils/tool-names.js';
9
- const { FIND_COMPLETED_TASKS, ADD_TASKS } = ToolNames;
10
- const ArgsSchema = {
11
- searchText: z.string().optional().describe('The text to search for in tasks.'),
12
- projectId: z
13
- .string()
14
- .optional()
15
- .describe('Find tasks in this project. Project ID should be an ID string, or the text "inbox", for inbox tasks.'),
16
- sectionId: z.string().optional().describe('Find tasks in this section.'),
17
- parentId: z.string().optional().describe('Find subtasks of this parent task.'),
18
- responsibleUser: z
19
- .string()
20
- .optional()
21
- .describe('Find tasks assigned to this user. Can be a user ID, name, or email address.'),
22
- responsibleUserFiltering: z
23
- .enum(RESPONSIBLE_USER_FILTERING)
24
- .optional()
25
- .describe('How to filter by responsible user when responsibleUser is not provided. "assigned" = only tasks assigned to others; "unassignedOrMe" = only unassigned tasks or tasks assigned to me; "all" = all tasks regardless of assignment. Default value will be `unassignedOrMe`.'),
26
- limit: z
27
- .number()
28
- .int()
29
- .min(1)
30
- .max(ApiLimits.TASKS_MAX)
31
- .default(ApiLimits.TASKS_DEFAULT)
32
- .describe('The maximum number of tasks to return.'),
33
- cursor: z
34
- .string()
35
- .optional()
36
- .describe('The cursor to get the next page of tasks (cursor is obtained from the previous call to this tool, with the same parameters).'),
37
- ...LabelsSchema,
38
- };
39
- const findTasks = {
40
- name: ToolNames.FIND_TASKS,
41
- description: 'Find tasks by text search, or by project/section/parent container/responsible user. At least one filter must be provided.',
42
- parameters: ArgsSchema,
43
- async execute(args, client) {
44
- const { searchText, projectId, sectionId, parentId, responsibleUser, responsibleUserFiltering, limit, cursor, labels, labelsOperator, } = args;
45
- const todoistUser = await client.getUser();
46
- // Validate at least one filter is provided
47
- const hasLabels = labels && labels.length > 0;
48
- if (!searchText &&
49
- !projectId &&
50
- !sectionId &&
51
- !parentId &&
52
- !responsibleUser &&
53
- !hasLabels) {
54
- throw new Error('At least one filter must be provided: searchText, projectId, sectionId, parentId, responsibleUser, or labels');
55
- }
56
- // Resolve assignee name to user ID if provided
57
- const resolved = await resolveResponsibleUser(client, responsibleUser);
58
- const resolvedAssigneeId = resolved?.userId;
59
- const assigneeEmail = resolved?.email;
60
- // If using container-based filtering, use direct API
61
- if (projectId || sectionId || parentId) {
62
- const taskParams = {
63
- limit,
64
- cursor: cursor ?? null,
65
- };
66
- if (projectId) {
67
- taskParams.projectId =
68
- projectId === 'inbox' ? todoistUser.inboxProjectId : projectId;
69
- }
70
- if (sectionId)
71
- taskParams.sectionId = sectionId;
72
- if (parentId)
73
- taskParams.parentId = parentId;
74
- const { results, nextCursor } = await client.getTasks(taskParams);
75
- const mappedTasks = results.map(mapTask);
76
- // Apply search text filter
77
- let filteredTasks = searchText
78
- ? mappedTasks.filter((task) => task.content.toLowerCase().includes(searchText.toLowerCase()) ||
79
- task.description?.toLowerCase().includes(searchText.toLowerCase()))
80
- : mappedTasks;
81
- // Apply responsibleUid filter
82
- filteredTasks = filterTasksByResponsibleUser({
83
- tasks: filteredTasks,
84
- resolvedAssigneeId,
85
- currentUserId: todoistUser.id,
86
- responsibleUserFiltering,
87
- });
88
- // Apply label filter
89
- if (labels && labels.length > 0) {
90
- filteredTasks =
91
- labelsOperator === 'and'
92
- ? filteredTasks.filter((task) => labels.every((label) => task.labels.includes(label)))
93
- : filteredTasks.filter((task) => labels.some((label) => task.labels.includes(label)));
94
- }
95
- const textContent = generateTextContent({
96
- tasks: filteredTasks,
97
- args,
98
- nextCursor,
99
- isContainerSearch: true,
100
- assigneeEmail,
101
- });
102
- return getToolOutput({
103
- textContent,
104
- structuredContent: {
105
- tasks: filteredTasks,
106
- nextCursor,
107
- totalCount: filteredTasks.length,
108
- hasMore: Boolean(nextCursor),
109
- appliedFilters: args,
110
- },
111
- });
112
- }
113
- // If only responsibleUid is provided (without containers), use assignee filter
114
- if (resolvedAssigneeId && !searchText && !hasLabels) {
115
- const tasks = await client.getTasksByFilter({
116
- query: `assigned to: ${assigneeEmail}`,
117
- lang: 'en',
118
- limit,
119
- cursor: cursor ?? null,
120
- });
121
- const mappedTasks = tasks.results.map(mapTask);
122
- const textContent = generateTextContent({
123
- tasks: mappedTasks,
124
- args,
125
- nextCursor: tasks.nextCursor,
126
- isContainerSearch: false,
127
- assigneeEmail,
128
- });
129
- return getToolOutput({
130
- textContent,
131
- structuredContent: {
132
- tasks: mappedTasks,
133
- nextCursor: tasks.nextCursor,
134
- totalCount: mappedTasks.length,
135
- hasMore: Boolean(tasks.nextCursor),
136
- appliedFilters: args,
137
- },
138
- });
139
- }
140
- // Handle search text and/or labels using filter query (responsibleUid filtering done client-side)
141
- let query = '';
142
- // Add search text component
143
- if (searchText) {
144
- query = `search: ${searchText}`;
145
- }
146
- // Add labels component
147
- const labelsFilter = generateLabelsFilter(labels, labelsOperator);
148
- query = appendToQuery(query, labelsFilter);
149
- // Execute filter query
150
- const result = await getTasksByFilter({
151
- client,
152
- query,
153
- cursor: args.cursor,
154
- limit: args.limit,
155
- });
156
- const tasks = filterTasksByResponsibleUser({
157
- tasks: result.tasks,
158
- resolvedAssigneeId,
159
- currentUserId: todoistUser.id,
160
- responsibleUserFiltering,
161
- });
162
- const textContent = generateTextContent({
163
- tasks,
164
- args,
165
- nextCursor: result.nextCursor,
166
- isContainerSearch: false,
167
- assigneeEmail,
168
- });
169
- return getToolOutput({
170
- textContent,
171
- structuredContent: {
172
- tasks,
173
- nextCursor: result.nextCursor,
174
- totalCount: tasks.length,
175
- hasMore: Boolean(result.nextCursor),
176
- appliedFilters: args,
177
- },
178
- });
179
- },
180
- };
181
- function getContainerZeroReasonHints(args) {
182
- if (args.projectId) {
183
- const hints = [
184
- args.searchText ? 'No tasks in project match search' : 'Project has no tasks yet',
185
- ];
186
- if (!args.searchText) {
187
- hints.push(`Use ${ADD_TASKS} to create tasks`);
188
- }
189
- return hints;
190
- }
191
- if (args.sectionId) {
192
- const hints = [args.searchText ? 'No tasks in section match search' : 'Section is empty'];
193
- if (!args.searchText) {
194
- hints.push('Tasks may be in other sections of the project');
195
- }
196
- return hints;
197
- }
198
- if (args.parentId) {
199
- const hints = [args.searchText ? 'No subtasks match search' : 'No subtasks created yet'];
200
- if (!args.searchText) {
201
- hints.push(`Use ${ADD_TASKS} with parentId to add subtasks`);
202
- }
203
- return hints;
204
- }
205
- return [];
206
- }
207
- function generateTextContent({ tasks, args, nextCursor, isContainerSearch, assigneeEmail, }) {
208
- // Generate subject and filter descriptions based on search type
209
- let subject = 'Tasks';
210
- const filterHints = [];
211
- const zeroReasonHints = [];
212
- if (isContainerSearch) {
213
- // Container-based search
214
- if (args.projectId) {
215
- subject = 'Tasks in project';
216
- filterHints.push(`in project ${args.projectId}`);
217
- }
218
- else if (args.sectionId) {
219
- subject = 'Tasks in section';
220
- filterHints.push(`in section ${args.sectionId}`);
221
- }
222
- else if (args.parentId) {
223
- subject = 'Subtasks';
224
- filterHints.push(`subtasks of ${args.parentId}`);
225
- }
226
- else {
227
- subject = 'Tasks'; // fallback, though this shouldn't happen
228
- }
229
- // Add search text filter if present
230
- if (args.searchText) {
231
- subject += ` matching "${args.searchText}"`;
232
- filterHints.push(`containing "${args.searchText}"`);
233
- }
234
- // Add responsibleUid filter if present
235
- if (args.responsibleUser) {
236
- const email = assigneeEmail || args.responsibleUser;
237
- subject += ` assigned to ${email}`;
238
- filterHints.push(`assigned to ${email}`);
239
- }
240
- // Add label filter information
241
- if (args.labels && args.labels.length > 0) {
242
- const labelText = args.labels
243
- .map((label) => `@${label}`)
244
- .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
245
- filterHints.push(`labels: ${labelText}`);
246
- }
247
- // Container-specific zero result hints
248
- if (tasks.length === 0) {
249
- zeroReasonHints.push(...getContainerZeroReasonHints(args));
250
- }
251
- }
252
- else {
253
- // Text, responsibleUid, or labels search
254
- const email = assigneeEmail || args.responsibleUser;
255
- // Build subject based on filters
256
- const subjectParts = [];
257
- if (args.searchText) {
258
- subjectParts.push(`"${args.searchText}"`);
259
- }
260
- if (args.responsibleUser) {
261
- subjectParts.push(`assigned to ${email}`);
262
- }
263
- if (args.labels && args.labels.length > 0) {
264
- const labelText = args.labels
265
- .map((label) => `@${label}`)
266
- .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
267
- subjectParts.push(`with labels: ${labelText}`);
268
- }
269
- if (args.searchText) {
270
- subject = `Search results for ${subjectParts.join(' ')}`;
271
- filterHints.push(`matching "${args.searchText}"`);
272
- }
273
- else if (args.responsibleUser && (!args.labels || args.labels.length === 0)) {
274
- subject = `Tasks assigned to ${email}`;
275
- }
276
- else if (args.labels && args.labels.length > 0 && !args.responsibleUser) {
277
- const labelText = args.labels
278
- .map((label) => `@${label}`)
279
- .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
280
- subject = `Tasks with labels: ${labelText}`;
281
- }
282
- else {
283
- subject = `Tasks ${subjectParts.join(' ')}`;
284
- }
285
- // Add filter hints
286
- if (args.responsibleUser) {
287
- filterHints.push(`assigned to ${email}`);
288
- }
289
- if (args.labels && args.labels.length > 0) {
290
- const labelText = args.labels
291
- .map((label) => `@${label}`)
292
- .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
293
- filterHints.push(`labels: ${labelText}`);
294
- }
295
- if (tasks.length === 0) {
296
- if (args.responsibleUser) {
297
- const email = assigneeEmail || args.responsibleUser;
298
- zeroReasonHints.push(`No tasks assigned to ${email}`);
299
- zeroReasonHints.push('Check if the user name is correct');
300
- zeroReasonHints.push(`Check completed tasks with ${FIND_COMPLETED_TASKS}`);
301
- }
302
- if (args.searchText) {
303
- zeroReasonHints.push('Try broader search terms');
304
- zeroReasonHints.push('Verify spelling and try partial words');
305
- if (!args.responsibleUser) {
306
- zeroReasonHints.push(`Check completed tasks with ${FIND_COMPLETED_TASKS}`);
307
- }
308
- }
309
- }
310
- }
311
- // Generate contextual next steps
312
- const now = new Date();
313
- const todayDateString = getDateString(now);
314
- const nextSteps = generateTaskNextSteps('listed', tasks, {
315
- hasToday: tasks.some((task) => task.dueDate === todayDateString),
316
- hasOverdue: tasks.some((task) => task.dueDate && new Date(task.dueDate) < now),
317
- });
318
- return summarizeList({
319
- subject,
320
- count: tasks.length,
321
- limit: args.limit,
322
- nextCursor: nextCursor ?? undefined,
323
- filterHints,
324
- previewLines: previewTasks(tasks, Math.min(tasks.length, args.limit)),
325
- zeroReasonHints,
326
- nextSteps,
327
- });
328
- }
329
- export { findTasks };