@doist/todoist-ai 4.13.4 → 4.14.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.
- package/dist/index.d.ts +109 -42
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +8 -1
- package/dist/tool-helpers.d.ts +18 -2
- package/dist/tool-helpers.d.ts.map +1 -1
- package/dist/tool-helpers.js +19 -1
- package/dist/tools/__tests__/find-activity.test.d.ts +2 -0
- package/dist/tools/__tests__/find-activity.test.d.ts.map +1 -0
- package/dist/tools/__tests__/find-activity.test.js +229 -0
- package/dist/tools/add-comments.d.ts +3 -3
- package/dist/tools/add-projects.d.ts +3 -3
- package/dist/tools/add-sections.d.ts +3 -3
- package/dist/tools/add-tasks.d.ts +6 -6
- package/dist/tools/delete-object.d.ts +2 -2
- package/dist/tools/find-activity.d.ts +69 -0
- package/dist/tools/find-activity.d.ts.map +1 -0
- package/dist/tools/find-activity.js +221 -0
- package/dist/tools/find-comments.d.ts +2 -2
- package/dist/tools/find-completed-tasks.d.ts +4 -4
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.js +2 -2
- package/dist/tools/find-tasks-by-date.d.ts +2 -2
- package/dist/tools/find-tasks.d.ts +4 -4
- package/dist/tools/update-projects.d.ts +3 -3
- package/dist/tools/update-tasks.d.ts +9 -9
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +4 -0
- package/dist/utils/tool-names.d.ts +1 -0
- package/dist/utils/tool-names.d.ts.map +1 -1
- package/dist/utils/tool-names.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { extractTextContent } from '../../utils/test-helpers.js';
|
|
3
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
4
|
+
import { findActivity } from '../find-activity.js';
|
|
5
|
+
// Mock the Todoist API
|
|
6
|
+
const mockTodoistApi = {
|
|
7
|
+
getActivityLogs: jest.fn(),
|
|
8
|
+
};
|
|
9
|
+
const { FIND_ACTIVITY } = ToolNames;
|
|
10
|
+
/**
|
|
11
|
+
* Helper to create a mock activity event
|
|
12
|
+
*/
|
|
13
|
+
function createMockActivityEvent(overrides = {}) {
|
|
14
|
+
return {
|
|
15
|
+
id: 'event-123',
|
|
16
|
+
objectType: 'task',
|
|
17
|
+
objectId: 'task-456',
|
|
18
|
+
eventType: 'added',
|
|
19
|
+
eventDate: '2024-10-23T10:30:00Z',
|
|
20
|
+
parentProjectId: 'project-789',
|
|
21
|
+
parentItemId: null,
|
|
22
|
+
initiatorId: 'user-001',
|
|
23
|
+
extraData: null,
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
describe(`${FIND_ACTIVITY} tool`, () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
jest.clearAllMocks();
|
|
30
|
+
});
|
|
31
|
+
describe('basic functionality', () => {
|
|
32
|
+
it('should retrieve activity events with default parameters', async () => {
|
|
33
|
+
const mockEvents = [
|
|
34
|
+
createMockActivityEvent({
|
|
35
|
+
id: 'event-1',
|
|
36
|
+
eventType: 'added',
|
|
37
|
+
eventDate: '2024-10-23T10:00:00Z',
|
|
38
|
+
}),
|
|
39
|
+
createMockActivityEvent({
|
|
40
|
+
id: 'event-2',
|
|
41
|
+
eventType: 'completed',
|
|
42
|
+
eventDate: '2024-10-23T11:00:00Z',
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
45
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
46
|
+
results: mockEvents,
|
|
47
|
+
nextCursor: null,
|
|
48
|
+
});
|
|
49
|
+
const result = await findActivity.execute({ limit: 20 }, mockTodoistApi);
|
|
50
|
+
expect(mockTodoistApi.getActivityLogs).toHaveBeenCalledWith({
|
|
51
|
+
limit: 20,
|
|
52
|
+
cursor: null,
|
|
53
|
+
});
|
|
54
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
55
|
+
});
|
|
56
|
+
it('should handle empty results', async () => {
|
|
57
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
58
|
+
results: [],
|
|
59
|
+
nextCursor: null,
|
|
60
|
+
});
|
|
61
|
+
const result = await findActivity.execute({ limit: 20 }, mockTodoistApi);
|
|
62
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
63
|
+
});
|
|
64
|
+
it('should handle pagination with cursor', async () => {
|
|
65
|
+
const mockEvents = Array.from({ length: 20 }, (_, i) => createMockActivityEvent({
|
|
66
|
+
id: `event-${i}`,
|
|
67
|
+
objectId: `task-${i}`,
|
|
68
|
+
}));
|
|
69
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
70
|
+
results: mockEvents,
|
|
71
|
+
nextCursor: 'next-page-cursor',
|
|
72
|
+
});
|
|
73
|
+
const result = await findActivity.execute({ limit: 20, cursor: 'current-cursor' }, mockTodoistApi);
|
|
74
|
+
expect(mockTodoistApi.getActivityLogs).toHaveBeenCalledWith({
|
|
75
|
+
limit: 20,
|
|
76
|
+
cursor: 'current-cursor',
|
|
77
|
+
});
|
|
78
|
+
expect(extractTextContent(result)).toContain('Pass cursor');
|
|
79
|
+
expect(extractTextContent(result)).toContain('next-page-cursor');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('filtering', () => {
|
|
83
|
+
it.each([
|
|
84
|
+
['task', 'added'],
|
|
85
|
+
['project', 'updated'],
|
|
86
|
+
['comment', 'deleted'],
|
|
87
|
+
])('should filter by object type: %s', async (objectType, eventType) => {
|
|
88
|
+
const mockEvents = [
|
|
89
|
+
createMockActivityEvent({
|
|
90
|
+
objectType: objectType,
|
|
91
|
+
eventType: eventType,
|
|
92
|
+
}),
|
|
93
|
+
];
|
|
94
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
95
|
+
results: mockEvents,
|
|
96
|
+
nextCursor: null,
|
|
97
|
+
});
|
|
98
|
+
const result = await findActivity.execute({ objectType: objectType, limit: 20 }, mockTodoistApi);
|
|
99
|
+
expect(mockTodoistApi.getActivityLogs).toHaveBeenCalledWith({
|
|
100
|
+
objectType,
|
|
101
|
+
limit: 20,
|
|
102
|
+
cursor: null,
|
|
103
|
+
});
|
|
104
|
+
expect(extractTextContent(result)).toContain(objectType);
|
|
105
|
+
});
|
|
106
|
+
it.each([
|
|
107
|
+
['added', 'task-1'],
|
|
108
|
+
['completed', 'task-2'],
|
|
109
|
+
['updated', 'task-3'],
|
|
110
|
+
['deleted', 'task-4'],
|
|
111
|
+
])('should filter by event type: %s', async (eventType, objectId) => {
|
|
112
|
+
const mockEvents = [
|
|
113
|
+
createMockActivityEvent({
|
|
114
|
+
eventType: eventType,
|
|
115
|
+
objectId,
|
|
116
|
+
}),
|
|
117
|
+
];
|
|
118
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
119
|
+
results: mockEvents,
|
|
120
|
+
nextCursor: null,
|
|
121
|
+
});
|
|
122
|
+
const result = await findActivity.execute({
|
|
123
|
+
eventType: eventType,
|
|
124
|
+
limit: 20,
|
|
125
|
+
}, mockTodoistApi);
|
|
126
|
+
expect(mockTodoistApi.getActivityLogs).toHaveBeenCalledWith({
|
|
127
|
+
eventType,
|
|
128
|
+
limit: 20,
|
|
129
|
+
cursor: null,
|
|
130
|
+
});
|
|
131
|
+
expect(extractTextContent(result)).toContain(eventType);
|
|
132
|
+
});
|
|
133
|
+
it.each([
|
|
134
|
+
['objectId', 'task-123', { objectId: 'task-123' }],
|
|
135
|
+
['projectId', 'project-abc', { parentProjectId: 'project-abc' }],
|
|
136
|
+
['taskId', 'parent-task-789', { parentItemId: 'parent-task-789' }],
|
|
137
|
+
['initiatorId', 'user-alice', { initiatorId: 'user-alice' }],
|
|
138
|
+
])('should filter by %s', async (filterName, filterId, expectedApiCall) => {
|
|
139
|
+
const mockEvents = [createMockActivityEvent()];
|
|
140
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
141
|
+
results: mockEvents,
|
|
142
|
+
nextCursor: null,
|
|
143
|
+
});
|
|
144
|
+
const args = { limit: 20 };
|
|
145
|
+
args[filterName] = filterId;
|
|
146
|
+
await findActivity.execute(args, mockTodoistApi);
|
|
147
|
+
expect(mockTodoistApi.getActivityLogs).toHaveBeenCalledWith({
|
|
148
|
+
...expectedApiCall,
|
|
149
|
+
limit: 20,
|
|
150
|
+
cursor: null,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
it('should support multiple filters simultaneously', async () => {
|
|
154
|
+
const mockEvents = [
|
|
155
|
+
createMockActivityEvent({
|
|
156
|
+
objectType: 'task',
|
|
157
|
+
eventType: 'completed',
|
|
158
|
+
parentProjectId: 'project-work',
|
|
159
|
+
initiatorId: 'user-bob',
|
|
160
|
+
}),
|
|
161
|
+
];
|
|
162
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
163
|
+
results: mockEvents,
|
|
164
|
+
nextCursor: null,
|
|
165
|
+
});
|
|
166
|
+
const result = await findActivity.execute({
|
|
167
|
+
objectType: 'task',
|
|
168
|
+
eventType: 'completed',
|
|
169
|
+
projectId: 'project-work',
|
|
170
|
+
initiatorId: 'user-bob',
|
|
171
|
+
limit: 50,
|
|
172
|
+
}, mockTodoistApi);
|
|
173
|
+
expect(mockTodoistApi.getActivityLogs).toHaveBeenCalledWith({
|
|
174
|
+
objectType: 'task',
|
|
175
|
+
eventType: 'completed',
|
|
176
|
+
parentProjectId: 'project-work',
|
|
177
|
+
initiatorId: 'user-bob',
|
|
178
|
+
limit: 50,
|
|
179
|
+
cursor: null,
|
|
180
|
+
});
|
|
181
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe('content extraction', () => {
|
|
185
|
+
it('should extract task content from extraData', async () => {
|
|
186
|
+
const mockEvents = [
|
|
187
|
+
createMockActivityEvent({
|
|
188
|
+
eventType: 'added',
|
|
189
|
+
extraData: { content: 'Buy groceries' },
|
|
190
|
+
}),
|
|
191
|
+
];
|
|
192
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
193
|
+
results: mockEvents,
|
|
194
|
+
nextCursor: null,
|
|
195
|
+
});
|
|
196
|
+
const result = await findActivity.execute({ limit: 20 }, mockTodoistApi);
|
|
197
|
+
expect(extractTextContent(result)).toContain('Buy groceries');
|
|
198
|
+
});
|
|
199
|
+
it('should handle system-generated events with no initiator', async () => {
|
|
200
|
+
const mockEvents = [
|
|
201
|
+
createMockActivityEvent({
|
|
202
|
+
initiatorId: null,
|
|
203
|
+
eventType: 'completed',
|
|
204
|
+
}),
|
|
205
|
+
];
|
|
206
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
207
|
+
results: mockEvents,
|
|
208
|
+
nextCursor: null,
|
|
209
|
+
});
|
|
210
|
+
const result = await findActivity.execute({ limit: 20 }, mockTodoistApi);
|
|
211
|
+
expect(extractTextContent(result)).toContain('system');
|
|
212
|
+
});
|
|
213
|
+
it('should truncate long content', async () => {
|
|
214
|
+
const longContent = 'A'.repeat(100);
|
|
215
|
+
const mockEvents = [
|
|
216
|
+
createMockActivityEvent({
|
|
217
|
+
extraData: { content: longContent },
|
|
218
|
+
}),
|
|
219
|
+
];
|
|
220
|
+
mockTodoistApi.getActivityLogs.mockResolvedValue({
|
|
221
|
+
results: mockEvents,
|
|
222
|
+
nextCursor: null,
|
|
223
|
+
});
|
|
224
|
+
const result = await findActivity.execute({ limit: 20 }, mockTodoistApi);
|
|
225
|
+
expect(extractTextContent(result)).toContain('...');
|
|
226
|
+
expect(extractTextContent(result)).not.toContain(longContent);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -9,19 +9,19 @@ declare const addComments: {
|
|
|
9
9
|
content: z.ZodString;
|
|
10
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
11
|
content: string;
|
|
12
|
-
taskId?: string | undefined;
|
|
13
12
|
projectId?: string | undefined;
|
|
13
|
+
taskId?: string | undefined;
|
|
14
14
|
}, {
|
|
15
15
|
content: string;
|
|
16
|
-
taskId?: string | undefined;
|
|
17
16
|
projectId?: string | undefined;
|
|
17
|
+
taskId?: string | undefined;
|
|
18
18
|
}>, "many">;
|
|
19
19
|
};
|
|
20
20
|
execute(args: {
|
|
21
21
|
comments: {
|
|
22
22
|
content: string;
|
|
23
|
-
taskId?: string | undefined;
|
|
24
23
|
projectId?: string | undefined;
|
|
24
|
+
taskId?: string | undefined;
|
|
25
25
|
}[];
|
|
26
26
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
27
27
|
content: {
|
|
@@ -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;
|
|
13
14
|
isFavorite?: boolean | undefined;
|
|
14
15
|
viewStyle?: "list" | "board" | "calendar" | undefined;
|
|
15
|
-
parentId?: string | undefined;
|
|
16
16
|
}, {
|
|
17
17
|
name: string;
|
|
18
|
+
parentId?: string | undefined;
|
|
18
19
|
isFavorite?: boolean | undefined;
|
|
19
20
|
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;
|
|
26
27
|
isFavorite?: boolean | undefined;
|
|
27
28
|
viewStyle?: "list" | "board" | "calendar" | undefined;
|
|
28
|
-
parentId?: string | undefined;
|
|
29
29
|
}[];
|
|
30
30
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
31
31
|
content: {
|
|
@@ -7,17 +7,17 @@ declare const addSections: {
|
|
|
7
7
|
name: z.ZodString;
|
|
8
8
|
projectId: z.ZodString;
|
|
9
9
|
}, "strip", z.ZodTypeAny, {
|
|
10
|
-
name: string;
|
|
11
10
|
projectId: string;
|
|
12
|
-
}, {
|
|
13
11
|
name: string;
|
|
12
|
+
}, {
|
|
14
13
|
projectId: string;
|
|
14
|
+
name: string;
|
|
15
15
|
}>, "many">;
|
|
16
16
|
};
|
|
17
17
|
execute({ sections }: {
|
|
18
18
|
sections: {
|
|
19
|
-
name: string;
|
|
20
19
|
projectId: string;
|
|
20
|
+
name: string;
|
|
21
21
|
}[];
|
|
22
22
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
23
23
|
content: {
|
|
@@ -17,24 +17,24 @@ declare const addTasks: {
|
|
|
17
17
|
responsibleUser: z.ZodOptional<z.ZodString>;
|
|
18
18
|
}, "strip", z.ZodTypeAny, {
|
|
19
19
|
content: string;
|
|
20
|
-
description?: string | undefined;
|
|
21
|
-
parentId?: string | undefined;
|
|
22
20
|
projectId?: string | undefined;
|
|
23
21
|
sectionId?: string | undefined;
|
|
22
|
+
parentId?: string | undefined;
|
|
24
23
|
labels?: string[] | undefined;
|
|
25
24
|
duration?: string | undefined;
|
|
26
25
|
priority?: "p1" | "p2" | "p3" | "p4" | undefined;
|
|
26
|
+
description?: string | undefined;
|
|
27
27
|
dueString?: string | undefined;
|
|
28
28
|
responsibleUser?: string | undefined;
|
|
29
29
|
}, {
|
|
30
30
|
content: string;
|
|
31
|
-
description?: string | undefined;
|
|
32
|
-
parentId?: string | undefined;
|
|
33
31
|
projectId?: string | undefined;
|
|
34
32
|
sectionId?: string | undefined;
|
|
33
|
+
parentId?: string | undefined;
|
|
35
34
|
labels?: string[] | undefined;
|
|
36
35
|
duration?: string | undefined;
|
|
37
36
|
priority?: "p1" | "p2" | "p3" | "p4" | undefined;
|
|
37
|
+
description?: string | undefined;
|
|
38
38
|
dueString?: string | undefined;
|
|
39
39
|
responsibleUser?: string | undefined;
|
|
40
40
|
}>, "many">;
|
|
@@ -42,13 +42,13 @@ declare const addTasks: {
|
|
|
42
42
|
execute({ tasks }: {
|
|
43
43
|
tasks: {
|
|
44
44
|
content: string;
|
|
45
|
-
description?: string | undefined;
|
|
46
|
-
parentId?: string | undefined;
|
|
47
45
|
projectId?: string | undefined;
|
|
48
46
|
sectionId?: string | undefined;
|
|
47
|
+
parentId?: string | undefined;
|
|
49
48
|
labels?: string[] | undefined;
|
|
50
49
|
duration?: string | undefined;
|
|
51
50
|
priority?: "p1" | "p2" | "p3" | "p4" | undefined;
|
|
51
|
+
description?: string | undefined;
|
|
52
52
|
dueString?: string | undefined;
|
|
53
53
|
responsibleUser?: string | undefined;
|
|
54
54
|
}[];
|
|
@@ -8,7 +8,7 @@ declare const deleteObject: {
|
|
|
8
8
|
};
|
|
9
9
|
execute(args: {
|
|
10
10
|
id: string;
|
|
11
|
-
type: "task" | "
|
|
11
|
+
type: "task" | "project" | "comment" | "section";
|
|
12
12
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
13
13
|
content: {
|
|
14
14
|
type: "text";
|
|
@@ -16,7 +16,7 @@ declare const deleteObject: {
|
|
|
16
16
|
}[];
|
|
17
17
|
structuredContent: {
|
|
18
18
|
deletedEntity: {
|
|
19
|
-
type: "task" | "
|
|
19
|
+
type: "task" | "project" | "comment" | "section";
|
|
20
20
|
id: string;
|
|
21
21
|
};
|
|
22
22
|
success: boolean;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const findActivity: {
|
|
3
|
+
name: "find-activity";
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
objectType: z.ZodOptional<z.ZodEnum<["task", "project", "comment"]>>;
|
|
7
|
+
objectId: z.ZodOptional<z.ZodString>;
|
|
8
|
+
eventType: z.ZodOptional<z.ZodEnum<["added", "updated", "deleted", "completed", "uncompleted", "archived", "unarchived", "shared", "left"]>>;
|
|
9
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
10
|
+
taskId: z.ZodOptional<z.ZodString>;
|
|
11
|
+
initiatorId: z.ZodOptional<z.ZodString>;
|
|
12
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
13
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
14
|
+
};
|
|
15
|
+
execute(args: {
|
|
16
|
+
limit: number;
|
|
17
|
+
projectId?: string | undefined;
|
|
18
|
+
objectType?: "task" | "project" | "comment" | undefined;
|
|
19
|
+
objectId?: string | undefined;
|
|
20
|
+
eventType?: "added" | "updated" | "deleted" | "completed" | "uncompleted" | "archived" | "unarchived" | "shared" | "left" | undefined;
|
|
21
|
+
initiatorId?: string | undefined;
|
|
22
|
+
cursor?: string | undefined;
|
|
23
|
+
taskId?: string | undefined;
|
|
24
|
+
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
25
|
+
content: {
|
|
26
|
+
type: "text";
|
|
27
|
+
text: string;
|
|
28
|
+
}[];
|
|
29
|
+
structuredContent: {
|
|
30
|
+
events: {
|
|
31
|
+
id: string | null;
|
|
32
|
+
objectType: string;
|
|
33
|
+
objectId: string;
|
|
34
|
+
eventType: string;
|
|
35
|
+
eventDate: string;
|
|
36
|
+
parentProjectId: string | null;
|
|
37
|
+
parentItemId: string | null;
|
|
38
|
+
initiatorId: string | null;
|
|
39
|
+
extraData: Record<string, any> | null;
|
|
40
|
+
}[];
|
|
41
|
+
nextCursor: string | null;
|
|
42
|
+
totalCount: number;
|
|
43
|
+
hasMore: boolean;
|
|
44
|
+
appliedFilters: {
|
|
45
|
+
limit: number;
|
|
46
|
+
projectId?: string | undefined;
|
|
47
|
+
objectType?: "task" | "project" | "comment" | undefined;
|
|
48
|
+
objectId?: string | undefined;
|
|
49
|
+
eventType?: "added" | "updated" | "deleted" | "completed" | "uncompleted" | "archived" | "unarchived" | "shared" | "left" | undefined;
|
|
50
|
+
initiatorId?: string | undefined;
|
|
51
|
+
cursor?: string | undefined;
|
|
52
|
+
taskId?: string | undefined;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
} | {
|
|
56
|
+
content: ({
|
|
57
|
+
type: "text";
|
|
58
|
+
text: string;
|
|
59
|
+
mimeType?: undefined;
|
|
60
|
+
} | {
|
|
61
|
+
type: "text";
|
|
62
|
+
mimeType: string;
|
|
63
|
+
text: string;
|
|
64
|
+
})[];
|
|
65
|
+
structuredContent?: undefined;
|
|
66
|
+
}>;
|
|
67
|
+
};
|
|
68
|
+
export { findActivity };
|
|
69
|
+
//# sourceMappingURL=find-activity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-activity.d.ts","sourceRoot":"","sources":["../../src/tools/find-activity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAwDvB,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CwB,CAAA;AA+J1C,OAAO,EAAE,YAAY,EAAE,CAAA"}
|
|
@@ -0,0 +1,221 @@
|
|
|
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)
|
|
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 };
|
|
@@ -10,10 +10,10 @@ declare const findComments: {
|
|
|
10
10
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
11
11
|
};
|
|
12
12
|
execute(args: {
|
|
13
|
-
limit?: number | undefined;
|
|
14
|
-
taskId?: string | undefined;
|
|
15
13
|
projectId?: string | undefined;
|
|
14
|
+
limit?: number | undefined;
|
|
16
15
|
cursor?: string | undefined;
|
|
16
|
+
taskId?: string | undefined;
|
|
17
17
|
commentId?: string | undefined;
|
|
18
18
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
19
19
|
content: {
|