@doist/todoist-ai 4.10.0 → 4.13.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 (35) hide show
  1. package/dist/filter-helpers.d.ts +51 -0
  2. package/dist/filter-helpers.d.ts.map +1 -0
  3. package/dist/filter-helpers.js +79 -0
  4. package/dist/index.d.ts +47 -23
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/tool-helpers.d.ts +8 -20
  7. package/dist/tool-helpers.d.ts.map +1 -1
  8. package/dist/tool-helpers.js +6 -23
  9. package/dist/tools/__tests__/find-completed-tasks.test.js +71 -12
  10. package/dist/tools/__tests__/find-tasks-by-date.test.js +118 -20
  11. package/dist/tools/__tests__/find-tasks.test.js +2 -0
  12. package/dist/tools/add-projects.d.ts +3 -3
  13. package/dist/tools/add-tasks.d.ts +6 -3
  14. package/dist/tools/add-tasks.d.ts.map +1 -1
  15. package/dist/tools/delete-object.d.ts +1 -1
  16. package/dist/tools/find-completed-tasks.d.ts +8 -2
  17. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  18. package/dist/tools/find-completed-tasks.js +37 -4
  19. package/dist/tools/find-tasks-by-date.d.ts +9 -0
  20. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  21. package/dist/tools/find-tasks-by-date.js +39 -15
  22. package/dist/tools/find-tasks.d.ts +5 -2
  23. package/dist/tools/find-tasks.d.ts.map +1 -1
  24. package/dist/tools/find-tasks.js +20 -36
  25. package/dist/tools/update-comments.d.ts +3 -3
  26. package/dist/tools/update-sections.d.ts +3 -3
  27. package/dist/tools/update-tasks.d.ts +9 -6
  28. package/dist/tools/update-tasks.d.ts.map +1 -1
  29. package/dist/utils/test-helpers.d.ts +3 -0
  30. package/dist/utils/test-helpers.d.ts.map +1 -1
  31. package/dist/utils/test-helpers.js +3 -0
  32. package/dist/utils/user-resolver.d.ts +2 -4
  33. package/dist/utils/user-resolver.d.ts.map +1 -1
  34. package/dist/utils/user-resolver.js +5 -5
  35. package/package.json +4 -4
@@ -6,11 +6,25 @@ import { findCompletedTasks } from '../find-completed-tasks.js';
6
6
  const mockTodoistApi = {
7
7
  getCompletedTasksByCompletionDate: jest.fn(),
8
8
  getCompletedTasksByDueDate: jest.fn(),
9
+ getUser: jest.fn(),
9
10
  };
10
11
  const { FIND_COMPLETED_TASKS } = ToolNames;
11
12
  describe(`${FIND_COMPLETED_TASKS} tool`, () => {
12
13
  beforeEach(() => {
13
14
  jest.clearAllMocks();
15
+ // Mock default user with UTC timezone
16
+ mockTodoistApi.getUser.mockResolvedValue({
17
+ id: 'test-user-id',
18
+ fullName: 'Test User',
19
+ email: 'test@example.com',
20
+ tzInfo: {
21
+ timezone: 'UTC',
22
+ gmtString: '+00:00',
23
+ hours: 0,
24
+ minutes: 0,
25
+ isDst: 0,
26
+ },
27
+ });
14
28
  });
15
29
  describe('getting completed tasks by completion date (default)', () => {
16
30
  it('should get completed tasks by completion date', async () => {
@@ -46,8 +60,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
46
60
  labelsOperator: 'or',
47
61
  }, mockTodoistApi);
48
62
  expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
49
- since: '2025-08-10',
50
- until: '2025-08-15',
63
+ since: '2025-08-10T00:00:00.000Z',
64
+ until: '2025-08-15T23:59:59.000Z',
51
65
  limit: 50,
52
66
  });
53
67
  expect(extractTextContent(result)).toMatchSnapshot();
@@ -68,8 +82,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
68
82
  labelsOperator: 'or',
69
83
  }, mockTodoistApi);
70
84
  expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
71
- since: '2025-08-01',
72
- until: '2025-08-31',
85
+ since: '2025-08-01T00:00:00.000Z',
86
+ until: '2025-08-31T23:59:59.000Z',
73
87
  projectId: 'specific-project-id',
