@doist/todoist-ai 4.4.0 → 4.6.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 (80) hide show
  1. package/dist/index.d.ts +168 -35
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +24 -16
  4. package/dist/mcp-helpers.d.ts.map +1 -1
  5. package/dist/mcp-helpers.js +1 -1
  6. package/dist/mcp-server.d.ts.map +1 -1
  7. package/dist/mcp-server.js +78 -17
  8. package/dist/tool-helpers.d.ts +4 -0
  9. package/dist/tool-helpers.d.ts.map +1 -1
  10. package/dist/tool-helpers.js +2 -0
  11. package/dist/tools/__tests__/add-projects.test.js +1 -1
  12. package/dist/tools/__tests__/add-sections.test.js +1 -1
  13. package/dist/tools/__tests__/add-tasks.test.js +182 -13
  14. package/dist/tools/__tests__/assignment-integration.test.d.ts +2 -0
  15. package/dist/tools/__tests__/assignment-integration.test.d.ts.map +1 -0
  16. package/dist/tools/__tests__/assignment-integration.test.js +415 -0
  17. package/dist/tools/__tests__/find-projects.test.js +1 -1
  18. package/dist/tools/__tests__/find-sections.test.js +1 -1
  19. package/dist/tools/__tests__/find-tasks-by-date.test.js +1 -1
  20. package/dist/tools/__tests__/find-tasks.test.js +3 -3
  21. package/dist/tools/__tests__/get-overview.test.js +1 -1
  22. package/dist/tools/__tests__/update-tasks.test.js +82 -6
  23. package/dist/tools/__tests__/user-info.test.js +1 -1
  24. package/dist/tools/add-comments.d.ts +3 -3
  25. package/dist/tools/add-comments.d.ts.map +1 -1
  26. package/dist/tools/add-comments.js +1 -1
  27. package/dist/tools/add-projects.d.ts.map +1 -1
  28. package/dist/tools/add-projects.js +1 -1
  29. package/dist/tools/add-sections.d.ts.map +1 -1
  30. package/dist/tools/add-sections.js +1 -1
  31. package/dist/tools/add-tasks.d.ts +17 -7
  32. package/dist/tools/add-tasks.d.ts.map +1 -1
  33. package/dist/tools/add-tasks.js +51 -4
  34. package/dist/tools/find-comments.d.ts +2 -2
  35. package/dist/tools/find-completed-tasks.d.ts +4 -2
  36. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  37. package/dist/tools/find-completed-tasks.js +2 -2
  38. package/dist/tools/find-project-collaborators.d.ts +64 -0
  39. package/dist/tools/find-project-collaborators.d.ts.map +1 -0
  40. package/dist/tools/find-project-collaborators.js +151 -0
  41. package/dist/tools/find-tasks-by-date.d.ts +2 -0
  42. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  43. package/dist/tools/find-tasks-by-date.js +2 -2
  44. package/dist/tools/find-tasks.d.ts +7 -2
  45. package/dist/tools/find-tasks.d.ts.map +1 -1
  46. package/dist/tools/find-tasks.js +128 -33
  47. package/dist/tools/get-overview.d.ts +2 -2
  48. package/dist/tools/get-overview.d.ts.map +1 -1
  49. package/dist/tools/get-overview.js +1 -1
  50. package/dist/tools/manage-assignments.d.ts +52 -0
  51. package/dist/tools/manage-assignments.d.ts.map +1 -0
  52. package/dist/tools/manage-assignments.js +337 -0
  53. package/dist/tools/update-comments.d.ts.map +1 -1
  54. package/dist/tools/update-comments.js +1 -1
  55. package/dist/tools/update-sections.d.ts.map +1 -1
  56. package/dist/tools/update-sections.js +1 -1
  57. package/dist/tools/update-tasks.d.ts +17 -7
  58. package/dist/tools/update-tasks.d.ts.map +1 -1
  59. package/dist/tools/update-tasks.js +40 -10
  60. package/dist/utils/assignment-validator.d.ts +69 -0
  61. package/dist/utils/assignment-validator.d.ts.map +1 -0
  62. package/dist/utils/assignment-validator.js +253 -0
  63. package/dist/utils/duration-parser.d.ts +2 -2
  64. package/dist/utils/duration-parser.d.ts.map +1 -1
  65. package/dist/utils/priorities.d.ts +8 -0
  66. package/dist/utils/priorities.d.ts.map +1 -0
  67. package/dist/utils/priorities.js +15 -0
  68. package/dist/utils/response-builders.d.ts +2 -2
  69. package/dist/utils/response-builders.d.ts.map +1 -1
  70. package/dist/utils/response-builders.js +8 -1
  71. package/dist/utils/test-helpers.d.ts +2 -0
  72. package/dist/utils/test-helpers.d.ts.map +1 -1
  73. package/dist/utils/test-helpers.js +2 -0
  74. package/dist/utils/tool-names.d.ts +2 -0
  75. package/dist/utils/tool-names.d.ts.map +1 -1
  76. package/dist/utils/tool-names.js +3 -0
  77. package/dist/utils/user-resolver.d.ts +39 -0
  78. package/dist/utils/user-resolver.d.ts.map +1 -0
  79. package/dist/utils/user-resolver.js +179 -0
  80. package/package.json +6 -6
