@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.
@@ -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" | "comment" | "project" | "section";
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" | "comment" | "project" | "section";
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: {