74
88
  limit: 100,
75
89
  cursor: 'current-cursor',
@@ -111,8 +125,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
111
125
  labelsOperator: 'or',
112
126
  }, mockTodoistApi);
113
127
  expect(mockTodoistApi.getCompletedTasksByDueDate).toHaveBeenCalledWith({
114
- since: '2025-08-10',
115
- until: '2025-08-20',
128
+ since: '2025-08-10T00:00:00.000Z',
129
+ until: '2025-08-20T23:59:59.000Z',
116
130
  limit: 50,
117
131
  });
118
132
  expect(mockTodoistApi.getCompletedTasksByCompletionDate).not.toHaveBeenCalled();
@@ -172,8 +186,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
172
186
  mockMethod.mockResolvedValue(mockResponse);
173
187
  const result = await findCompletedTasks.execute(params, mockTodoistApi);
174
188
  expect(mockMethod).toHaveBeenCalledWith({
175
- since: params.since,
176
- until: params.until,
189
+ since: `${params.since}T00:00:00.000Z`,
190
+ until: `${params.until}T23:59:59.000Z`,
177
191
  limit: params.limit,
178
192
  filterQuery: expectedFilter,
179
193
  filterLang: 'en',
@@ -194,8 +208,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
194
208
  mockTodoistApi.getCompletedTasksByCompletionDate.mockResolvedValue(mockResponse);
195
209
  await findCompletedTasks.execute(params, mockTodoistApi);
196
210
  expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
197
- since: params.since,
198
- until: params.until,
211
+ since: `${params.since}T00:00:00.000Z`,
212
+ until: `${params.until}T23:59:59.000Z`,
199
213
  limit: params.limit,
200
214
  });
201
215
  });
@@ -221,8 +235,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
221
235
  mockTodoistApi.getCompletedTasksByDueDate.mockResolvedValue(mockResponse);
222
236
  const result = await findCompletedTasks.execute(params, mockTodoistApi);
223
237
  expect(mockTodoistApi.getCompletedTasksByDueDate).toHaveBeenCalledWith({
224
- since: params.since,
225
- until: params.until,
238
+ since: `${params.since}T00:00:00.000Z`,
239
+ until: `${params.until}T23:59:59.000Z`,
226
240
  limit: params.limit,
227
241
  projectId: params.projectId,
228
242
  sectionId: params.sectionId,
@@ -233,6 +247,51 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
233
247
  expect(textContent).toMatchSnapshot();
234
248
  });
235
249
  });