@@ -1,5 +1,5 @@
1
1
  import { jest } from '@jest/globals';
2
- import { TEST_IDS, createMockProject, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
2
+ import { createMockProject, extractStructuredContent, extractTextContent, TEST_IDS, } from '../../utils/test-helpers.js';
3
3
  import { ToolNames } from '../../utils/tool-names.js';
4
4
  import { addProjects } from '../add-projects.js';
5
5
  // Mock the Todoist API
@@ -1,5 +1,5 @@
1
1
  import { jest } from '@jest/globals';
2
- import { TEST_IDS, createMockSection, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
2
+ import { createMockSection, extractStructuredContent, extractTextContent, TEST_IDS, } from '../../utils/test-helpers.js';
3
3
  import { ToolNames } from '../../utils/tool-names.js';
4
4
  import { addSections } from '../add-sections.js';
5
5
  // Mock the Todoist API
@@ -1,5 +1,5 @@
1
1
  import { jest } from '@jest/globals';
2
- import { TODAY, createMockTask, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
2
+ import { createMockTask, extractStructuredContent, extractTextContent, TODAY, } from '../../utils/test-helpers.js';
3
3
  import { ToolNames } from '../../utils/tool-names.js';
4
4
  import { addTasks } from '../add-tasks.js';
5
5
  // Mock the Todoist API
@@ -42,11 +42,14 @@ describe(`${ADD_TASKS} tool`, () => {
42
42
  .mockResolvedValueOnce(mockApiResponse2);
43
43
  const result = await addTasks.execute({
44
44
  tasks: [
45
- { content: 'First task content', projectId: '6cfCcrrCFg2xP94Q' },
45
+ {
46
+ content: 'First task content',
47
+ projectId: '6cfCcrrCFg2xP94Q',
48
+ },
46
49
  {
47
50
  content: 'Second task content',
48
51
  description: 'Task description',
49
- priority: 2,
52
+ priority: 'p2',
50
53
  dueString: 'Aug 15',
51
54
  projectId: '6cfCcrrCFg2xP94Q',
52
55
  },
@@ -63,7 +66,7 @@ describe(`${ADD_TASKS} tool`, () => {
63
66
  expect(mockTodoistApi.addTask).toHaveBeenNthCalledWith(2, {
64
67
  content: 'Second task content',
65
68
  description: 'Task description',
66
- priority: 2,
69
+ priority: 3,
67
70
  dueString: 'Aug 15',
68
71
  projectId: '6cfCcrrCFg2xP94Q',
69
72
  sectionId: undefined,
@@ -99,7 +102,7 @@ describe(`${ADD_TASKS} tool`, () => {
99
102
  {
100
103
  content: 'Subtask content',
101
104
  description: 'Subtask description',
102
- priority: 3,
105
+ priority: 'p3',
103
106
  projectId: '6cfCcrrCFg2xP94Q',
104
107
  sectionId: 'section-123',
105
108
  parentId: 'parent-task-456',
@@ -109,7 +112,7 @@ describe(`${ADD_TASKS} tool`, () => {
109
112
  expect(mockTodoistApi.addTask).toHaveBeenCalledWith({
110
113
  content: 'Subtask content',
111
114
  description: 'Subtask description',
112
- priority: 3,
115
+ priority: 2,
113
116
  projectId: '6cfCcrrCFg2xP94Q',
114
117
  sectionId: 'section-123',
115
118
  parentId: 'parent-task-456',
@@ -203,25 +206,179 @@ describe(`${ADD_TASKS} tool`, () => {
203
206
  ];
204
207
  for (const testCase of testCases) {
205
208
  mockTodoistApi.addTask.mockClear();
206
- await addTasks.execute({ tasks: [{ content: 'Test task', duration: testCase.input }] }, mockTodoistApi);
209
+ await addTasks.execute({
210
+ tasks: [
211
+ {
212
+ content: 'Test task',
213
+ duration: testCase.input,
214
+ projectId: '6cfCcrrCFg2xP94Q',
215
+ },
216
+ ],
217
+ }, mockTodoistApi);
207
218
  expect(mockTodoistApi.addTask).toHaveBeenCalledWith(expect.objectContaining({
208
219
  duration: testCase.expectedMinutes,
209
220
  durationUnit: 'minute',
210
221
  }));
211
222
  }
212
223
  });
224
+ it('should add task with labels', async () => {
225
+ const mockApiResponse = createMockTask({
226
+ id: '8485093755',
227
+ content: 'Task with labels',
228
+ labels: ['urgent', 'work'],
229
+ url: 'https://todoist.com/showTask?id=8485093755',
230
+ addedAt: '2025-08-13T22:09:56.123456Z',
231
+ });
232
+ mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
233
+ const result = await addTasks.execute({
234
+ tasks: [
235
+ {
236
+ content: 'Task with labels',
237
+ labels: ['urgent', 'work'],
238
+ projectId: '6cfCcrrCFg2xP94Q',
239
+ },
240
+ ],
241
+ }, mockTodoistApi);
242
+ expect(mockTodoistApi.addTask).toHaveBeenCalledWith({
243
+ content: 'Task with labels',
244
+ labels: ['urgent', 'work'],
245
+ projectId: '6cfCcrrCFg2xP94Q',
246
+ sectionId: undefined,
247
+ parentId: undefined,
248
+ });
249
+ // Verify structured content includes labels
250
+ const structuredContent = extractStructuredContent(result);
251
+ expect(structuredContent.tasks).toHaveLength(1);
252
+ expect(structuredContent.tasks[0]).toEqual(expect.objectContaining({
253
+ labels: ['urgent', 'work'],
254
+ }));
255
+ });
256
+ it('should add task with empty labels array', async () => {
257
+ const mockApiResponse = createMockTask({
258
+ id: '8485093756',
259
+ content: 'Task with empty labels',
260
+ labels: [],
261
+ url: 'https://todoist.com/showTask?id=8485093756',
262
+ addedAt: '2025-08-13T22:09:56.123456Z',
263
+ });
264
+ mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
265
+ await addTasks.execute({
266
+ tasks: [
267
+ {
268
+ content: 'Task with empty labels',
269
+ labels: [],
270
+ projectId: '6cfCcrrCFg2xP94Q',
271
+ },
272
+ ],
273
+ }, mockTodoistApi);
274
+ expect(mockTodoistApi.addTask).toHaveBeenCalledWith({
275
+ content: 'Task with empty labels',
276
+ labels: [],
277
+ projectId: '6cfCcrrCFg2xP94Q',
278
+ sectionId: undefined,
279
+ parentId: undefined,
280
+ });
281
+ });
282
+ it('should add task without labels field', async () => {
283
+ const mockApiResponse = createMockTask({
284
+ id: '8485093757',
285
+ content: 'Task without labels',
286
+ url: 'https://todoist.com/showTask?id=8485093757',
287
+ addedAt: '2025-08-13T22:09:56.123456Z',
288
+ });
289
+ mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
290
+ await addTasks.execute({
291
+ tasks: [
292
+ {
293
+ content: 'Task without labels',
294
+ projectId: '6cfCcrrCFg2xP94Q',
295
+ },
296
+ ],
297
+ }, mockTodoistApi);
298
+ expect(mockTodoistApi.addTask).toHaveBeenCalledWith({
299
+ content: 'Task without labels',
300
+ labels: undefined,
301
+ projectId: '6cfCcrrCFg2xP94Q',
302
+ sectionId: undefined,
303
+ parentId: undefined,
304
+ });
305
+ });
306
+ it('should add multiple tasks with different label configurations', async () => {
307
+ const mockApiResponse1 = createMockTask({
308
+ id: '8485093758',
309
+ content: 'Task with labels',
310
+ labels: ['personal'],
311
+ });
312
+ const mockApiResponse2 = createMockTask({
313
+ id: '8485093759',
314
+ content: 'Task without labels',
315
+ });
316
+ const mockApiResponse3 = createMockTask({
317
+ id: '8485093760',
318
+ content: 'Task with multiple labels',
319
+ labels: ['work', 'urgent', 'review'],
320
+ });
321
+ mockTodoistApi.addTask
322
+ .mockResolvedValueOnce(mockApiResponse1)
323
+ .mockResolvedValueOnce(mockApiResponse2)
324
+ .mockResolvedValueOnce(mockApiResponse3);
325
+ await addTasks.execute({
326
+ tasks: [
327
+ {
328
+ content: 'Task with labels',
329
+ labels: ['personal'],
330
+ projectId: '6cfCcrrCFg2xP94Q',
331
+ },
332
+ {
333
+ content: 'Task without labels',
334
+ projectId: '6cfCcrrCFg2xP94Q',
335
+ },
336
+ {
337
+ content: 'Task with multiple labels',
338
+ labels: ['work', 'urgent', 'review'],
339
+ projectId: '6cfCcrrCFg2xP94Q',
340
+ },
341
+ ],
342
+ }, mockTodoistApi);
343
+ expect(mockTodoistApi.addTask).toHaveBeenCalledTimes(3);
344
+ expect(mockTodoistApi.addTask).toHaveBeenNthCalledWith(1, expect.objectContaining({
345
+ labels: ['personal'],
346
+ }));
347
+ expect(mockTodoistApi.addTask).toHaveBeenNthCalledWith(2, expect.objectContaining({
348
+ labels: undefined,
349
+ }));
350
+ expect(mockTodoistApi.addTask).toHaveBeenNthCalledWith(3, expect.objectContaining({
351
+ labels: ['work', 'urgent', 'review'],
352
+ }));
353
+ });
213
354
  });
214
355
  describe('error handling', () => {
215
356
  it('should throw error for invalid duration format', async () => {
216
- await expect(addTasks.execute({ tasks: [{ content: 'Task with invalid duration', duration: 'invalid' }] }, mockTodoistApi)).rejects.toThrow('Task "Task with invalid duration": Invalid duration format "invalid"');
357
+ await expect(addTasks.execute({
358
+ tasks: [
359
+ {
360
+ content: 'Task with invalid duration',
361
+ duration: 'invalid',
362
+ projectId: '6cfCcrrCFg2xP94Q',
363
+ },
364
+ ],
365
+ }, mockTodoistApi)).rejects.toThrow('Task "Task with invalid duration": Invalid duration format "invalid"');
217
366
  });
218
367
  it('should throw error for duration exceeding 24 hours', async () => {
219
- await expect(addTasks.execute({ tasks: [{ content: 'Task with too long duration', duration: '25h' }] }, mockTodoistApi)).rejects.toThrow('Task "Task with too long duration": Invalid duration format "25h": Duration cannot exceed 24 hours (1440 minutes)');
368
+ await expect(addTasks.execute({
369
+ tasks: [
370
+ {
371
+ content: 'Task with too long duration',
372
+ duration: '25h',
373
+ projectId: '6cfCcrrCFg2xP94Q',
374
+ },
375
+ ],
376
+ }, mockTodoistApi)).rejects.toThrow('Task "Task with too long duration": Invalid duration format "25h": Duration cannot exceed 24 hours (1440 minutes)');
220
377
  });
221
378
  it('should propagate API errors', async () => {
222
379
  const apiError = new Error('API Error: Task content is required');
223
380
  mockTodoistApi.addTask.mockRejectedValue(apiError);
224
- await expect(addTasks.execute({ tasks: [{ content: '' }] }, mockTodoistApi)).rejects.toThrow(apiError.message);
381
+ await expect(addTasks.execute({ tasks: [{ content: '', projectId: '6cfCcrrCFg2xP94Q' }] }, mockTodoistApi)).rejects.toThrow(apiError.message);
225
382
  });
226
383
  it('should handle partial failures when adding multiple tasks', async () => {
227
384
  const mockApiResponse = createMockTask({
@@ -236,8 +393,8 @@ describe(`${ADD_TASKS} tool`, () => {
236
393
  .mockRejectedValueOnce(apiError);
237
394
  await expect(addTasks.execute({
238
395
  tasks: [
239
- { content: 'First task content' },
240
- { content: 'Second task content' },
396
+ { content: 'First task content', projectId: '6cfCcrrCFg2xP94Q' },
397
+ { content: 'Second task content', projectId: '6cfCcrrCFg2xP94Q' },
241
398
  ],
242
399
  }, mockTodoistApi)).rejects.toThrow('API Error: Second task failed');
243
400
  // Verify first task was attempted
@@ -246,6 +403,8 @@ describe(`${ADD_TASKS} tool`, () => {
246
403
  });
247
404
  describe('next steps logic', () => {
248
405
  it('should suggest find-tasks-by-date for today when hasToday is true', async () => {
406
+ // Clear any leftover mocks from previous tests
407
+ mockTodoistApi.addTask.mockClear();
249
408
  const mockApiResponse = createMockTask({
250
409
  id: '8485093755',
251
410
  content: 'Task due today',
@@ -260,12 +419,22 @@ describe(`${ADD_TASKS} tool`, () => {
260
419
  },
261
420
  });
262
421
  mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
263
- const result = await addTasks.execute({ tasks: [{ content: 'Task due today', dueString: 'today' }] }, mockTodoistApi);
422
+ const result = await addTasks.execute({
423
+ tasks: [
424
+ {
425
+ content: 'Task due today',
426
+ dueString: 'today',
427
+ projectId: '6cfCcrrCFg2xP94Q',
428
+ },
429
+ ],
430
+ }, mockTodoistApi);
264
431
  const textContent = extractTextContent(result);
265
432
  expect(textContent).toMatchSnapshot();
266
433
  expect(textContent).toContain(`Use ${GET_OVERVIEW} to see your updated project organization`);
267
434
  });
268
435
  it('should suggest overview tool when no hasToday context', async () => {
436
+ // Clear any leftover mocks from previous tests
437
+ mockTodoistApi.addTask.mockClear();
269
438
  const mockApiResponse = createMockTask({
270
439
  id: '8485093756',
271
440
  content: 'Regular task',
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=assignment-integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignment-integration.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/assignment-integration.test.ts"],"names":[],"mappings":""}