@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,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 };
@@ -1,102 +0,0 @@
1
- import { getProjectUrl, getTaskUrl } from '@doist/todoist-api-typescript';
2
- import { z } from 'zod';
3
- import { getErrorOutput } from '../mcp-helpers.js';
4
- import { mapProject, mapTask } from '../tool-helpers.js';
5
- import { ToolNames } from '../utils/tool-names.js';
6
- const ArgsSchema = {
7
- id: z
8
- .string()
9
- .min(1)
10
- .describe('A unique identifier for the document in the format "task:{id}" or "project:{id}".'),
11
- };
12
- /**
13
- * OpenAI MCP fetch tool - retrieves the full contents of a task or project by ID.
14
- *
15
- * This tool follows the OpenAI MCP fetch tool specification:
16
- * @see https://platform.openai.com/docs/mcp#fetch-tool
17
- */
18
- const fetch = {
19
- name: ToolNames.FETCH,
20
- description: 'Fetch the full contents of a task or project by its ID. The ID should be in the format "task:{id}" or "project:{id}".',
21
- parameters: ArgsSchema,
22
- async execute(args, client) {
23
- try {
24
- const { id } = args;
25
- // Parse the composite ID
26
- const [type, objectId] = id.split(':', 2);
27
- if (!objectId || (type !== 'task' && type !== 'project')) {
28
- throw new Error('Invalid ID format. Expected "task:{id}" or "project:{id}". Example: "task:8485093748" or "project:6cfCcrrCFg2xP94Q"');
29
- }
30
- let result;
31
- if (type === 'task') {
32
- // Fetch task
33
- const task = await client.getTask(objectId);
34
- const mappedTask = mapTask(task);
35
- // Build text content
36
- const textParts = [mappedTask.content];
37
- if (mappedTask.description) {
38
- textParts.push(`\n\nDescription: ${mappedTask.description}`);
39
- }
40
- if (mappedTask.dueDate) {
41
- textParts.push(`\nDue: ${mappedTask.dueDate}`);
42
- }
43
- if (mappedTask.labels.length > 0) {
44
- textParts.push(`\nLabels: ${mappedTask.labels.join(', ')}`);
45
- }
46
- result = {
47
- id: `task:${mappedTask.id}`,
48
- title: mappedTask.content,
49
- text: textParts.join(''),
50
- url: getTaskUrl(mappedTask.id),
51
- metadata: {
52
- priority: mappedTask.priority,
53
- projectId: mappedTask.projectId,
54
- sectionId: mappedTask.sectionId,
55
- parentId: mappedTask.parentId,
56
- recurring: mappedTask.recurring,
57
- duration: mappedTask.duration,
58
- responsibleUid: mappedTask.responsibleUid,
59
- assignedByUid: mappedTask.assignedByUid,
60
- checked: mappedTask.checked,
61
- completedAt: mappedTask.completedAt,
62
- },
63
- };
64
- }
65
- else {
66
- // Fetch project
67
- const project = await client.getProject(objectId);
68
- const mappedProject = mapProject(project);
69
- // Build text content
70
- const textParts = [mappedProject.name];
71
- if (mappedProject.isShared) {
72
- textParts.push('\n\nShared project');
73
- }
74
- if (mappedProject.isFavorite) {
75
- textParts.push('\nFavorite: Yes');
76
- }
77
- result = {
78
- id: `project:${mappedProject.id}`,
79
- title: mappedProject.name,
80
- text: textParts.join(''),
81
- url: getProjectUrl(mappedProject.id),
82
- metadata: {
83
- color: mappedProject.color,
84
- isFavorite: mappedProject.isFavorite,
85
- isShared: mappedProject.isShared,
86
- parentId: mappedProject.parentId,
87
- inboxProject: mappedProject.inboxProject,
88
- viewStyle: mappedProject.viewStyle,
89
- },
90
- };
91
- }
92
- // Return as JSON-encoded string in a text content item (OpenAI MCP spec)
93
- const jsonText = JSON.stringify(result);
94
- return { content: [{ type: 'text', text: jsonText }] };
95
- }
96
- catch (error) {
97
- const message = error instanceof Error ? error.message : 'An unknown error occurred';
98
- return getErrorOutput(message);
99
- }
100
- },
101
- };
102
- export { fetch };
@@ -1,221 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { mapActivityEvent } from '../tool-helpers.js';
4
- import { ApiLimits } from '../utils/constants.js';
5
- import { summarizeList } from '../utils/response-builders.js';
6
- import { ToolNames } from '../utils/tool-names.js';
7
- const { FIND_TASKS, USER_INFO } = ToolNames;
8
- const ArgsSchema = {
9
- objectType: z
10
- .enum(['task', 'project', 'comment'])
11
- .optional()
12
- .describe('Type of object to filter by.'),
13
- objectId: z
14
- .string()
15
- .optional()
16
- .describe('Filter by specific object ID (task, project, or comment).'),
17
- eventType: z
18
- .enum([
19
- 'added',
20
- 'updated',
21
- 'deleted',
22
- 'completed',
23
- 'uncompleted',
24
- 'archived',
25
- 'unarchived',
26
- 'shared',
27
- 'left',
28
- ])
29
- .optional()
30
- .describe('Type of event to filter by.'),
31
- projectId: z.string().optional().describe('Filter events by parent project ID.'),
32
- taskId: z.string().optional().describe('Filter events by parent task ID (for subtask events).'),
33
- initiatorId: z.string().optional().describe('Filter by the user ID who initiated the event.'),
34
- limit: z
35
- .number()
36
- .int()
37
- .min(1)
38
- .max(ApiLimits.ACTIVITY_MAX)
39
- .default(ApiLimits.ACTIVITY_DEFAULT)
40
- .describe('Maximum number of activity events to return.'),
41
- cursor: z
42
- .string()
43
- .optional()
44
- .describe('Pagination cursor for retrieving the next page of results.'),
45
- };
46
- const findActivity = {
47
- name: ToolNames.FIND_ACTIVITY,
48
- description: 'Retrieve recent activity logs to monitor and audit changes in Todoist. Shows events from all users by default (use initiatorId to filter by specific user). Track task completions, updates, deletions, project changes, and more with flexible filtering. Note: Date-based filtering is not supported by the Todoist API.',
49
- parameters: ArgsSchema,
50
- async execute(args, client) {
51
- const { objectType, objectId, eventType, projectId, taskId, initiatorId, limit, cursor } = args;
52
- // Build API arguments
53
- const apiArgs = {
54
- limit,
55
- cursor: cursor ?? null,
56
- };
57
- // Add optional filters
58
- if (objectType)
59
- apiArgs.objectType = objectType;
60
- if (objectId && objectId !== 'remove')
61
- apiArgs.objectId = objectId;
62
- if (eventType)
63
- apiArgs.eventType = eventType;
64
- if (projectId)
65
- apiArgs.parentProjectId = projectId;
66
- if (taskId)
67
- apiArgs.parentItemId = taskId;
68
- if (initiatorId)
69
- apiArgs.initiatorId = initiatorId;
70
- // Fetch activity logs from API
71
- const { results, nextCursor } = await client.getActivityLogs(apiArgs);
72
- const events = results.map(mapActivityEvent);
73
- // Generate text content
74
- const textContent = generateTextContent({
75
- events,
76
- args,
77
- nextCursor,
78
- });
79
- return getToolOutput({
80
- textContent,
81
- structuredContent: {
82
- events,
83
- nextCursor,
84
- totalCount: events.length,
85
- hasMore: Boolean(nextCursor),
86
- appliedFilters: args,
87
- },
88
- });
89
- },
90
- };
91
- function generateTextContent({ events, args, nextCursor, }) {
92
- // Generate subject description
93
- let subject = 'Activity events';
94
- // Build subject based on filters
95
- const subjectParts = [];
96
- if (args.eventType) {
97
- subjectParts.push(`${args.eventType}`);
98
- }
99
- if (args.objectType) {
100
- const objectLabel = args.objectType === 'task' ? 'tasks' : `${args.objectType}s`;
101
- subjectParts.push(objectLabel);
102
- }
103
- if (subjectParts.length > 0) {
104
- subject = `Activity: ${subjectParts.join(' ')}`;
105
- }
106
- // Generate filter hints
107
- const filterHints = [];
108
- if (args.objectId) {
109
- filterHints.push(`object ID: ${args.objectId}`);
110
- }
111
- if (args.projectId) {
112
- filterHints.push(`project: ${args.projectId}`);
113
- }
114
- if (args.taskId) {
115
- filterHints.push(`task: ${args.taskId}`);
116
- }
117
- if (args.initiatorId) {
118
- filterHints.push(`initiator: ${args.initiatorId}`);
119
- }
120
- // Generate helpful suggestions for empty results
121
- const zeroReasonHints = [];
122
- if (events.length === 0) {
123
- zeroReasonHints.push('No activity events match the specified filters');
124
- zeroReasonHints.push('Note: Activity logs only show recent events');
125
- if (args.eventType) {
126
- zeroReasonHints.push(`Try removing the eventType filter (${args.eventType})`);
127
- }
128
- if (args.objectType) {
129
- zeroReasonHints.push(`Try removing the objectType filter (${args.objectType})`);
130
- }
131
- if (args.objectId || args.projectId || args.taskId) {
132
- zeroReasonHints.push('Verify the object ID is correct');
133
- }
134
- }
135
- // Generate contextual next steps
136
- const nextSteps = [];
137
- if (events.length > 0) {
138
- // Suggest related tools based on what was found
139
- const hasTaskEvents = events.some((e) => e.objectType === 'task' || e.objectType === 'item');
140
- const hasCompletions = events.some((e) => e.eventType === 'completed');
141
- if (hasTaskEvents) {
142
- nextSteps.push(`Use ${FIND_TASKS} to view current task details`);
143
- }
144
- if (hasCompletions) {
145
- nextSteps.push('Review completed tasks to track productivity');
146
- }
147
- if (args.initiatorId) {
148
- nextSteps.push(`Use ${USER_INFO} to get details about the user`);
149
- }
150
- // Suggest narrowing down if too many results
151
- if (events.length >= args.limit && !nextCursor) {
152
- nextSteps.push('Add more specific filters to narrow down results');
153
- }
154
- }
155
- return summarizeList({
156
- subject,
157
- count: events.length,
158
- limit: args.limit,
159
- nextCursor: nextCursor ?? undefined,
160
- filterHints,
161
- previewLines: previewActivityEvents(events, Math.min(events.length, args.limit)),
162
- zeroReasonHints,
163
- nextSteps,
164
- });
165
- }
166
- /**
167
- * Formats activity events into readable preview lines
168
- */
169
- function previewActivityEvents(events, limit = 10) {
170
- const previewEvents = events.slice(0, limit);
171
- const lines = previewEvents.map(formatActivityEventPreview).join('\n');
172
- // If we're showing fewer events than the total, add an indicator
173
- if (events.length > limit) {
174
- const remaining = events.length - limit;
175
- return `${lines}\n ... and ${remaining} more event${remaining === 1 ? '' : 's'}`;
176
- }
177
- return lines;
178
- }
179
- /**
180
- * Formats a single activity event into a readable preview line
181
- */
182
- function formatActivityEventPreview(event) {
183
- const date = formatEventDate(event.eventDate);
184
- const eventLabel = `${event.eventType} ${event.objectType}`;
185
- // Extract useful content from extraData if available
186
- let contentInfo = '';
187
- if (event.extraData) {
188
- const content = event.extraData.content || event.extraData.name || event.extraData.last_content;
189
- if (content && typeof content === 'string') {
190
- // Truncate long content
191
- const truncated = content.length > 50 ? `${content.substring(0, 47)}...` : content;
192
- contentInfo = ` • "${truncated}"`;
193
- }
194
- }
195
- const objectId = event.objectId ? ` • id=${event.objectId}` : '';
196
- const initiator = event.initiatorId ? ` • by=${event.initiatorId}` : ' • system';
197
- const projectInfo = event.parentProjectId ? ` • project=${event.parentProjectId}` : '';
198
- return ` [${date}] ${eventLabel}${contentInfo}${objectId}${initiator}${projectInfo}`;
199
- }
200
- /**
201
- * Formats an ISO date string to a more readable format
202
- */
203
- function formatEventDate(isoDate) {
204
- try {
205
- const date = new Date(isoDate);
206
- // Format as: Oct 23, 14:30 (in UTC for deterministic snapshots)
207
- const month = date.toLocaleDateString('en-US', { month: 'short', timeZone: 'UTC' });
208
- const day = date.toLocaleDateString('en-US', { day: 'numeric', timeZone: 'UTC' });
209
- const time = date.toLocaleTimeString('en-US', {
210
- hour: '2-digit',
211
- minute: '2-digit',
212
- hour12: false,
213
- timeZone: 'UTC',
214
- });
215
- return `${month} ${day}, ${time}`;
216
- }
217
- catch {
218
- return isoDate;
219
- }
220
- }
221
- export { findActivity };
@@ -1,148 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { ApiLimits } from '../utils/constants.js';
4
- import { formatNextSteps } from '../utils/response-builders.js';
5
- import { ToolNames } from '../utils/tool-names.js';
6
- const { ADD_COMMENTS, UPDATE_COMMENTS, DELETE_OBJECT } = ToolNames;
7
- const ArgsSchema = {
8
- taskId: z.string().optional().describe('Find comments for a specific task.'),
9
- projectId: z
10
- .string()
11
- .optional()
12
- .describe('Find comments for a specific project. Project ID should be an ID string, or the text "inbox", for inbox tasks.'),
13
- commentId: z.string().optional().describe('Get a specific comment by ID.'),
14
- cursor: z.string().optional().describe('Pagination cursor for retrieving more results.'),
15
- limit: z
16
- .number()
17
- .int()
18
- .min(1)
19
- .max(ApiLimits.COMMENTS_MAX)
20
- .optional()
21
- .describe('Maximum number of comments to return'),
22
- };
23
- const findComments = {
24
- name: ToolNames.FIND_COMMENTS,
25
- description: 'Find comments by task, project, or get a specific comment by ID. Exactly one of taskId, projectId, or commentId must be provided.',
26
- parameters: ArgsSchema,
27
- async execute(args, client) {
28
- // Validate that exactly one search parameter is provided
29
- const searchParams = [args.taskId, args.projectId, args.commentId].filter(Boolean);
30
- if (searchParams.length === 0) {
31
- throw new Error('Must provide exactly one of: taskId, projectId, or commentId.');
32
- }
33
- if (searchParams.length > 1) {
34
- throw new Error('Cannot provide multiple search parameters. Choose one of: taskId, projectId, or commentId.');
35
- }
36
- // Resolve "inbox" to actual inbox project ID if needed
37
- const resolvedProjectId = args.projectId === 'inbox' ? (await client.getUser()).inboxProjectId : args.projectId;
38
- let comments;
39
- let hasMore = false;
40
- let nextCursor = null;
41
- if (args.commentId) {
42
- // Get single comment
43
- const comment = await client.getComment(args.commentId);
44
- comments = [comment];
45
- }
46
- else if (args.taskId) {
47
- // Get comments by task
48
- const response = await client.getComments({
49
- taskId: args.taskId,
50
- cursor: args.cursor || null,
51
- limit: args.limit || ApiLimits.COMMENTS_DEFAULT,
52
- });
53
- comments = response.results;
54
- hasMore = response.nextCursor !== null;
55
- nextCursor = response.nextCursor;
56
- }
57
- else if (resolvedProjectId) {
58
- // Get comments by project
59
- const response = await client.getComments({
60
- projectId: resolvedProjectId,
61
- cursor: args.cursor || null,
62
- limit: args.limit || ApiLimits.COMMENTS_DEFAULT,
63
- });
64
- comments = response.results;
65
- hasMore = response.nextCursor !== null;
66
- nextCursor = response.nextCursor;
67
- }
68
- else {
69
- // This should never happen due to validation, but TypeScript needs it
70
- throw new Error('Invalid state: no search parameter provided');
71
- }
72
- const textContent = generateTextContent({
73
- comments,
74
- searchType: args.commentId ? 'single' : args.taskId ? 'task' : 'project',
75
- searchId: args.commentId || args.taskId || args.projectId || '',
76
- hasMore,
77
- nextCursor,
78
- });
79
- return getToolOutput({
80
- textContent,
81
- structuredContent: {
82
- comments,
83
- searchType: args.commentId ? 'single' : args.taskId ? 'task' : 'project',
84
- searchId: args.commentId || args.taskId || args.projectId || '',
85
- hasMore,
86
- nextCursor,
87
- totalCount: comments.length,
88
- },
89
- });
90
- },
91
- };
92
- function generateTextContent({ comments, searchType, searchId, hasMore, nextCursor, }) {
93
- if (comments.length === 0) {
94
- return `No comments found for ${searchType}${searchType !== 'single' ? ` ${searchId}` : ''}`;
95
- }
96
- // Build summary
97
- let summary;
98
- if (searchType === 'single') {
99
- const comment = comments[0];
100
- if (!comment) {
101
- return 'Comment not found';
102
- }
103
- const hasAttachment = comment.fileAttachment !== null;
104
- const attachmentInfo = hasAttachment
105
- ? ` • Has attachment: ${comment.fileAttachment?.fileName || 'file'}`
106
- : '';
107
- summary = `Found comment${attachmentInfo} • id=${comment.id}`;
108
- }
109
- else {
110
- const attachmentCount = comments.filter((c) => c.fileAttachment !== null).length;
111
- const attachmentInfo = attachmentCount > 0 ? ` (${attachmentCount} with attachments)` : '';
112
- const commentsLabel = comments.length === 1 ? 'comment' : 'comments';
113
- summary = `Found ${comments.length} ${commentsLabel} for ${searchType} ${searchId}${attachmentInfo}`;
114
- if (hasMore) {
115
- summary += ' • More available';
116
- }
117
- }
118
- // Context-aware next steps
119
- const nextSteps = [];
120
- if (searchType === 'single') {
121
- const comment = comments[0];
122
- if (comment) {
123
- nextSteps.push(`Use ${UPDATE_COMMENTS} with id=${comment.id} to edit content`);
124
- nextSteps.push(`Use ${DELETE_OBJECT} with type=comment id=${comment.id} to remove`);
125
- // Suggest viewing related comments
126
- if (comment.taskId) {
127
- nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with taskId=${comment.taskId} to see all task comments`);
128
- }
129
- else if (comment.projectId) {
130
- nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with projectId=${comment.projectId} to see all project comments`);
131
- }
132
- }
133
- }
134
- else {
135
- // Multiple comments
136
- nextSteps.push(`Use ${ADD_COMMENTS} with ${searchType}Id=${searchId} to add new comment`);
137
- if (comments.length > 0) {
138
- nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with commentId to view specific comment details`);
139
- }
140
- // Pagination
141
- if (hasMore && nextCursor) {
142
- nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with cursor="${nextCursor}" to get more results`);
143
- }
144
- }
145
- const next = formatNextSteps(nextSteps);
146
- return `${summary}\n${next}`;
147
- }
148
- export { findComments };