@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.
Files changed (167) hide show
  1. package/dist/filter-helpers.d.ts +1 -1
  2. package/dist/index.d.ts +175 -175
  3. package/dist/index.js +61 -81
  4. package/dist/main.js +15 -23
  5. package/dist/mcp-helpers.d.ts +4 -4
  6. package/dist/mcp-server-6tm7Rhyz.js +2840 -0
  7. package/dist/todoist-tool.d.ts +2 -2
  8. package/dist/tool-helpers.d.ts +1 -1
  9. package/dist/tools/add-comments.d.ts +1 -1
  10. package/dist/tools/add-comments.d.ts.map +1 -1
  11. package/dist/tools/add-projects.d.ts +4 -4
  12. package/dist/tools/add-projects.d.ts.map +1 -1
  13. package/dist/tools/add-sections.d.ts +1 -1
  14. package/dist/tools/add-sections.d.ts.map +1 -1
  15. package/dist/tools/add-tasks.d.ts +4 -4
  16. package/dist/tools/add-tasks.d.ts.map +1 -1
  17. package/dist/tools/complete-tasks.d.ts +1 -1
  18. package/dist/tools/complete-tasks.d.ts.map +1 -1
  19. package/dist/tools/delete-object.d.ts +3 -3
  20. package/dist/tools/delete-object.d.ts.map +1 -1
  21. package/dist/tools/fetch.d.ts +1 -1
  22. package/dist/tools/find-activity.d.ts +5 -5
  23. package/dist/tools/find-activity.d.ts.map +1 -1
  24. package/dist/tools/find-comments.d.ts +2 -2
  25. package/dist/tools/find-comments.d.ts.map +1 -1
  26. package/dist/tools/find-completed-tasks.d.ts +3 -3
  27. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  28. package/dist/tools/find-project-collaborators.d.ts +2 -2
  29. package/dist/tools/find-projects.d.ts +1 -1
  30. package/dist/tools/find-projects.d.ts.map +1 -1
  31. package/dist/tools/find-sections.d.ts +1 -1
  32. package/dist/tools/find-sections.d.ts.map +1 -1
  33. package/dist/tools/find-tasks-by-date.d.ts +1 -1
  34. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  35. package/dist/tools/find-tasks.d.ts +3 -3
  36. package/dist/tools/find-tasks.d.ts.map +1 -1
  37. package/dist/tools/get-overview.d.ts +1 -1
  38. package/dist/tools/manage-assignments.d.ts +1 -1
  39. package/dist/tools/search.d.ts +1 -1
  40. package/dist/tools/update-comments.d.ts +4 -4
  41. package/dist/tools/update-comments.d.ts.map +1 -1
  42. package/dist/tools/update-projects.d.ts +1 -1
  43. package/dist/tools/update-projects.d.ts.map +1 -1
  44. package/dist/tools/update-sections.d.ts +4 -4
  45. package/dist/tools/update-sections.d.ts.map +1 -1
  46. package/dist/tools/update-tasks.d.ts +7 -7
  47. package/dist/tools/update-tasks.d.ts.map +1 -1
  48. package/dist/tools/user-info.d.ts +1 -1
  49. package/dist/utils/assignment-validator.d.ts +2 -2
  50. package/dist/utils/response-builders.d.ts +1 -3
  51. package/dist/utils/response-builders.d.ts.map +1 -1
  52. package/dist/utils/test-helpers.d.ts +1 -1
  53. package/dist/utils/user-resolver.d.ts +1 -1
  54. package/package.json +11 -9
  55. package/dist/filter-helpers.js +0 -79
  56. package/dist/mcp-helpers.js +0 -71
  57. package/dist/mcp-server.js +0 -142
  58. package/dist/todoist-tool.js +0 -1
  59. package/dist/tool-helpers.js +0 -125
  60. package/dist/tool-helpers.test.d.ts +0 -2
  61. package/dist/tool-helpers.test.d.ts.map +0 -1
  62. package/dist/tool-helpers.test.js +0 -223
  63. package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
  64. package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
  65. package/dist/tools/__tests__/add-comments.test.js +0 -241
  66. package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
  67. package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
  68. package/dist/tools/__tests__/add-projects.test.js +0 -174
  69. package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
  70. package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
  71. package/dist/tools/__tests__/add-sections.test.js +0 -185
  72. package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
  73. package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
  74. package/dist/tools/__tests__/add-tasks.test.js +0 -533
  75. package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
  76. package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
  77. package/dist/tools/__tests__/assignment-integration.test.js +0 -428
  78. package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
  79. package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
  80. package/dist/tools/__tests__/complete-tasks.test.js +0 -206
  81. package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
  82. package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
  83. package/dist/tools/__tests__/delete-object.test.js +0 -110
  84. package/dist/tools/__tests__/fetch.test.d.ts +0 -2
  85. package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
  86. package/dist/tools/__tests__/fetch.test.js +0 -279
  87. package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
  88. package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
  89. package/dist/tools/__tests__/find-activity.test.js +0 -229
  90. package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
  91. package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
  92. package/dist/tools/__tests__/find-comments.test.js +0 -236
  93. package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
  94. package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
  95. package/dist/tools/__tests__/find-completed-tasks.test.js +0 -324
  96. package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
  97. package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
  98. package/dist/tools/__tests__/find-projects.test.js +0 -154
  99. package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
  100. package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
  101. package/dist/tools/__tests__/find-sections.test.js +0 -245
  102. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
  103. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
  104. package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
  105. package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
  106. package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
  107. package/dist/tools/__tests__/find-tasks.test.js +0 -771
  108. package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
  109. package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
  110. package/dist/tools/__tests__/get-overview.test.js +0 -225
  111. package/dist/tools/__tests__/search.test.d.ts +0 -2
  112. package/dist/tools/__tests__/search.test.d.ts.map +0 -1
  113. package/dist/tools/__tests__/search.test.js +0 -206
  114. package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
  115. package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
  116. package/dist/tools/__tests__/update-comments.test.js +0 -294
  117. package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
  118. package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
  119. package/dist/tools/__tests__/update-projects.test.js +0 -217
  120. package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
  121. package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
  122. package/dist/tools/__tests__/update-sections.test.js +0 -169
  123. package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
  124. package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
  125. package/dist/tools/__tests__/update-tasks.test.js +0 -788
  126. package/dist/tools/__tests__/user-info.test.d.ts +0 -2
  127. package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
  128. package/dist/tools/__tests__/user-info.test.js +0 -139
  129. package/dist/tools/add-comments.js +0 -79
  130. package/dist/tools/add-projects.js +0 -63
  131. package/dist/tools/add-sections.js +0 -61
  132. package/dist/tools/add-tasks.js +0 -160
  133. package/dist/tools/complete-tasks.js +0 -68
  134. package/dist/tools/delete-object.js +0 -79
  135. package/dist/tools/fetch.js +0 -102
  136. package/dist/tools/find-activity.js +0 -221
  137. package/dist/tools/find-comments.js +0 -143
  138. package/dist/tools/find-completed-tasks.js +0 -161
  139. package/dist/tools/find-project-collaborators.js +0 -151
  140. package/dist/tools/find-projects.js +0 -101
  141. package/dist/tools/find-sections.js +0 -96
  142. package/dist/tools/find-tasks-by-date.js +0 -198
  143. package/dist/tools/find-tasks.js +0 -329
  144. package/dist/tools/get-overview.js +0 -249
  145. package/dist/tools/manage-assignments.js +0 -337
  146. package/dist/tools/search.js +0 -65
  147. package/dist/tools/update-comments.js +0 -82
  148. package/dist/tools/update-projects.js +0 -84
  149. package/dist/tools/update-sections.js +0 -70
  150. package/dist/tools/update-tasks.js +0 -170
  151. package/dist/tools/user-info.js +0 -142
  152. package/dist/utils/assignment-validator.js +0 -253
  153. package/dist/utils/constants.js +0 -45
  154. package/dist/utils/duration-parser.js +0 -96
  155. package/dist/utils/duration-parser.test.d.ts +0 -2
  156. package/dist/utils/duration-parser.test.d.ts.map +0 -1
  157. package/dist/utils/duration-parser.test.js +0 -147
  158. package/dist/utils/labels.js +0 -18
  159. package/dist/utils/priorities.js +0 -20
  160. package/dist/utils/response-builders.js +0 -210
  161. package/dist/utils/sanitize-data.js +0 -37
  162. package/dist/utils/sanitize-data.test.d.ts +0 -2
  163. package/dist/utils/sanitize-data.test.d.ts.map +0 -1
  164. package/dist/utils/sanitize-data.test.js +0 -93
  165. package/dist/utils/test-helpers.js +0 -237
  166. package/dist/utils/tool-names.js +0 -40
  167. package/dist/utils/user-resolver.js +0 -179
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=user-info.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-info.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/user-info.test.ts"],"names":[],"mappings":""}
@@ -1,139 +0,0 @@
1
- import { jest } from '@jest/globals';
2
- import { extractStructuredContent, extractTextContent, TEST_ERRORS, } from '../../utils/test-helpers.js';
3
- import { ToolNames } from '../../utils/tool-names.js';
4
- import { userInfo } from '../user-info.js';
5
- // Mock the Todoist API
6
- const mockTodoistApi = {
7
- getUser: jest.fn(),
8
- };
9
- const { USER_INFO } = ToolNames;
10
- // Helper function to create a mock user with default values that can be overridden
11
- function createMockUser(overrides = {}) {
12
- return {
13
- id: '123',
14
- fullName: 'Test User',
15
- email: 'test@example.com',
16
- isPremium: true,
17
- completedToday: 12,
18
- dailyGoal: 10,
19
- weeklyGoal: 100,
20
- startDay: 1, // Monday
21
- tzInfo: {
22
- timezone: 'Europe/Madrid',
23
- gmtString: '+02:00',
24
- hours: 2,
25
- minutes: 0,
26
- isDst: 1,
27
- },
28
- lang: 'en',
29
- avatarBig: 'https://example.com/avatar.jpg',
30
- avatarMedium: null,
31
- avatarS640: null,
32
- avatarSmall: null,
33
- karma: 86394.0,
34
- karmaTrend: 'up',
35
- nextWeek: 1,
36
- weekendStartDay: 6,
37
- timeFormat: 0,
38
- dateFormat: 0,
39
- daysOff: [6, 7],
40
- businessAccountId: null,
41
- completedCount: 102920,
42
- inboxProjectId: '6PVw8cMf7m8fWwRp',
43
- startPage: 'overdue',
44
- ...overrides,
45
- };
46
- }
47
- describe(`${USER_INFO} tool`, () => {
48
- beforeEach(() => {
49
- jest.clearAllMocks();
50
- });
51
- it('should generate user info with all required fields', async () => {
52
- const mockUser = createMockUser();
53
- mockTodoistApi.getUser.mockResolvedValue(mockUser);
54
- const result = await userInfo.execute({}, mockTodoistApi);
55
- expect(mockTodoistApi.getUser).toHaveBeenCalledWith();
56
- // Test text content contains expected information
57
- const textContent = extractTextContent(result);
58
- expect(textContent).toContain('User ID:** 123');
59
- expect(textContent).toContain('Test User');
60
- expect(textContent).toContain('test@example.com');
61
- expect(textContent).toContain('Europe/Madrid');
62
- expect(textContent).toContain('Monday (1)');
63
- expect(textContent).toContain('Completed Today:** 12');
64
- expect(textContent).toContain('Plan:** Todoist Pro');
65
- // Test structured content
66
- const structuredContent = extractStructuredContent(result);
67
- expect(structuredContent).toEqual(expect.objectContaining({
68
- type: 'user_info',
69
- userId: '123',
70
- fullName: 'Test User',
71
- email: 'test@example.com',
72
- timezone: 'Europe/Madrid',
73
- startDay: 1,
74
- startDayName: 'Monday',
75
- completedToday: 12,
76
- dailyGoal: 10,
77
- weeklyGoal: 100,
78
- plan: 'Todoist Pro',
79
- currentLocalTime: expect.any(String),
80
- weekStartDate: expect.any(String),
81
- weekEndDate: expect.any(String),
82
- currentWeekNumber: expect.any(Number),
83
- }));
84
- // Verify date formats
85
- expect(structuredContent.weekStartDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
86
- expect(structuredContent.weekEndDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
87
- expect(structuredContent.currentLocalTime).toMatch(/^\d{2}\/\d{2}\/\d{4}, \d{2}:\d{2}:\d{2}$/);
88
- });
89
- it('should handle missing timezone info', async () => {
90
- const mockUser = createMockUser({
91
- isPremium: false,
92
- tzInfo: {
93
- timezone: 'UTC',
94
- gmtString: '+00:00',
95
- hours: 0,
96
- minutes: 0,
97
- isDst: 0,
98
- },
99
- });
100
- mockTodoistApi.getUser.mockResolvedValue(mockUser);
101
- const result = await userInfo.execute({}, mockTodoistApi);
102
- const textContent = extractTextContent(result);
103
- expect(textContent).toContain('UTC'); // Should default to UTC
104
- expect(textContent).toContain('Monday (1)'); // Should default to Monday
105
- expect(textContent).toContain('Plan:** Todoist Free');
106
- const structuredContent = extractStructuredContent(result);
107
- expect(structuredContent.timezone).toBe('UTC');
108
- expect(structuredContent.startDay).toBe(1);
109
- expect(structuredContent.startDayName).toBe('Monday');
110
- expect(structuredContent.plan).toBe('Todoist Free');
111
- });
112
- it('should handle invalid timezone and fallback to UTC', async () => {
113
- const mockUser = createMockUser({
114
- startDay: 2, // Tuesday
115
- tzInfo: {
116
- timezone: 'Invalid/Timezone',
117
- gmtString: '+05:30',
118
- hours: 5,
119
- minutes: 30,
120
- isDst: 0,
121
- },
122
- });
123
- mockTodoistApi.getUser.mockResolvedValue(mockUser);
124
- const result = await userInfo.execute({}, mockTodoistApi);
125
- const textContent = extractTextContent(result);
126
- expect(textContent).toContain('UTC'); // Should fallback to UTC
127
- expect(textContent).toContain('Tuesday (2)');
128
- const structuredContent = extractStructuredContent(result);
129
- expect(structuredContent.timezone).toBe('UTC'); // Should be UTC, not the invalid timezone
130
- expect(structuredContent.startDay).toBe(2);
131
- expect(structuredContent.startDayName).toBe('Tuesday');
132
- expect(structuredContent.currentLocalTime).toMatch(/^\d{2}\/\d{2}\/\d{4}, \d{2}:\d{2}:\d{2}$/);
133
- });
134
- it('should propagate API errors', async () => {
135
- const apiError = new Error(TEST_ERRORS.API_UNAUTHORIZED);
136
- mockTodoistApi.getUser.mockRejectedValue(apiError);
137
- await expect(userInfo.execute({}, mockTodoistApi)).rejects.toThrow(TEST_ERRORS.API_UNAUTHORIZED);
138
- });
139
- });
@@ -1,79 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { formatNextSteps } from '../utils/response-builders.js';
4
- import { ToolNames } from '../utils/tool-names.js';
5
- const { FIND_COMMENTS, UPDATE_COMMENTS, DELETE_OBJECT } = ToolNames;
6
- const CommentSchema = z.object({
7
- taskId: z.string().optional().describe('The ID of the task to comment on.'),
8
- projectId: z.string().optional().describe('The ID of the project to comment on.'),
9
- content: z.string().min(1).describe('The content of the comment.'),
10
- });
11
- const ArgsSchema = {
12
- comments: z.array(CommentSchema).min(1).describe('The array of comments to add.'),
13
- };
14
- const addComments = {
15
- name: ToolNames.ADD_COMMENTS,
16
- description: 'Add multiple comments to tasks or projects. Each comment must specify either taskId or projectId.',
17
- parameters: ArgsSchema,
18
- async execute(args, client) {
19
- const { comments } = args;
20
- // Validate each comment
21
- for (const [index, comment] of comments.entries()) {
22
- if (!comment.taskId && !comment.projectId) {
23
- throw new Error(`Comment ${index + 1}: Either taskId or projectId must be provided.`);
24
- }
25
- if (comment.taskId && comment.projectId) {
26
- throw new Error(`Comment ${index + 1}: Cannot provide both taskId and projectId. Choose one.`);
27
- }
28
- }
29
- const addCommentPromises = comments.map(async ({ content, taskId, projectId }) => await client.addComment({
30
- content,
31
- ...(taskId ? { taskId } : { projectId }),
32
- }));
33
- const newComments = await Promise.all(addCommentPromises);
34
- const textContent = generateTextContent({ comments: newComments });
35
- return getToolOutput({
36
- textContent,
37
- structuredContent: {
38
- comments: newComments,
39
- totalCount: newComments.length,
40
- addedCommentIds: newComments.map((comment) => comment.id),
41
- },
42
- });
43
- },
44
- };
45
- function generateTextContent({ comments }) {
46
- // Group comments by entity type and count
47
- const taskComments = comments.filter((c) => c.taskId).length;
48
- const projectComments = comments.filter((c) => c.projectId).length;
49
- // Generate summary text
50
- const parts = [];
51
- if (taskComments > 0) {
52
- const commentsLabel = taskComments > 1 ? 'comments' : 'comment';
53
- parts.push(`${taskComments} task ${commentsLabel}`);
54
- }
55
- if (projectComments > 0) {
56
- const commentsLabel = projectComments > 1 ? 'comments' : 'comment';
57
- parts.push(`${projectComments} project ${commentsLabel}`);
58
- }
59
- const summary = parts.length > 0 ? `Added ${parts.join(' and ')}` : 'No comments added';
60
- // Context-aware next steps
61
- const nextSteps = [];
62
- if (comments.length > 0) {
63
- if (comments.length === 1 && comments[0]) {
64
- const comment = comments[0];
65
- const targetId = comment.taskId || comment.projectId || '';
66
- const targetType = comment.taskId ? 'task' : 'project';
67
- nextSteps.push(`Use ${FIND_COMMENTS} with ${targetType}Id=${targetId} to see all comments`);
68
- nextSteps.push(`Use ${UPDATE_COMMENTS} with id=${comment.id} to edit content`);
69
- }
70
- else {
71
- nextSteps.push(`Use ${FIND_COMMENTS} to view comments by task or project`);
72
- nextSteps.push(`Use ${UPDATE_COMMENTS} to edit any comment content`);
73
- }
74
- nextSteps.push(`Use ${DELETE_OBJECT} with type=comment to remove comments`);
75
- }
76
- const next = formatNextSteps(nextSteps);
77
- return `${summary}\n${next}`;
78
- }
79
- export { addComments };
@@ -1,63 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { formatNextSteps } from '../utils/response-builders.js';
4
- import { ToolNames } from '../utils/tool-names.js';
5
- const { ADD_SECTIONS, ADD_TASKS, FIND_PROJECTS, GET_OVERVIEW } = ToolNames;
6
- const ProjectSchema = z.object({
7
- name: z.string().min(1).describe('The name of the project.'),
8
- parentId: z
9
- .string()
10
- .optional()
11
- .describe('The ID of the parent project. If provided, creates this as a sub-project.'),
12
- isFavorite: z
13
- .boolean()
14
- .optional()
15
- .describe('Whether the project is a favorite. Defaults to false.'),
16
- viewStyle: z
17
- .enum(['list', 'board', 'calendar'])
18
- .optional()
19
- .describe('The project view style. Defaults to "list".'),
20
- });
21
- const ArgsSchema = {
22
- projects: z.array(ProjectSchema).min(1).describe('The array of projects to add.'),
23
- };
24
- const addProjects = {
25
- name: ToolNames.ADD_PROJECTS,
26
- description: 'Add one or more new projects.',
27
- parameters: ArgsSchema,
28
- async execute({ projects }, client) {
29
- const newProjects = await Promise.all(projects.map((project) => client.addProject(project)));
30
- const textContent = generateTextContent({ projects: newProjects });
31
- return getToolOutput({
32
- textContent,
33
- structuredContent: {
34
- projects: newProjects,
35
- totalCount: newProjects.length,
36
- },
37
- });
38
- },
39
- };
40
- function generateTextContent({ projects }) {
41
- const count = projects.length;
42
- const projectList = projects.map((project) => `• ${project.name} (id=${project.id})`).join('\n');
43
- const summary = `Added ${count} project${count === 1 ? '' : 's'}:\n${projectList}`;
44
- // Context-aware next steps for new projects
45
- const nextSteps = [];
46
- if (count === 1) {
47
- const project = projects[0];
48
- if (project) {
49
- nextSteps.push(`Use ${ADD_SECTIONS} to organize new project with sections`);
50
- nextSteps.push(`Use ${ADD_TASKS} to add your first tasks to this project`);
51
- nextSteps.push(`Use ${GET_OVERVIEW} with projectId=${project.id} to see project structure`);
52
- }
53
- }
54
- else {
55
- nextSteps.push(`Use ${ADD_SECTIONS} to organize these projects with sections`);
56
- nextSteps.push(`Use ${ADD_TASKS} to add tasks to these projects`);
57
- nextSteps.push(`Use ${FIND_PROJECTS} to see all projects including the new ones`);
58
- nextSteps.push(`Use ${GET_OVERVIEW} to see updated project hierarchy`);
59
- }
60
- const next = formatNextSteps(nextSteps);
61
- return `${summary}\n${next}`;
62
- }
63
- export { addProjects };
@@ -1,61 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { formatNextSteps } from '../utils/response-builders.js';
4
- import { ToolNames } from '../utils/tool-names.js';
5
- const { ADD_TASKS, FIND_TASKS, GET_OVERVIEW, FIND_SECTIONS } = ToolNames;
6
- const SectionSchema = z.object({
7
- name: z.string().min(1).describe('The name of the section.'),
8
- projectId: z.string().min(1).describe('The ID of the project to add the section to.'),
9
- });
10
- const ArgsSchema = {
11
- sections: z.array(SectionSchema).min(1).describe('The array of sections to add.'),
12
- };
13
- const addSections = {
14
- name: ToolNames.ADD_SECTIONS,
15
- description: 'Add one or more new sections to projects.',
16
- parameters: ArgsSchema,
17
- async execute({ sections }, client) {
18
- const newSections = await Promise.all(sections.map((section) => client.addSection(section)));
19
- const textContent = generateTextContent({ sections: newSections });
20
- return getToolOutput({
21
- textContent,
22
- structuredContent: {
23
- sections: newSections,
24
- totalCount: newSections.length,
25
- },
26
- });
27
- },
28
- };
29
- function generateTextContent({ sections }) {
30
- const count = sections.length;
31
- const sectionList = sections
32
- .map((section) => `• ${section.name} (id=${section.id}, projectId=${section.projectId})`)
33
- .join('\n');
34
- const summary = `Added ${count} section${count === 1 ? '' : 's'}:\n${sectionList}`;
35
- // Context-aware next steps for new sections
36
- const nextSteps = [];
37
- if (count === 1) {
38
- const section = sections[0];
39
- if (section) {
40
- nextSteps.push(`Use ${ADD_TASKS} with sectionId=${section.id} to add your first tasks`);
41
- nextSteps.push(`Use ${FIND_TASKS} with sectionId=${section.id} to verify setup`);
42
- nextSteps.push(`Use ${GET_OVERVIEW} with projectId=${section.projectId} to see project organization`);
43
- }
44
- }
45
- else {
46
- // Group sections by project for better guidance
47
- const projectIds = [...new Set(sections.map((s) => s.projectId))];
48
- nextSteps.push(`Use ${ADD_TASKS} to add tasks to these new sections`);
49
- if (projectIds.length === 1) {
50
- nextSteps.push(`Use ${GET_OVERVIEW} with projectId=${projectIds[0]} to see updated project structure`);
51
- nextSteps.push(`Use ${FIND_SECTIONS} with projectId=${projectIds[0]} to see all sections`);
52
- }
53
- else {
54
- nextSteps.push(`Use ${GET_OVERVIEW} to see updated project structures`);
55
- nextSteps.push(`Use ${FIND_SECTIONS} to see sections in specific projects`);
56
- }
57
- }
58
- const next = formatNextSteps(nextSteps);
59
- return `${summary}\n${next}`;
60
- }
61
- export { addSections };
@@ -1,160 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { mapTask } from '../tool-helpers.js';
4
- import { assignmentValidator } from '../utils/assignment-validator.js';
5
- import { DurationParseError, parseDuration } from '../utils/duration-parser.js';
6
- import { convertPriorityToNumber, PrioritySchema } from '../utils/priorities.js';
7
- import { generateTaskNextSteps, getDateString, summarizeTaskOperation, } from '../utils/response-builders.js';
8
- import { ToolNames } from '../utils/tool-names.js';
9
- const TaskSchema = z.object({
10
- content: z
11
- .string()
12
- .min(1)
13
- .describe('The task name/title. Should be concise and actionable (e.g., "Review PR #123", "Call dentist"). For longer content, use the description field instead. Supports Markdown.'),
14
- description: z
15
- .string()
16
- .optional()
17
- .describe('Additional details, notes, or context for the task. Use this for longer content rather than putting it in the task name. Supports Markdown.'),
18
- priority: PrioritySchema.optional().describe('The priority of the task: p1 (highest), p2 (high), p3 (medium), p4 (lowest/default).'),
19
- dueString: z.string().optional().describe('The due date for the task, in natural language.'),
20
- deadlineDate: z
21
- .string()
22
- .optional()
23
- .describe('The deadline date for the task in ISO 8601 format (YYYY-MM-DD, e.g., "2025-12-31"). Deadlines are immovable constraints shown with a different indicator than due dates.'),
24
- duration: z
25
- .string()
26
- .optional()
27
- .describe('The duration of the task. Use format: "2h" (hours), "90m" (minutes), "2h30m" (combined), or "1.5h" (decimal hours). Max 24h.'),
28
- labels: z.array(z.string()).optional().describe('The labels to attach to the task.'),
29
- projectId: z.string().optional().describe('The project ID to add this task to.'),
30
- sectionId: z.string().optional().describe('The section ID to add this task to.'),
31
- parentId: z.string().optional().describe('The parent task ID (for subtasks).'),
32
- responsibleUser: z
33
- .string()
34
- .optional()
35
- .describe('Assign task to this user. Can be a user ID, name, or email address. User must be a collaborator on the target project.'),
36
- });
37
- const ArgsSchema = {
38
- tasks: z.array(TaskSchema).min(1).describe('The array of tasks to add.'),
39
- };
40
- const addTasks = {
41
- name: ToolNames.ADD_TASKS,
42
- description: 'Add one or more tasks to a project, section, or parent. Supports assignment to project collaborators.',
43
- parameters: ArgsSchema,
44
- async execute({ tasks }, client) {
45
- const addTaskPromises = tasks.map((task) => processTask(task, client));
46
- const newTasks = await Promise.all(addTaskPromises);
47
- const mappedTasks = newTasks.map(mapTask);
48
- const textContent = generateTextContent({
49
- tasks: mappedTasks,
50
- args: { tasks },
51
- });
52
- return getToolOutput({
53
- textContent,
54
- structuredContent: {
55
- tasks: mappedTasks,
56
- totalCount: mappedTasks.length,
57
- },
58
- });
59
- },
60
- };
61
- async function processTask(task, client) {
62
- const { duration: durationStr, projectId, sectionId, parentId, responsibleUser, priority, labels, deadlineDate, ...otherTaskArgs } = task;
63
- let taskArgs = {
64
- ...otherTaskArgs,
65
- projectId,
66
- sectionId,
67
- parentId,
68
- labels,
69
- deadlineDate,
70
- };
71
- // Handle priority conversion if provided
72
- if (priority) {
73
- taskArgs.priority = convertPriorityToNumber(priority);
74
- }
75
- // Only prevent assignment (not task creation) without sufficient project context
76
- if (responsibleUser && !projectId && !sectionId && !parentId) {
77
- throw new Error(`Task "${task.content}": Cannot assign tasks without specifying project context. Please specify a projectId, sectionId, or parentId.`);
78
- }
79
- // Parse duration if provided
80
- if (durationStr) {
81
- try {
82
- const { minutes } = parseDuration(durationStr);
83
- taskArgs = {
84
- ...taskArgs,
85
- duration: minutes,
86
- durationUnit: 'minute',
87
- };
88
- }
89
- catch (error) {
90
- if (error instanceof DurationParseError) {
91
- throw new Error(`Task "${task.content}": ${error.message}`);
92
- }
93
- throw error;
94
- }
95
- }
96
- // Handle assignment if provided
97
- if (responsibleUser) {
98
- // Resolve target project for validation
99
- let targetProjectId = projectId;
100
- if (!targetProjectId && parentId) {
101
- // For subtasks, get project from parent task
102
- try {
103
- const parentTask = await client.getTask(parentId);
104
- targetProjectId = parentTask.projectId;
105
- }
106
- catch (_error) {
107
- throw new Error(`Task "${task.content}": Parent task "${parentId}" not found`);
108
- }
109
- }
110
- else if (!targetProjectId && sectionId) {
111
- // For section tasks, we need to find the project - this is a limitation
112
- // For now, we'll require explicit projectId when using assignments with sections
113
- throw new Error(`Task "${task.content}": When assigning tasks to sections, please also specify projectId`);
114
- }
115
- if (!targetProjectId) {
116
- throw new Error(`Task "${task.content}": Cannot determine target project for assignment validation`);
117
- }
118
- // Validate assignment using comprehensive validator
119
- const validation = await assignmentValidator.validateTaskCreationAssignment(client, targetProjectId, responsibleUser);
120
- if (!validation.isValid) {
121
- const errorMsg = validation.error?.message || 'Assignment validation failed';
122
- const suggestions = validation.error?.suggestions?.join('. ') || '';
123
- throw new Error(`Task "${task.content}": ${errorMsg}${suggestions ? `. ${suggestions}` : ''}`);
124
- }
125
- // Use the validated assignee ID
126
- taskArgs.assigneeId = validation.resolvedUser?.userId;
127
- }
128
- return await client.addTask(taskArgs);
129
- }
130
- function generateTextContent({ tasks, args, }) {
131
- // Get context for smart next steps
132
- const todayStr = getDateString();
133
- const hasToday = tasks.some((task) => task.dueDate === todayStr);
134
- // Generate context description for mixed contexts
135
- const contextTypes = new Set();
136
- for (const task of args.tasks) {
137
- if (task.projectId)
138
- contextTypes.add('projects');
139
- else if (task.sectionId)
140
- contextTypes.add('sections');
141
- else if (task.parentId)
142
- contextTypes.add('subtasks');
143
- else
144
- contextTypes.add('inbox');
145
- }
146
- let projectContext = '';
147
- if (contextTypes.size === 1) {
148
- const contextType = Array.from(contextTypes)[0];
149
- projectContext = contextType === 'inbox' ? '' : `to ${contextType}`;
150
- }
151
- else if (contextTypes.size > 1) {
152
- projectContext = 'to multiple contexts';
153
- }
154
- return summarizeTaskOperation('Added', tasks, {
155
- context: projectContext,
156
- nextSteps: generateTaskNextSteps('added', tasks, { hasToday }),
157
- showDetails: true,
158
- });
159
- }
160
- export { addTasks };
@@ -1,68 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { summarizeBatch } from '../utils/response-builders.js';
4
- import { ToolNames } from '../utils/tool-names.js';
5
- const ArgsSchema = {
6
- ids: z.array(z.string().min(1)).min(1).describe('The IDs of the tasks to complete.'),
7
- };
8
- const completeTasks = {
9
- name: ToolNames.COMPLETE_TASKS,
10
- description: 'Complete one or more tasks by their IDs.',
11
- parameters: ArgsSchema,
12
- async execute(args, client) {
13
- const completed = [];
14
- const failures = [];
15
- for (const id of args.ids) {
16
- try {
17
- await client.closeTask(id);
18
- completed.push(id);
19
- }
20
- catch (error) {
21
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
22
- failures.push({
23
- item: id,
24
- error: errorMessage,
25
- });
26
- }
27
- }
28
- const textContent = generateTextContent({
29
- completed,
30
- failures,
31
- args,
32
- });
33
- return getToolOutput({
34
- textContent,
35
- structuredContent: {
36
- completed,
37
- failures,
38
- totalRequested: args.ids.length,
39
- successCount: completed.length,
40
- failureCount: failures.length,
41
- },
42
- });
43
- },
44
- };
45
- function generateNextSteps(completed, failures) {
46
- if (completed > 0) {
47
- const moveResult = failures === 0
48
- ? "Use find-tasks-by-date('today') to tackle remaining overdue items."
49
- : 'Review failed completions and retry if needed.';
50
- return [moveResult];
51
- }
52
- if (failures > 0) {
53
- return ['Check task IDs and permissions, then retry.'];
54
- }
55
- return ['No tasks were completed.'];
56
- }
57
- function generateTextContent({ completed, failures, args, }) {
58
- const nextSteps = generateNextSteps(completed.length, failures.length);
59
- return summarizeBatch({
60
- action: 'Completed tasks',
61
- success: completed.length,
62
- total: args.ids.length,
63
- successItems: completed,
64
- failures,
65
- nextSteps,
66
- });
67
- }
68
- export { completeTasks };
@@ -1,79 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { formatNextSteps } from '../utils/response-builders.js';
4
- import { ToolNames } from '../utils/tool-names.js';
5
- const { FIND_PROJECTS, GET_OVERVIEW, FIND_SECTIONS, FIND_TASKS, FIND_TASKS_BY_DATE, FIND_COMMENTS, } = ToolNames;
6
- const ArgsSchema = {
7
- type: z
8
- .enum(['project', 'section', 'task', 'comment'])
9
- .describe('The type of entity to delete.'),
10
- id: z.string().min(1).describe('The ID of the entity to delete.'),
11
- };
12
- const deleteObject = {
13
- name: ToolNames.DELETE_OBJECT,
14
- description: 'Delete a project, section, task, or comment by its ID.',
15
- parameters: ArgsSchema,
16
- async execute(args, client) {
17
- switch (args.type) {
18
- case 'project':
19
- await client.deleteProject(args.id);
20
- break;
21
- case 'section':
22
- await client.deleteSection(args.id);
23
- break;
24
- case 'task':
25
- await client.deleteTask(args.id);
26
- break;
27
- case 'comment':
28
- await client.deleteComment(args.id);
29
- break;
30
- }
31
- const textContent = generateTextContent({
32
- type: args.type,
33
- id: args.id,
34
- });
35
- return getToolOutput({
36
- textContent,
37
- structuredContent: {
38
- deletedEntity: {
39
- type: args.type,
40
- id: args.id,
41
- },
42
- success: true,
43
- },
44
- });
45
- },
46
- };
47
- function generateTextContent({ type, id, }) {
48
- const summary = `Deleted ${type}: id=${id}`;
49
- // Recovery-focused next steps based on what was deleted
50
- const nextSteps = [];
51
- switch (type) {
52
- case 'project':
53
- // Help user understand impact and navigate remaining work
54
- nextSteps.push(`Use ${FIND_PROJECTS} to see remaining projects`);
55
- nextSteps.push('Note: All tasks and sections in this project were also deleted');
56
- nextSteps.push(`Use ${GET_OVERVIEW} to review your updated project structure`);
57
- break;
58
- case 'section':
59
- // Guide user to reorganize remaining sections and tasks
60
- nextSteps.push(`Use ${FIND_SECTIONS} to see remaining sections in the project`);
61
- nextSteps.push('Note: Tasks in this section were also deleted');
62
- nextSteps.push(`Use ${FIND_TASKS} with projectId to see unorganized tasks`);
63
- break;
64
- case 'task':
65
- // Help user stay focused on remaining work
66
- nextSteps.push(`Use ${FIND_TASKS_BY_DATE} to see remaining tasks for today`);
67
- nextSteps.push(`Use ${GET_OVERVIEW} to check if this affects any dependent tasks`);
68
- nextSteps.push('Note: Any subtasks of this task were also deleted');
69
- break;
70
- case 'comment':
71
- // Help user understand comment deletion impact
72
- nextSteps.push(`Use ${FIND_COMMENTS} to see remaining comments on the task/project`);
73
- nextSteps.push('Note: Comment attachments were also deleted');
74
- break;
75
- }
76
- const next = formatNextSteps(nextSteps);
77
- return `${summary}\n${next}`;
78
- }
79
- export { deleteObject };