@doist/todoist-ai 4.10.0 → 4.11.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.
@@ -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');
@@ -16,17 +16,17 @@ declare const findCompletedTasks: {
16
16
  cursor: z.ZodOptional<z.ZodString>;
17
17
  };
18
18
  execute(args: {
19
- limit: number;
20
- getBy: "due" | "completion";
19
+ getBy: "completion" | "due";
21
20
  since: string;
22
21
  until: string;
23
- projectId?: string | undefined;
24
- parentId?: string | undefined;
22
+ limit: number;
23
+ labels?: string[] | undefined;
24
+ labelsOperator?: "and" | "or" | undefined;
25
25
  workspaceId?: string | undefined;
26
+ projectId?: string | undefined;
26
27
  sectionId?: string | undefined;
27
- labels?: string[] | undefined;
28
+ parentId?: string | undefined;
28
29
  cursor?: string | undefined;
29
- labelsOperator?: "and" | "or" | undefined;
30
30
  }, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
31
31
  content: {
32
32
  type: "text";
@@ -52,17 +52,17 @@ declare const findCompletedTasks: {
52
52
  totalCount: number;
53
53
  hasMore: boolean;
54
54
  appliedFilters: {
55
- limit: number;
56
- getBy: "due" | "completion";
55
+ getBy: "completion" | "due";
57
56
  since: string;
58
57
  until: string;
59
- projectId?: string | undefined;
60
- parentId?: string | undefined;
58
+ limit: number;
59
+ labels?: string[] | undefined;
60
+ labelsOperator?: "and" | "or" | undefined;
61
61
  workspaceId?: string | undefined;
62
+ projectId?: string | undefined;
62
63
  sectionId?: string | undefined;
63
- labels?: string[] | undefined;
64
+ parentId?: string | undefined;
64
65
  cursor?: string | undefined;
65
- labelsOperator?: "and" | "or" | undefined;
66
66
  };
67
67
  };
68
68
  } | {
@@ -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;AAiDvB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDkB,CAAA;AAmE1C,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
@@ -43,15 +43,29 @@ const findCompletedTasks = {
43
43
  description: 'Get completed tasks.',
44
44
  parameters: ArgsSchema,
45
45
  async execute(args, client) {
46
- const { getBy, labels, labelsOperator, ...rest } = args;
46
+ const { getBy, labels, labelsOperator, since, until, ...rest } = args;
47
47
  const labelsFilter = generateLabelsFilter(labels, labelsOperator);
48
+ // Get user timezone to convert local dates to UTC
49
+ const user = await client.getUser();
50
+ const userGmtOffset = user.tzInfo?.gmtString || '+00:00';
51
+ // Convert user's local date to UTC timestamps
52
+ // This ensures we capture the entire day from the user's perspective
53
+ const sinceWithOffset = `${since}T00:00:00${userGmtOffset}`;
54
+ const untilWithOffset = `${until}T23:59:59${userGmtOffset}`;
55
+ // Parse and convert to UTC
56
+ const sinceDateTime = new Date(sinceWithOffset).toISOString();
57
+ const untilDateTime = new Date(untilWithOffset).toISOString();
48
58
  const { items, nextCursor } = getBy === 'completion'
49
59
  ? await client.getCompletedTasksByCompletionDate({
50
60
  ...rest,
61
+ since: sinceDateTime,
62
+ until: untilDateTime,
51
63
  ...(labelsFilter ? { filterQuery: labelsFilter, filterLang: 'en' } : {}),
52
64
  })
53
65
  : await client.getCompletedTasksByDueDate({
54
66
  ...rest,
67
+ since: sinceDateTime,
68
+ until: untilDateTime,
55
69
  ...(labelsFilter ? { filterQuery: labelsFilter, filterLang: 'en' } : {}),
56
70
  });
57
71
  const mappedTasks = items.map(mapTask);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/todoist-ai",
3
- "version": "4.10.0",
3
+ "version": "4.11.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",