@doist/todoist-ai 4.15.1 → 4.16.1
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.
- package/dist/filter-helpers.d.ts +1 -1
- package/dist/index.d.ts +175 -175
- package/dist/index.js +61 -81
- package/dist/main.js +15 -23
- package/dist/mcp-helpers.d.ts +4 -4
- package/dist/mcp-server-6tm7Rhyz.js +2840 -0
- package/dist/todoist-tool.d.ts +2 -2
- package/dist/tool-helpers.d.ts +1 -1
- package/dist/tools/add-comments.d.ts +1 -1
- package/dist/tools/add-comments.d.ts.map +1 -1
- package/dist/tools/add-projects.d.ts +4 -4
- package/dist/tools/add-projects.d.ts.map +1 -1
- package/dist/tools/add-sections.d.ts +1 -1
- package/dist/tools/add-sections.d.ts.map +1 -1
- package/dist/tools/add-tasks.d.ts +4 -4
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/complete-tasks.d.ts +1 -1
- package/dist/tools/complete-tasks.d.ts.map +1 -1
- package/dist/tools/delete-object.d.ts +3 -3
- package/dist/tools/delete-object.d.ts.map +1 -1
- package/dist/tools/fetch.d.ts +1 -1
- package/dist/tools/find-activity.d.ts +5 -5
- package/dist/tools/find-activity.d.ts.map +1 -1
- package/dist/tools/find-comments.d.ts +2 -2
- package/dist/tools/find-comments.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.d.ts +3 -3
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-project-collaborators.d.ts +2 -2
- package/dist/tools/find-projects.d.ts +1 -1
- package/dist/tools/find-projects.d.ts.map +1 -1
- package/dist/tools/find-sections.d.ts +1 -1
- package/dist/tools/find-sections.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.d.ts +1 -1
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks.d.ts +3 -3
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/get-overview.d.ts +1 -1
- package/dist/tools/manage-assignments.d.ts +1 -1
- package/dist/tools/search.d.ts +1 -1
- package/dist/tools/update-comments.d.ts +4 -4
- package/dist/tools/update-comments.d.ts.map +1 -1
- package/dist/tools/update-projects.d.ts +1 -1
- package/dist/tools/update-projects.d.ts.map +1 -1
- package/dist/tools/update-sections.d.ts +4 -4
- package/dist/tools/update-sections.d.ts.map +1 -1
- package/dist/tools/update-tasks.d.ts +7 -7
- package/dist/tools/update-tasks.d.ts.map +1 -1
- package/dist/tools/user-info.d.ts +1 -1
- package/dist/utils/assignment-validator.d.ts +2 -2
- package/dist/utils/response-builders.d.ts +1 -3
- package/dist/utils/response-builders.d.ts.map +1 -1
- package/dist/utils/test-helpers.d.ts +1 -1
- package/dist/utils/user-resolver.d.ts +1 -1
- package/package.json +11 -9
- package/dist/filter-helpers.js +0 -79
- package/dist/mcp-helpers.js +0 -71
- package/dist/mcp-server.js +0 -142
- package/dist/todoist-tool.js +0 -1
- package/dist/tool-helpers.js +0 -125
- package/dist/tool-helpers.test.d.ts +0 -2
- package/dist/tool-helpers.test.d.ts.map +0 -1
- package/dist/tool-helpers.test.js +0 -223
- package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-comments.test.js +0 -241
- package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-projects.test.js +0 -174
- package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-sections.test.js +0 -185
- package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-tasks.test.js +0 -533
- package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
- package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
- package/dist/tools/__tests__/assignment-integration.test.js +0 -428
- package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/complete-tasks.test.js +0 -206
- package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
- package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
- package/dist/tools/__tests__/delete-object.test.js +0 -110
- package/dist/tools/__tests__/fetch.test.d.ts +0 -2
- package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
- package/dist/tools/__tests__/fetch.test.js +0 -279
- package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
- package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-activity.test.js +0 -229
- package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-comments.test.js +0 -236
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-completed-tasks.test.js +0 -324
- package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-projects.test.js +0 -154
- package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-sections.test.js +0 -245
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
- package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks.test.js +0 -771
- package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
- package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
- package/dist/tools/__tests__/get-overview.test.js +0 -225
- package/dist/tools/__tests__/search.test.d.ts +0 -2
- package/dist/tools/__tests__/search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/search.test.js +0 -206
- package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-comments.test.js +0 -294
- package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-projects.test.js +0 -217
- package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-sections.test.js +0 -169
- package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-tasks.test.js +0 -788
- package/dist/tools/__tests__/user-info.test.d.ts +0 -2
- package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
- package/dist/tools/__tests__/user-info.test.js +0 -139
- package/dist/tools/add-comments.js +0 -79
- package/dist/tools/add-projects.js +0 -63
- package/dist/tools/add-sections.js +0 -61
- package/dist/tools/add-tasks.js +0 -160
- package/dist/tools/complete-tasks.js +0 -68
- package/dist/tools/delete-object.js +0 -79
- package/dist/tools/fetch.js +0 -102
- package/dist/tools/find-activity.js +0 -221
- package/dist/tools/find-comments.js +0 -143
- package/dist/tools/find-completed-tasks.js +0 -161
- package/dist/tools/find-project-collaborators.js +0 -151
- package/dist/tools/find-projects.js +0 -101
- package/dist/tools/find-sections.js +0 -96
- package/dist/tools/find-tasks-by-date.js +0 -198
- package/dist/tools/find-tasks.js +0 -329
- package/dist/tools/get-overview.js +0 -249
- package/dist/tools/manage-assignments.js +0 -337
- package/dist/tools/search.js +0 -65
- package/dist/tools/update-comments.js +0 -82
- package/dist/tools/update-projects.js +0 -84
- package/dist/tools/update-sections.js +0 -70
- package/dist/tools/update-tasks.js +0 -170
- package/dist/tools/user-info.js +0 -142
- package/dist/utils/assignment-validator.js +0 -253
- package/dist/utils/constants.js +0 -45
- package/dist/utils/duration-parser.js +0 -96
- package/dist/utils/duration-parser.test.d.ts +0 -2
- package/dist/utils/duration-parser.test.d.ts.map +0 -1
- package/dist/utils/duration-parser.test.js +0 -147
- package/dist/utils/labels.js +0 -18
- package/dist/utils/priorities.js +0 -20
- package/dist/utils/response-builders.js +0 -210
- package/dist/utils/sanitize-data.js +0 -37
- package/dist/utils/sanitize-data.test.d.ts +0 -2
- package/dist/utils/sanitize-data.test.d.ts.map +0 -1
- package/dist/utils/sanitize-data.test.js +0 -93
- package/dist/utils/test-helpers.js +0 -237
- package/dist/utils/tool-names.js +0 -40
- package/dist/utils/user-resolver.js +0 -179
package/dist/tools/find-tasks.js
DELETED
|
@@ -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 };
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getToolOutput } from '../mcp-helpers.js';
|
|
3
|
-
import { isPersonalProject, mapTask } from '../tool-helpers.js';
|
|
4
|
-
import { ApiLimits } from '../utils/constants.js';
|
|
5
|
-
import { ToolNames } from '../utils/tool-names.js';
|
|
6
|
-
const ArgsSchema = {
|
|
7
|
-
projectId: z
|
|
8
|
-
.string()
|
|
9
|
-
.min(1)
|
|
10
|
-
.optional()
|
|
11
|
-
.describe('Optional project ID. If provided, shows detailed overview of that project. If omitted, shows overview of all projects.'),
|
|
12
|
-
};
|
|
13
|
-
function buildProjectTree(projects) {
|
|
14
|
-
// Sort projects by childOrder, then build a tree
|
|
15
|
-
const byId = {};
|
|
16
|
-
for (const p of projects) {
|
|
17
|
-
byId[p.id] = {
|
|
18
|
-
...p,
|
|
19
|
-
children: [],
|
|
20
|
-
childOrder: p.childOrder ?? 0,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
const roots = [];
|
|
24
|
-
for (const p of projects) {
|
|
25
|
-
const current = byId[p.id];
|
|
26
|
-
if (!current)
|
|
27
|
-
continue;
|
|
28
|
-
if (isPersonalProject(p) && p.parentId) {
|
|
29
|
-
const parent = byId[p.parentId];
|
|
30
|
-
if (parent) {
|
|
31
|
-
parent.children.push(current);
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
roots.push(current);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
roots.push(current);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function sortTree(nodes) {
|
|
42
|
-
nodes.sort((a, b) => a.childOrder - b.childOrder);
|
|
43
|
-
for (const n of nodes) {
|
|
44
|
-
sortTree(n.children);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
sortTree(roots);
|
|
48
|
-
return roots;
|
|
49
|
-
}
|
|
50
|
-
async function getSectionsByProject(client, projectIds) {
|
|
51
|
-
const result = {};
|
|
52
|
-
await Promise.all(projectIds.map(async (projectId) => {
|
|
53
|
-
const { results } = await client.getSections({ projectId });
|
|
54
|
-
result[projectId] = results;
|
|
55
|
-
}));
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
function renderProjectMarkdown(project, sectionsByProject, indent = '') {
|
|
59
|
-
const lines = [];
|
|
60
|
-
lines.push(`${indent}- Project: ${project.name} (id=${project.id})`);
|
|
61
|
-
const sections = sectionsByProject[project.id] || [];
|
|
62
|
-
for (const section of sections) {
|
|
63
|
-
lines.push(`${indent} - Section: ${section.name} (id=${section.id})`);
|
|
64
|
-
}
|
|
65
|
-
for (const child of project.children) {
|
|
66
|
-
lines.push(...renderProjectMarkdown(child, sectionsByProject, `${indent} `));
|
|
67
|
-
}
|
|
68
|
-
return lines;
|
|
69
|
-
}
|
|
70
|
-
function buildTaskTree(tasks) {
|
|
71
|
-
const byId = {};
|
|
72
|
-
for (const task of tasks) {
|
|
73
|
-
byId[task.id] = { ...task, children: [] };
|
|
74
|
-
}
|
|
75
|
-
const roots = [];
|
|
76
|
-
for (const task of tasks) {
|
|
77
|
-
const node = byId[task.id];
|
|
78
|
-
if (!node)
|
|
79
|
-
continue;
|
|
80
|
-
if (!task.parentId) {
|
|
81
|
-
roots.push(node);
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const parent = byId[task.parentId];
|
|
85
|
-
if (parent) {
|
|
86
|
-
parent.children.push(node);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
roots.push(node);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return roots;
|
|
93
|
-
}
|
|
94
|
-
function renderTaskTreeMarkdown(tasks, indent = '') {
|
|
95
|
-
const lines = [];
|
|
96
|
-
for (const task of tasks) {
|
|
97
|
-
const idPart = `id=${task.id}`;
|
|
98
|
-
const duePart = task.dueDate ? `; due=${task.dueDate}` : '';
|
|
99
|
-
const contentPart = `; content=${task.content}`;
|
|
100
|
-
lines.push(`${indent}- ${idPart}${duePart}${contentPart}`);
|
|
101
|
-
if (task.children.length > 0) {
|
|
102
|
-
lines.push(...renderTaskTreeMarkdown(task.children, `${indent} `));
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return lines;
|
|
106
|
-
}
|
|
107
|
-
function buildProjectStructure(project, sectionsByProject) {
|
|
108
|
-
return {
|
|
109
|
-
id: project.id,
|
|
110
|
-
name: project.name,
|
|
111
|
-
parentId: isPersonalProject(project) ? (project.parentId ?? null) : null,
|
|
112
|
-
sections: sectionsByProject[project.id] || [],
|
|
113
|
-
children: project.children.map((child) => buildProjectStructure(child, sectionsByProject)),
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
async function getAllTasksForProject(client, projectId) {
|
|
117
|
-
let allTasks = [];
|
|
118
|
-
let cursor;
|
|
119
|
-
do {
|
|
120
|
-
const { results, nextCursor } = await client.getTasks({
|
|
121
|
-
projectId,
|
|
122
|
-
limit: ApiLimits.TASKS_BATCH_SIZE,
|
|
123
|
-
cursor: cursor ?? undefined,
|
|
124
|
-
});
|
|
125
|
-
allTasks = allTasks.concat(results.map(mapTask));
|
|
126
|
-
cursor = nextCursor ?? undefined;
|
|
127
|
-
} while (cursor);
|
|
128
|
-
return allTasks;
|
|
129
|
-
}
|
|
130
|
-
async function getProjectSections(client, projectId) {
|
|
131
|
-
const { results } = await client.getSections({ projectId });
|
|
132
|
-
return results;
|
|
133
|
-
}
|
|
134
|
-
async function generateAccountOverview(client) {
|
|
135
|
-
const { results: projects } = await client.getProjects({});
|
|
136
|
-
const inbox = projects.find((p) => isPersonalProject(p) && p.inboxProject === true);
|
|
137
|
-
const nonInbox = projects.filter((p) => !isPersonalProject(p) || p.inboxProject !== true);
|
|
138
|
-
const tree = buildProjectTree(nonInbox);
|
|
139
|
-
const allProjectIds = projects.map((p) => p.id);
|
|
140
|
-
const sectionsByProject = await getSectionsByProject(client, allProjectIds);
|
|
141
|
-
// Generate markdown text content
|
|
142
|
-
const lines = ['# Personal Projects', ''];
|
|
143
|
-
if (inbox) {
|
|
144
|
-
lines.push(`- Inbox Project: ${inbox.name} (id=${inbox.id})`);
|
|
145
|
-
for (const section of sectionsByProject[inbox.id] || []) {
|
|
146
|
-
lines.push(` - Section: ${section.name} (id=${section.id})`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (tree.length) {
|
|
150
|
-
for (const project of tree) {
|
|
151
|
-
lines.push(...renderProjectMarkdown(project, sectionsByProject));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
lines.push('_No projects found._');
|
|
156
|
-
}
|
|
157
|
-
lines.push('');
|
|
158
|
-
// Add explanation about nesting if there are nested projects
|
|
159
|
-
const hasNested = tree.some((p) => p.children.length > 0);
|
|
160
|
-
if (hasNested) {
|
|
161
|
-
lines.push('_Note: Indentation indicates that a project is a sub-project of the one above it. This allows for organizing projects hierarchically, with parent projects containing related sub-projects._', '');
|
|
162
|
-
}
|
|
163
|
-
const textContent = lines.join('\n');
|
|
164
|
-
// Generate structured content
|
|
165
|
-
const structuredContent = {
|
|
166
|
-
type: 'account_overview',
|
|
167
|
-
inbox: inbox
|
|
168
|
-
? {
|
|
169
|
-
id: inbox.id,
|
|
170
|
-
name: inbox.name,
|
|
171
|
-
sections: sectionsByProject[inbox.id] || [],
|
|
172
|
-
}
|
|
173
|
-
: null,
|
|
174
|
-
projects: tree.map((project) => buildProjectStructure(project, sectionsByProject)),
|
|
175
|
-
totalProjects: projects.length,
|
|
176
|
-
totalSections: allProjectIds.reduce((total, id) => total + (sectionsByProject[id]?.length || 0), 0),
|
|
177
|
-
hasNestedProjects: hasNested,
|
|
178
|
-
};
|
|
179
|
-
return { textContent, structuredContent };
|
|
180
|
-
}
|
|
181
|
-
async function generateProjectOverview(client, projectId) {
|
|
182
|
-
const project = await client.getProject(projectId);
|
|
183
|
-
const sections = await getProjectSections(client, projectId);
|
|
184
|
-
const allTasks = await getAllTasksForProject(client, projectId);
|
|
185
|
-
// Group tasks by sectionId
|
|
186
|
-
const tasksBySection = {};
|
|
187
|
-
for (const section of sections) {
|
|
188
|
-
tasksBySection[section.id] = [];
|
|
189
|
-
}
|
|
190
|
-
const tasksWithoutSection = [];
|
|
191
|
-
for (const task of allTasks) {
|
|
192
|
-
const sectionTasks = task.sectionId
|
|
193
|
-
? (tasksBySection[task.sectionId] ?? tasksWithoutSection)
|
|
194
|
-
: tasksWithoutSection;
|
|
195
|
-
sectionTasks.push(task);
|
|
196
|
-
}
|
|
197
|
-
// Generate markdown text content
|
|
198
|
-
const lines = [`# ${project.name}`];
|
|
199
|
-
if (tasksWithoutSection.length > 0) {
|
|
200
|
-
lines.push('');
|
|
201
|
-
const tree = buildTaskTree(tasksWithoutSection);
|
|
202
|
-
lines.push(...renderTaskTreeMarkdown(tree));
|
|
203
|
-
}
|
|
204
|
-
for (const section of sections) {
|
|
205
|
-
lines.push('');
|
|
206
|
-
lines.push(`## ${section.name}`);
|
|
207
|
-
const sectionTasks = tasksBySection[section.id];
|
|
208
|
-
if (!sectionTasks?.length) {
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
const tree = buildTaskTree(sectionTasks);
|
|
212
|
-
lines.push(...renderTaskTreeMarkdown(tree));
|
|
213
|
-
}
|
|
214
|
-
const textContent = lines.join('\n');
|
|
215
|
-
// Generate structured content
|
|
216
|
-
const structuredContent = {
|
|
217
|
-
type: 'project_overview',
|
|
218
|
-
project: {
|
|
219
|
-
id: project.id,
|
|
220
|
-
name: project.name,
|
|
221
|
-
},
|
|
222
|
-
sections: sections,
|
|
223
|
-
tasks: allTasks.map((task) => ({
|
|
224
|
-
...task,
|
|
225
|
-
children: [], // Tasks already include hierarchical info via parentId
|
|
226
|
-
})),
|
|
227
|
-
stats: {
|
|
228
|
-
totalTasks: allTasks.length,
|
|
229
|
-
totalSections: sections.length,
|
|
230
|
-
tasksWithoutSection: tasksWithoutSection.length,
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
return { textContent, structuredContent };
|
|
234
|
-
}
|
|
235
|
-
const getOverview = {
|
|
236
|
-
name: ToolNames.GET_OVERVIEW,
|
|
237
|
-
description: 'Get a Markdown overview. If no projectId is provided, shows all projects with hierarchy and sections (useful for navigation). If projectId is provided, shows detailed overview of that specific project including all tasks grouped by sections.',
|
|
238
|
-
parameters: ArgsSchema,
|
|
239
|
-
async execute(args, client) {
|
|
240
|
-
const result = args.projectId
|
|
241
|
-
? await generateProjectOverview(client, args.projectId)
|
|
242
|
-
: await generateAccountOverview(client);
|
|
243
|
-
return getToolOutput({
|
|
244
|
-
textContent: result.textContent,
|
|
245
|
-
structuredContent: result.structuredContent,
|
|
246
|
-
});
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
export { getOverview };
|