250
+ describe('timezone handling', () => {
251
+ it('should convert user timezone to UTC correctly (Europe/Madrid)', async () => {
252
+ // Mock user with Madrid timezone
253
+ mockTodoistApi.getUser.mockResolvedValue({
254
+ id: 'test-user-id',
255
+ fullName: 'Test User',
256
+ email: 'test@example.com',
257
+ tzInfo: {
258
+ timezone: 'Europe/Madrid',
259
+ gmtString: '+02:00',
260
+ hours: 2,
261
+ minutes: 0,
262
+ isDst: 0,
263
+ },
264
+ });
265
+ const mockCompletedTasks = [
266
+ createMockTask({
267
+ id: '8485093750',
268
+ content: 'Task completed in Madrid timezone',
269
+ completedAt: '2025-10-11T15:30:00Z',
270
+ }),
271
+ ];
272
+ mockTodoistApi.getCompletedTasksByCompletionDate.mockResolvedValue({
273
+ items: mockCompletedTasks,
274
+ nextCursor: null,
275
+ });
276
+ const result = await findCompletedTasks.execute({
277
+ getBy: 'completion',
278
+ limit: 50,
279
+ since: '2025-10-11',
280
+ until: '2025-10-11',
281
+ labels: [],
282
+ labelsOperator: 'or',
283
+ }, mockTodoistApi);
284
+ // Should convert Madrid local time to UTC
285
+ // 2025-10-11 00:00:00 +02:00 = 2025-10-10 22:00:00 UTC
286
+ // 2025-10-11 23:59:59 +02:00 = 2025-10-11 21:59:59 UTC
287
+ expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
288
+ since: '2025-10-10T22:00:00.000Z',
289
+ until: '2025-10-11T21:59:59.000Z',
290
+ limit: 50,
291
+ });
292
+ expect(extractTextContent(result)).toMatchSnapshot();
293
+ });
294
+ });
236
295
  describe('error handling', () => {
237
296
  it('should propagate completion date API errors', async () => {
238
297
  const apiError = new Error('API Error: Invalid date range');
@@ -2,16 +2,22 @@ import { jest } from '@jest/globals';
2
2
  import { getTasksByFilter } from '../../tool-helpers.js';
3
3
  import { createMappedTask, createMockUser, extractStructuredContent, extractTextContent, TEST_ERRORS, TEST_IDS, } from '../../utils/test-helpers.js';
4
4
  import { ToolNames } from '../../utils/tool-names.js';
5
+ import { resolveUserNameToId } from '../../utils/user-resolver.js';
5
6
  import { findTasksByDate } from '../find-tasks-by-date.js';
6
- // Mock the tool helpers
7
+ // Mock only getTasksByFilter, use actual implementations for everything else
7
8
  jest.mock('../../tool-helpers', () => {
8
9
  const actual = jest.requireActual('../../tool-helpers');
9
10
  return {
11
+ ...actual,
10
12
  getTasksByFilter: jest.fn(),
11
- filterTasksByResponsibleUser: actual.filterTasksByResponsibleUser,
12
13
  };
13
14
  });
15
+ // Mock user resolver
16
+ jest.mock('../../utils/user-resolver', () => ({
17
+ resolveUserNameToId: jest.fn(),
18
+ }));
14
19
  const mockGetTasksByFilter = getTasksByFilter;
20
+ const mockResolveUserNameToId = resolveUserNameToId;
15
21
  // Mock the Todoist API (not directly used by find-tasks-by-date, but needed for type)
16
22
  const mockTodoistApi = {
17
23
  getUser: jest.fn(),
@@ -57,7 +63,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
57
63
  // Verify the query uses daysCount=1 by checking the end date calculation
58
64
  expect(mockGetTasksByFilter).toHaveBeenCalledWith({
59
65
  client: mockTodoistApi,
60
- query: '(due after: 2025-08-20 | due: 2025-08-20) & due before: 2025-08-21',
66
+ query: '(due after: 2025-08-20 | due: 2025-08-20) & due before: 2025-08-21 & !assigned to: others',
61
67
  cursor: undefined,
62
68
  limit: 50,
63
69
  });
@@ -71,7 +77,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
71
77
  const result = await findTasksByDate.execute({ startDate: 'today', limit: 50, daysCount: 7 }, mockTodoistApi);
72
78
  expect(mockGetTasksByFilter).toHaveBeenCalledWith({
73
79
  client: mockTodoistApi,
74
- query: 'today | overdue',
80
+ query: '(today | overdue) & !assigned to: others',
75
81
  cursor: undefined,
76
82
  limit: 50,
77
83
  });
@@ -236,7 +242,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
236
242
  limit: 50,
237
243
  labels: ['work'],
238
244
  },
239
- expectedQueryPattern: 'today | overdue & ((@work))', // Will be combined with date query
245
+ expectedQueryPattern: '(today | overdue) & ((@work)) & !assigned to: others', // Will be combined with date query
240
246
  },
241
247
  {
242
248
  name: 'multiple labels with AND operator',
@@ -247,7 +253,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
247
253
  labels: ['work', 'urgent'],
248
254
  labelsOperator: 'and',
249
255
  },
250
- expectedQueryPattern: 'today | overdue & ((@work & @urgent))',
256
+ expectedQueryPattern: '(today | overdue) & ((@work & @urgent)) & !assigned to: others',
251
257
  },
252
258
  {
253
259
  name: 'multiple labels with OR operator',
@@ -339,6 +345,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
339
345
  });
340
346
  describe('responsible user filtering', () => {
341
347
  it('should filter results to show only unassigned tasks or tasks assigned to current user', async () => {
348
+ // Backend filtering: API should only return unassigned + assigned to me
342
349
  const mockTasks = [
343
350
  createMappedTask({
344
351
  id: TEST_IDS.TASK_1,
@@ -352,16 +359,22 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
352
359
  dueDate: '2025-08-15',
353
360
  responsibleUid: null, // Unassigned
354
361
  }),
355
- createMappedTask({
356
- id: TEST_IDS.TASK_3,
357
- content: 'Someone else task',
358
- dueDate: '2025-08-15',
359
- responsibleUid: 'other-user-id', // Assigned to someone else
360
- }),
361
362
  ];
362
363
  const mockResponse = { tasks: mockTasks, nextCursor: null };
363
364
  mockGetTasksByFilter.mockResolvedValue(mockResponse);
364
- const result = await findTasksByDate.execute({ startDate: 'today', daysCount: 1, limit: 50 }, mockTodoistApi);
365
+ const result = await findTasksByDate.execute({
366
+ startDate: 'today',
367
+ daysCount: 1,
368
+ limit: 50,
369
+ responsibleUserFiltering: 'unassignedOrMe',
370
+ }, mockTodoistApi);
371
+ // Verify the query includes the assignment filter
372
+ expect(mockGetTasksByFilter).toHaveBeenCalledWith({
373
+ client: mockTodoistApi,
374
+ query: '(today | overdue) & !assigned to: others',
375
+ cursor: undefined,
376
+ limit: 50,
377
+ });
365
378
  const structuredContent = extractStructuredContent(result);
366
379
  // Should only return tasks 1 and 2, not task 3
367
380
  expect(structuredContent.tasks).toHaveLength(2);
@@ -371,6 +384,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
371
384
  ]);
372
385
  });
373
386
  it('should filter overdue results to show only unassigned tasks or tasks assigned to current user', async () => {
387
+ // Backend filtering: API should only return unassigned + assigned to me
374
388
  const mockTasks = [
375
389
  createMappedTask({
376
390
  id: TEST_IDS.TASK_1,
@@ -384,16 +398,22 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
384
398
  dueDate: '2025-08-10',
385
399
  responsibleUid: null, // Unassigned
386
400
  }),
387
- createMappedTask({
388
- id: TEST_IDS.TASK_3,
389
- content: 'Someone else overdue task',
390
- dueDate: '2025-08-10',
391
- responsibleUid: 'other-user-id', // Assigned to someone else
392
- }),
393
401
  ];
394
402
  const mockResponse = { tasks: mockTasks, nextCursor: null };
395
403
  mockGetTasksByFilter.mockResolvedValue(mockResponse);
396
- const result = await findTasksByDate.execute({ overdueOption: 'overdue-only', daysCount: 1, limit: 50 }, mockTodoistApi);
404
+ const result = await findTasksByDate.execute({
405
+ overdueOption: 'overdue-only',
406
+ daysCount: 1,
407
+ limit: 50,
408
+ responsibleUserFiltering: 'unassignedOrMe',
409
+ }, mockTodoistApi);
410
+ // Verify the query includes the assignment filter
411
+ expect(mockGetTasksByFilter).toHaveBeenCalledWith({
412
+ client: mockTodoistApi,
413
+ query: 'overdue & !assigned to: others',
414
+ cursor: undefined,
415
+ limit: 50,
416
+ });
397
417
  const structuredContent = extractStructuredContent(result);
398
418
  // Should only return tasks 1 and 2, not task 3
399
419
  expect(structuredContent.tasks).toHaveLength(2);
@@ -403,6 +423,84 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
403
423
  ]);
404
424
  });
405
425
  });
426
+ describe('responsibleUser parameter', () => {
427
+ it('should filter tasks by specific user email', async () => {
428
+ mockResolveUserNameToId.mockResolvedValue({
429
+ userId: 'user-123',
430
+ displayName: 'John Doe',
431
+ email: 'john@example.com',
432
+ });
433
+ const mockTasks = [
434
+ createMappedTask({
435
+ id: TEST_IDS.TASK_1,
436
+ content: 'Task assigned to John',
437
+ dueDate: '2025-08-15',
438
+ responsibleUid: 'user-123',
439
+ }),
440
+ ];
441
+ const mockResponse = { tasks: mockTasks, nextCursor: null };
442
+ mockGetTasksByFilter.mockResolvedValue(mockResponse);
443
+ const result = await findTasksByDate.execute({
444
+ startDate: 'today',
445
+ daysCount: 1,
446
+ limit: 50,
447
+ responsibleUser: 'john@example.com',
448
+ }, mockTodoistApi);
449
+ expect(mockResolveUserNameToId).toHaveBeenCalledWith(mockTodoistApi, 'john@example.com');
450
+ expect(mockGetTasksByFilter).toHaveBeenCalledWith({
451
+ client: mockTodoistApi,
452
+ query: '(today | overdue) & assigned to: john@example.com',
453
+ cursor: undefined,
454
+ limit: 50,
455
+ });
456
+ const textContent = extractTextContent(result);
457
+ expect(textContent).toContain('assigned to john@example.com');
458
+ expect(textContent).toMatchSnapshot();
459
+ });
460
+ it('should throw error when user cannot be resolved', async () => {
461
+ mockResolveUserNameToId.mockResolvedValue(null);
462
+ await expect(findTasksByDate.execute({
463
+ startDate: 'today',
464
+ daysCount: 1,
465
+ limit: 50,
466
+ responsibleUser: 'nonexistent@example.com',
467
+ }, mockTodoistApi)).rejects.toThrow('Could not find user: "nonexistent@example.com". Make sure the user is a collaborator on a shared project.');
468
+ });
469
+ it('should combine responsibleUser with labels and date filters', async () => {
470
+ mockResolveUserNameToId.mockResolvedValue({
471
+ userId: 'user-789',
472
+ displayName: 'Bob Wilson',
473
+ email: 'bob@example.com',
474
+ });
475
+ const mockTasks = [
476
+ createMappedTask({
477
+ id: TEST_IDS.TASK_1,
478
+ content: 'Important task for Bob',
479
+ dueDate: '2025-08-20',
480
+ responsibleUid: 'user-789',
481
+ labels: ['urgent'],
482
+ }),
483
+ ];
484
+ const mockResponse = { tasks: mockTasks, nextCursor: null };
485
+ mockGetTasksByFilter.mockResolvedValue(mockResponse);
486
+ await findTasksByDate.execute({
487
+ startDate: '2025-08-20',
488
+ daysCount: 1,
489
+ limit: 50,
490
+ responsibleUser: 'bob@example.com',
491
+ labels: ['urgent'],
492
+ }, mockTodoistApi);
493
+ expect(mockGetTasksByFilter).toHaveBeenCalledWith({
494
+ client: mockTodoistApi,
495
+ query: expect.stringContaining('2025-08-20'),
496
+ cursor: undefined,
497
+ limit: 50,
498
+ });
499
+ const call = mockGetTasksByFilter.mock.calls[0]?.[0];
500
+ expect(call?.query).toContain('(@urgent)');
501
+ expect(call?.query).toContain('assigned to: bob@example.com');
502
+ });
503
+ });
406
504
  describe('error handling', () => {
407
505
  it.each([
408
506
  {
@@ -697,6 +697,7 @@ End of test content.`;
697
697
  mockResolveUserNameToId.mockResolvedValue({
698
698
  userId: 'specific-user-id',
699
699
  displayName: 'John Doe',
700
+ email: 'john@example.com',
700
701
  });
701
702
  const result = await findTasks.execute({ searchText: 'task', responsibleUser: 'John Doe', limit: 10 }, mockTodoistApi);
702
703
  const structuredContent = extractStructuredContent(result);
@@ -726,6 +727,7 @@ End of test content.`;
726
727
  mockResolveUserNameToId.mockResolvedValue({
727
728
  userId: 'specific-user-id',
728
729
  displayName: 'John Doe',
730
+ email: 'john@example.com',
729
731
  });
730
732
  const result = await findTasks.execute({ projectId: TEST_IDS.PROJECT_WORK, responsibleUser: 'John Doe', limit: 10 }, mockTodoistApi);
731
733
  const structuredContent = extractStructuredContent(result);
@@ -10,22 +10,22 @@ declare const addProjects: {
10
10
  viewStyle: z.ZodOptional<z.ZodEnum<["list", "board", "calendar"]>>;
11
11
  }, "strip", z.ZodTypeAny, {
12
12
  name: string;
13
- parentId?: string | undefined;
14
13
  isFavorite?: boolean | undefined;
15
14
  viewStyle?: "list" | "board" | "calendar" | undefined;
15
+ parentId?: string | undefined;
16
16
  }, {
17
17
  name: string;
18
- parentId?: string | undefined;
19
18
  isFavorite?: boolean | undefined;
20
19
  viewStyle?: "list" | "board" | "calendar" | undefined;
20
+ parentId?: string | undefined;
21
21
  }>, "many">;
22
22
  };
23
23
  execute({ projects }: {
24
24
  projects: {
25
25
  name: string;
26
- parentId?: string | undefined;
27
26
  isFavorite?: boolean | undefined;
28
27
  viewStyle?: "list" | "board" | "calendar" | undefined;
28
+ parentId?: string | undefined;
29
29
  }[];
30
30
  }, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
31
31
  content: {
@@ -18,8 +18,8 @@ declare const addTasks: {
18
18
  }, "strip", z.ZodTypeAny, {
19
19
  content: string;
20
20
  description?: string | undefined;
21
- projectId?: string | undefined;
22
21
  parentId?: string | undefined;
22
+ projectId?: string | undefined;
23
23
  sectionId?: string | undefined;
24
24
  labels?: string[] | undefined;
25
25
  duration?: string | undefined;
@@ -29,8 +29,8 @@ declare const addTasks: {
29
29
  }, {
30
30
  content: string;
31
31
  description?: string | undefined;
32
- projectId?: string | undefined;
33
32
  parentId?: string | undefined;
33
+ projectId?: string | undefined;
34
34
  sectionId?: string | undefined;
35
35
  labels?: string[] | undefined;
36
36
  duration?: string | undefined;
@@ -43,8 +43,8 @@ declare const addTasks: {
43
43
  tasks: {
44
44
  content: string;
45
45
  description?: string | undefined;
46
- projectId?: string | undefined;
47
46
  parentId?: string | undefined;
47
+ projectId?: string | undefined;
48
48
  sectionId?: string | undefined;
49
49
  labels?: string[] | undefined;
50
50
  duration?: string | undefined;
@@ -72,6 +72,9 @@ declare const addTasks: {
72
72
  duration: string | null;
73
73
  responsibleUid: string | null;
74
74
  assignedByUid: string | null;
75
+ checked: boolean;
76
+ completedAt: string | null;
77
+ updatedAt: string | null;
75
78
  }[];
76
79
  totalCount: number;
77
80
  };
@@ -1 +1 @@
1
- {"version":3,"file":"add-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/add-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAClF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAqDvB,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuB4B,CAAA;AAgI1C,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"add-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/add-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAClF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAqDvB,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuB4B,CAAA;AAgI1C,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -7,8 +7,8 @@ declare const deleteObject: {
7
7
  id: z.ZodString;
8
8
  };
9
9
  execute(args: {
10
- type: "task" | "comment" | "project" | "section";
11
10
  id: string;
11
+ type: "task" | "comment" | "project" | "section";
12
12
  }, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
13
13
  content: {
14
14
  type: "text";
@@ -12,6 +12,7 @@ declare const findCompletedTasks: {
12
12
  projectId: z.ZodOptional<z.ZodString>;
13
13
  sectionId: z.ZodOptional<z.ZodString>;
14
14
  parentId: z.ZodOptional<z.ZodString>;
15
+ responsibleUser: z.ZodOptional<z.ZodString>;
15
16
  limit: z.ZodDefault<z.ZodNumber>;
16
17
  cursor: z.ZodOptional<z.ZodString>;
17
18
  };
@@ -20,12 +21,13 @@ declare const findCompletedTasks: {
20
21
  getBy: "due" | "completion";
21
22
  since: string;
22
23
  until: string;
23
- projectId?: string | undefined;
24
24
  parentId?: string | undefined;
25
25
  workspaceId?: string | undefined;
26
+ projectId?: string | undefined;
26
27
  sectionId?: string | undefined;
27
28
  labels?: string[] | undefined;
28
29
  cursor?: string | undefined;
30
+ responsibleUser?: string | undefined;
29
31
  labelsOperator?: "and" | "or" | undefined;
30
32
  }, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
31
33
  content: {
@@ -47,6 +49,9 @@ declare const findCompletedTasks: {
47
49
  duration: string | null;
48
50
  responsibleUid: string | null;
49
51
  assignedByUid: string | null;
52
+ checked: boolean;
53
+ completedAt: string | null;
54
+ updatedAt: string | null;
50
55
  }[];
51
56
  nextCursor: string | null;
52
57
  totalCount: number;
@@ -56,12 +61,13 @@ declare const findCompletedTasks: {
56
61
  getBy: "due" | "completion";
57
62
  since: string;
58
63
  until: string;
59
- projectId?: string | undefined;
60
64
  parentId?: string | undefined;
61
65
  workspaceId?: string | undefined;
66
+ projectId?: string | undefined;
62
67
  sectionId?: string | undefined;
63
68
  labels?: string[] | undefined;
64
69
  cursor?: string | undefined;
70
+ responsibleUser?: string | undefined;
65
71
  labelsOperator?: "and" | "or" | undefined;
66
72
  };
67
73
  };
@@ -1 +1 @@
1
- {"version":3,"file":"find-completed-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/find-completed-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiDvB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCkB,CAAA;AAmE1C,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
1
+ {"version":3,"file":"find-completed-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/find-completed-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAsDvB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEkB,CAAA;AA2E1C,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { appendToQuery, resolveResponsibleUser } from '../filter-helpers.js';
2
3
  import { getToolOutput } from '../mcp-helpers.js';
3
4
  import { mapTask } from '../tool-helpers.js';
4
5
  import { ApiLimits } from '../utils/constants.js';
@@ -25,6 +26,10 @@ const ArgsSchema = {
25
26
  projectId: z.string().optional().describe('The ID of the project to get the tasks for.'),
26
27
  sectionId: z.string().optional().describe('The ID of the section to get the tasks for.'),
27
28
  parentId: z.string().optional().describe('The ID of the parent task to get the tasks for.'),
29
+ responsibleUser: z
30
+ .string()
31
+ .optional()
32
+ .describe('Find tasks assigned to this user. Can be a user ID, name, or email address.'),
28
33
  limit: z
29
34
  .number()
30
35
  .int()
@@ -43,22 +48,45 @@ const findCompletedTasks = {
43
48
  description: 'Get completed tasks.',
44
49
  parameters: ArgsSchema,
45
50
  async execute(args, client) {
46
- const { getBy, labels, labelsOperator, ...rest } = args;
51
+ const { getBy, labels, labelsOperator, since, until, responsibleUser, ...rest } = args;
52
+ // Resolve assignee name to user ID if provided
53
+ const resolved = await resolveResponsibleUser(client, responsibleUser);
54
+ const assigneeEmail = resolved?.email;
55
+ // Build combined filter query (labels + assignment)
47
56
  const labelsFilter = generateLabelsFilter(labels, labelsOperator);
57
+ let filterQuery = labelsFilter;
58
+ if (resolved && assigneeEmail) {
59
+ filterQuery = appendToQuery(filterQuery, `assigned to: ${assigneeEmail}`);
60
+ }
61
+ // Get user timezone to convert local dates to UTC
62
+ const user = await client.getUser();
63
+ const userGmtOffset = user.tzInfo?.gmtString || '+00:00';
64
+ // Convert user's local date to UTC timestamps
65
+ // This ensures we capture the entire day from the user's perspective
66
+ const sinceWithOffset = `${since}T00:00:00${userGmtOffset}`;
67
+ const untilWithOffset = `${until}T23:59:59${userGmtOffset}`;
68
+ // Parse and convert to UTC
69
+ const sinceDateTime = new Date(sinceWithOffset).toISOString();
70
+ const untilDateTime = new Date(untilWithOffset).toISOString();
48
71
  const { items, nextCursor } = getBy === 'completion'
49
72
  ? await client.getCompletedTasksByCompletionDate({
50
73
  ...rest,
51
- ...(labelsFilter ? { filterQuery: labelsFilter, filterLang: 'en' } : {}),
74
+ since: sinceDateTime,
75
+ until: untilDateTime,
76
+ ...(filterQuery ? { filterQuery, filterLang: 'en' } : {}),
52
77
  })
53
78
  : await client.getCompletedTasksByDueDate({
54
79
  ...rest,
55
- ...(labelsFilter ? { filterQuery: labelsFilter, filterLang: 'en' } : {}),
80
+ since: sinceDateTime,
81
+ until: untilDateTime,
82
+ ...(filterQuery ? { filterQuery, filterLang: 'en' } : {}),
56
83
  });
57
84
  const mappedTasks = items.map(mapTask);
58
85
  const textContent = generateTextContent({
59
86
  tasks: mappedTasks,
60
87
  args,
61
88
  nextCursor,
89
+ assigneeEmail,
62
90
  });
63
91
  return getToolOutput({
64
92
  textContent,
@@ -72,7 +100,7 @@ const findCompletedTasks = {
72
100
  });
73
101
  },
74
102
  };
75
- function generateTextContent({ tasks, args, nextCursor, }) {
103
+ function generateTextContent({ tasks, args, nextCursor, assigneeEmail, }) {
76
104
  // Generate subject description
77
105
  const getByText = args.getBy === 'completion' ? 'completed' : 'due';
78
106
  const subject = `Completed tasks (by ${getByText} date)`;
@@ -94,6 +122,11 @@ function generateTextContent({ tasks, args, nextCursor, }) {
94
122
  .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
95
123
  filterHints.push(`labels: ${labelText}`);
96
124
  }
125
+ // Add responsible user filter information
126
+ if (args.responsibleUser) {
127
+ const email = assigneeEmail || args.responsibleUser;
128
+ filterHints.push(`assigned to: ${email}`);
129
+ }
97
130
  // Generate helpful suggestions for empty results
98
131
  const zeroReasonHints = [];
99
132
  if (tasks.length === 0) {
@@ -10,12 +10,16 @@ declare const findTasksByDate: {
10
10
  daysCount: z.ZodDefault<z.ZodNumber>;
11
11
  limit: z.ZodDefault<z.ZodNumber>;
12
12
  cursor: z.ZodOptional<z.ZodString>;
13
+ responsibleUser: z.ZodOptional<z.ZodString>;
14
+ responsibleUserFiltering: z.ZodOptional<z.ZodEnum<["assigned", "unassignedOrMe", "all"]>>;
13
15
  };
14
16
  execute(args: {
15
17
  limit: number;
16
18
  daysCount: number;
19
+ responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
17
20
  labels?: string[] | undefined;
18
21
  cursor?: string | undefined;
22
+ responsibleUser?: string | undefined;
19
23
  labelsOperator?: "and" | "or" | undefined;
20
24
  startDate?: string | undefined;
21
25
  overdueOption?: "overdue-only" | "include-overdue" | "exclude-overdue" | undefined;
@@ -39,6 +43,9 @@ declare const findTasksByDate: {
39
43
  duration: string | null;
40
44
  responsibleUid: string | null;
41
45
  assignedByUid: string | null;
46
+ checked: boolean;
47
+ completedAt: string | null;
48
+ updatedAt: string | null;
42
49
  }[];
43
50
  nextCursor: string | null;
44
51
  totalCount: number;
@@ -46,8 +53,10 @@ declare const findTasksByDate: {
46
53
  appliedFilters: {
47
54
  limit: number;
48
55
  daysCount: number;
56
+ responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
49
57
  labels?: string[] | undefined;
50
58
  cursor?: string | undefined;
59
+ responsibleUser?: string | undefined;
51
60
  labelsOperator?: "and" | "or" | undefined;
52
61
  startDate?: string | undefined;
53
62
  overdueOption?: "overdue-only" | "include-overdue" | "exclude-overdue" | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"find-tasks-by-date.d.ts","sourceRoot":"","sources":["../../src/tools/find-tasks-by-date.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAmDvB,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmEqB,CAAA;AAwF1C,OAAO,EAAE,eAAe,EAAE,CAAA"}
1
+ {"version":3,"file":"find-tasks-by-date.d.ts","sourceRoot":"","sources":["../../src/tools/find-tasks-by-date.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAmEvB,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2EqB,CAAA;AAsG1C,OAAO,EAAE,eAAe,EAAE,CAAA"}