@doist/todoist-ai 4.1.0 → 4.4.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 +246 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +2 -0
- package/dist/tools/__tests__/find-completed-tasks.test.js +136 -2
- package/dist/tools/__tests__/find-tasks-by-date.test.js +121 -2
- package/dist/tools/__tests__/find-tasks.test.js +257 -10
- package/dist/tools/__tests__/update-sections.test.js +1 -0
- package/dist/tools/__tests__/user-info.test.d.ts +2 -0
- package/dist/tools/__tests__/user-info.test.d.ts.map +1 -0
- package/dist/tools/__tests__/user-info.test.js +139 -0
- package/dist/tools/add-comments.d.ts +25 -2
- package/dist/tools/add-comments.d.ts.map +1 -1
- package/dist/tools/add-projects.d.ts +46 -2
- package/dist/tools/add-projects.d.ts.map +1 -1
- package/dist/tools/add-sections.d.ts +14 -2
- package/dist/tools/add-sections.d.ts.map +1 -1
- package/dist/tools/add-tasks.d.ts +3 -3
- package/dist/tools/find-comments.d.ts +25 -2
- package/dist/tools/find-comments.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.d.ts +8 -2
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.js +19 -3
- package/dist/tools/find-tasks-by-date.d.ts +6 -0
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.js +18 -1
- package/dist/tools/find-tasks.d.ts +6 -0
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/find-tasks.js +63 -9
- package/dist/tools/update-comments.d.ts +25 -2
- package/dist/tools/update-comments.d.ts.map +1 -1
- package/dist/tools/update-projects.d.ts +46 -2
- package/dist/tools/update-projects.d.ts.map +1 -1
- package/dist/tools/update-sections.d.ts +14 -2
- package/dist/tools/update-sections.d.ts.map +1 -1
- package/dist/tools/update-tasks.d.ts +3 -3
- package/dist/tools/user-info.d.ts +44 -0
- package/dist/tools/user-info.d.ts.map +1 -0
- package/dist/tools/user-info.js +142 -0
- package/dist/utils/labels.d.ts +10 -0
- package/dist/utils/labels.d.ts.map +1 -0
- package/dist/utils/labels.js +18 -0
- package/dist/utils/test-helpers.d.ts.map +1 -1
- package/dist/utils/test-helpers.js +1 -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 +1 -0
- package/package.json +3 -3
package/dist/tools/find-tasks.js
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { getToolOutput } from '../mcp-helpers.js';
|
|
3
3
|
import { getTasksByFilter, mapTask } from '../tool-helpers.js';
|
|
4
4
|
import { ApiLimits } from '../utils/constants.js';
|
|
5
|
+
import { LabelsSchema, generateLabelsFilter } from '../utils/labels.js';
|
|
5
6
|
import { generateTaskNextSteps, getDateString, previewTasks, summarizeList, } from '../utils/response-builders.js';
|
|
6
7
|
import { ToolNames } from '../utils/tool-names.js';
|
|
7
8
|
const { FIND_COMPLETED_TASKS, ADD_TASKS } = ToolNames;
|
|
@@ -21,16 +22,18 @@ const ArgsSchema = {
|
|
|
21
22
|
.string()
|
|
22
23
|
.optional()
|
|
23
24
|
.describe('The cursor to get the next page of tasks (cursor is obtained from the previous call to this tool, with the same parameters).'),
|
|
25
|
+
...LabelsSchema,
|
|
24
26
|
};
|
|
25
27
|
const findTasks = {
|
|
26
28
|
name: ToolNames.FIND_TASKS,
|
|
27
29
|
description: 'Find tasks by text search, or by project/section/parent container. At least one filter must be provided.',
|
|
28
30
|
parameters: ArgsSchema,
|
|
29
31
|
async execute(args, client) {
|
|
30
|
-
const { searchText, projectId, sectionId, parentId, limit, cursor } = args;
|
|
32
|
+
const { searchText, projectId, sectionId, parentId, limit, cursor, labels, labelsOperator, } = args;
|
|
31
33
|
// Validate at least one filter is provided
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
const hasLabels = labels && labels.length > 0;
|
|
35
|
+
if (!searchText && !projectId && !sectionId && !parentId && !hasLabels) {
|
|
36
|
+
throw new Error('At least one filter must be provided: searchText, projectId, sectionId, parentId, or labels');
|
|
34
37
|
}
|
|
35
38
|
// If using container-based filtering, use direct API
|
|
36
39
|
if (projectId || sectionId || parentId) {
|
|
@@ -47,10 +50,17 @@ const findTasks = {
|
|
|
47
50
|
const { results, nextCursor } = await client.getTasks(taskParams);
|
|
48
51
|
const mappedTasks = results.map(mapTask);
|
|
49
52
|
// If also has searchText, filter the results
|
|
50
|
-
|
|
53
|
+
let finalTasks = searchText
|
|
51
54
|
? mappedTasks.filter((task) => task.content.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
52
55
|
task.description?.toLowerCase().includes(searchText.toLowerCase()))
|
|
53
56
|
: mappedTasks;
|
|
57
|
+
// If labels have also been provided, filter the results for those
|
|
58
|
+
if (labels && labels.length > 0) {
|
|
59
|
+
finalTasks =
|
|
60
|
+
labelsOperator === 'and'
|
|
61
|
+
? finalTasks.filter((task) => labels.every((label) => task.labels.includes(label)))
|
|
62
|
+
: finalTasks.filter((task) => labels.some((label) => task.labels.includes(label)));
|
|
63
|
+
}
|
|
54
64
|
const textContent = generateTextContent({
|
|
55
65
|
tasks: finalTasks,
|
|
56
66
|
args,
|
|
@@ -68,10 +78,26 @@ const findTasks = {
|
|
|
68
78
|
},
|
|
69
79
|
});
|
|
70
80
|
}
|
|
71
|
-
//
|
|
81
|
+
// Handle search text and/or labels using filter query
|
|
82
|
+
let query = '';
|
|
83
|
+
// Add search text component
|
|
84
|
+
if (searchText) {
|
|
85
|
+
query = `search: ${searchText}`;
|
|
86
|
+
}
|
|
87
|
+
// Add labels component
|
|
88
|
+
const labelsFilter = generateLabelsFilter(labels, labelsOperator);
|
|
89
|
+
if (labelsFilter.length > 0) {
|
|
90
|
+
if (query.length > 0) {
|
|
91
|
+
query += ` & ${labelsFilter}`;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
query = labelsFilter;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Execute filter query
|
|
72
98
|
const result = await getTasksByFilter({
|
|
73
99
|
client,
|
|
74
|
-
query
|
|
100
|
+
query,
|
|
75
101
|
cursor: args.cursor,
|
|
76
102
|
limit: args.limit,
|
|
77
103
|
});
|
|
@@ -146,15 +172,43 @@ function generateTextContent({ tasks, args, nextCursor, isContainerSearch, }) {
|
|
|
146
172
|
subject += ` matching "${args.searchText}"`;
|
|
147
173
|
filterHints.push(`containing "${args.searchText}"`);
|
|
148
174
|
}
|
|
175
|
+
// Add label filter information
|
|
176
|
+
if (args.labels && args.labels.length > 0) {
|
|
177
|
+
const labelText = args.labels
|
|
178
|
+
.map((label) => `@${label}`)
|
|
179
|
+
.join(args.labelsOperator === 'and' ? ' & ' : ' | ');
|
|
180
|
+
filterHints.push(`labels: ${labelText}`);
|
|
181
|
+
}
|
|
149
182
|
// Container-specific zero result hints
|
|
150
183
|
if (tasks.length === 0) {
|
|
151
184
|
zeroReasonHints.push(...getContainerZeroReasonHints(args));
|
|
152
185
|
}
|
|
153
186
|
}
|
|
154
187
|
else {
|
|
155
|
-
// Text-only search
|
|
156
|
-
|
|
157
|
-
|
|
188
|
+
// Text-only search or labels-only search
|
|
189
|
+
if (args.searchText) {
|
|
190
|
+
subject = `Search results for "${args.searchText}"`;
|
|
191
|
+
filterHints.push(`matching "${args.searchText}"`);
|
|
192
|
+
}
|
|
193
|
+
else if (args.labels && args.labels.length > 0) {
|
|
194
|
+
// Labels-only search
|
|
195
|
+
const labelText = args.labels
|
|
196
|
+
.map((label) => `@${label}`)
|
|
197
|
+
.join(args.labelsOperator === 'and' ? ' & ' : ' | ');
|
|
198
|
+
subject = `Tasks with labels: ${labelText}`;
|
|
199
|
+
filterHints.push(`labels: ${labelText}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Fallback (shouldn't happen given validation)
|
|
203
|
+
subject = 'Tasks';
|
|
204
|
+
}
|
|
205
|
+
// Add label filter information for text search with labels
|
|
206
|
+
if (args.searchText && args.labels && args.labels.length > 0) {
|
|
207
|
+
const labelText = args.labels
|
|
208
|
+
.map((label) => `@${label}`)
|
|
209
|
+
.join(args.labelsOperator === 'and' ? ' & ' : ' | ');
|
|
210
|
+
filterHints.push(`labels: ${labelText}`);
|
|
211
|
+
}
|
|
158
212
|
if (tasks.length === 0) {
|
|
159
213
|
zeroReasonHints.push('Try broader search terms');
|
|
160
214
|
zeroReasonHints.push(`Check completed tasks with ${FIND_COMPLETED_TASKS}`);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Comment } from '@doist/todoist-api-typescript';
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
declare const updateComments: {
|
|
4
3
|
name: "update-comments";
|
|
@@ -26,7 +25,31 @@ declare const updateComments: {
|
|
|
26
25
|
text: string;
|
|
27
26
|
}[];
|
|
28
27
|
structuredContent: {
|
|
29
|
-
comments:
|
|
28
|
+
comments: {
|
|
29
|
+
taskId: string | undefined;
|
|
30
|
+
id: string;
|
|
31
|
+
content: string;
|
|
32
|
+
postedAt: string;
|
|
33
|
+
fileAttachment: {
|
|
34
|
+
resourceType: string;
|
|
35
|
+
fileName?: string | null | undefined;
|
|
36
|
+
fileSize?: number | null | undefined;
|
|
37
|
+
fileType?: string | null | undefined;
|
|
38
|
+
fileUrl?: string | null | undefined;
|
|
39
|
+
fileDuration?: number | null | undefined;
|
|
40
|
+
uploadState?: "pending" | "completed" | null | undefined;
|
|
41
|
+
image?: string | null | undefined;
|
|
42
|
+
imageWidth?: number | null | undefined;
|
|
43
|
+
imageHeight?: number | null | undefined;
|
|
44
|
+
url?: string | null | undefined;
|
|
45
|
+
title?: string | null | undefined;
|
|
46
|
+
} | null;
|
|
47
|
+
postedUid: string;
|
|
48
|
+
uidsToNotify: string[] | null;
|
|
49
|
+
reactions: Record<string, string[]> | null;
|
|
50
|
+
isDeleted: boolean;
|
|
51
|
+
projectId?: string | undefined;
|
|
52
|
+
}[];
|
|
30
53
|
totalCount: number;
|
|
31
54
|
updatedCommentIds: string[];
|
|
32
55
|
appliedOperations: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-comments.d.ts","sourceRoot":"","sources":["../../src/tools/update-comments.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"update-comments.d.ts","sourceRoot":"","sources":["../../src/tools/update-comments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAyF88V,CAAC;4BAA6C,CAAC;4BAA6C,CAAC;2BAA4C,CAAC;gCAAiD,CAAC;+BAAgD,CAAC;yBAA2D,CAAC;8BAA+C,CAAC;+BAAgD,CAAC;uBAAwC,CAAC;yBAA0C,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;CA5Dt5W,CAAA;AA2D1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { PersonalProject, WorkspaceProject } from '@doist/todoist-api-typescript';
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
declare const updateProjects: {
|
|
4
3
|
name: "update-projects";
|
|
@@ -34,7 +33,52 @@ declare const updateProjects: {
|
|
|
34
33
|
text: string;
|
|
35
34
|
}[];
|
|
36
35
|
structuredContent: {
|
|
37
|
-
projects: (
|
|
36
|
+
projects: ({
|
|
37
|
+
url: string;
|
|
38
|
+
id: string;
|
|
39
|
+
canAssignTasks: boolean;
|
|
40
|
+
childOrder: number;
|
|
41
|
+
color: string;
|
|
42
|
+
createdAt: string | null;
|
|
43
|
+
isArchived: boolean;
|
|
44
|
+
isDeleted: boolean;
|
|
45
|
+
isFavorite: boolean;
|
|
46
|
+
isFrozen: boolean;
|
|
47
|
+
name: string;
|
|
48
|
+
updatedAt: string | null;
|
|
49
|
+
viewStyle: string;
|
|
50
|
+
defaultOrder: number;
|
|
51
|
+
description: string;
|
|
52
|
+
isCollapsed: boolean;
|
|
53
|
+
isShared: boolean;
|
|
54
|
+
parentId: string | null;
|
|
55
|
+
inboxProject: boolean;
|
|
56
|
+
} | {
|
|
57
|
+
url: string;
|
|
58
|
+
id: string;
|
|
59
|
+
canAssignTasks: boolean;
|
|
60
|
+
childOrder: number;
|
|
61
|
+
color: string;
|
|
62
|
+
createdAt: string | null;
|
|
63
|
+
isArchived: boolean;
|
|
64
|
+
isDeleted: boolean;
|
|
65
|
+
isFavorite: boolean;
|
|
66
|
+
isFrozen: boolean;
|
|
67
|
+
name: string;
|
|
68
|
+
updatedAt: string | null;
|
|
69
|
+
viewStyle: string;
|
|
70
|
+
defaultOrder: number;
|
|
71
|
+
description: string;
|
|
72
|
+
isCollapsed: boolean;
|
|
73
|
+
isShared: boolean;
|
|
74
|
+
collaboratorRoleDefault: string;
|
|
75
|
+
folderId: string | null;
|
|
76
|
+
isInviteOnly: boolean | null;
|
|
77
|
+
isLinkSharingEnabled: boolean;
|
|
78
|
+
role: string | null;
|
|
79
|
+
status: string;
|
|
80
|
+
workspaceId: string;
|
|
81
|
+
})[];
|
|
38
82
|
totalCount: number;
|
|
39
83
|
updatedProjectIds: string[];
|
|
40
84
|
appliedOperations: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-projects.d.ts","sourceRoot":"","sources":["../../src/tools/update-projects.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"update-projects.d.ts","sourceRoot":"","sources":["../../src/tools/update-projects.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAqBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCsB,CAAA;AAuD1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Section } from '@doist/todoist-api-typescript';
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
declare const updateSections: {
|
|
4
3
|
name: "update-sections";
|
|
@@ -26,7 +25,20 @@ declare const updateSections: {
|
|
|
26
25
|
text: string;
|
|
27
26
|
}[];
|
|
28
27
|
structuredContent: {
|
|
29
|
-
sections:
|
|
28
|
+
sections: {
|
|
29
|
+
url: string;
|
|
30
|
+
id: string;
|
|
31
|
+
userId: string;
|
|
32
|
+
projectId: string;
|
|
33
|
+
addedAt: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
archivedAt: string | null;
|
|
36
|
+
name: string;
|
|
37
|
+
sectionOrder: number;
|
|
38
|
+
isArchived: boolean;
|
|
39
|
+
isDeleted: boolean;
|
|
40
|
+
isCollapsed: boolean;
|
|
41
|
+
}[];
|
|
30
42
|
totalCount: number;
|
|
31
43
|
updatedSectionIds: string[];
|
|
32
44
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-sections.d.ts","sourceRoot":"","sources":["../../src/tools/update-sections.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"update-sections.d.ts","sourceRoot":"","sources":["../../src/tools/update-sections.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsBsB,CAAA;AAsD1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -21,9 +21,9 @@ declare const updateTasks: {
|
|
|
21
21
|
parentId?: string | undefined;
|
|
22
22
|
projectId?: string | undefined;
|
|
23
23
|
sectionId?: string | undefined;
|
|
24
|
+
duration?: string | undefined;
|
|
24
25
|
priority?: number | undefined;
|
|
25
26
|
dueString?: string | undefined;
|
|
26
|
-
duration?: string | undefined;
|
|
27
27
|
order?: number | undefined;
|
|
28
28
|
}, {
|
|
29
29
|
id: string;
|
|
@@ -32,9 +32,9 @@ declare const updateTasks: {
|
|
|
32
32
|
parentId?: string | undefined;
|
|
33
33
|
projectId?: string | undefined;
|
|
34
34
|
sectionId?: string | undefined;
|
|
35
|
+
duration?: string | undefined;
|
|
35
36
|
priority?: number | undefined;
|
|
36
37
|
dueString?: string | undefined;
|
|
37
|
-
duration?: string | undefined;
|
|
38
38
|
order?: number | undefined;
|
|
39
39
|
}>, "many">;
|
|
40
40
|
};
|
|
@@ -46,9 +46,9 @@ declare const updateTasks: {
|
|
|
46
46
|
parentId?: string | undefined;
|
|
47
47
|
projectId?: string | undefined;
|
|
48
48
|
sectionId?: string | undefined;
|
|
49
|
+
duration?: string | undefined;
|
|
49
50
|
priority?: number | undefined;
|
|
50
51
|
dueString?: string | undefined;
|
|
51
|
-
duration?: string | undefined;
|
|
52
52
|
order?: number | undefined;
|
|
53
53
|
}[];
|
|
54
54
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { TodoistApi } from '@doist/todoist-api-typescript';
|
|
2
|
+
type UserPlan = 'Todoist Free' | 'Todoist Pro' | 'Todoist Business';
|
|
3
|
+
type UserInfoStructured = Record<string, unknown> & {
|
|
4
|
+
type: 'user_info';
|
|
5
|
+
userId: string;
|
|
6
|
+
fullName: string;
|
|
7
|
+
timezone: string;
|
|
8
|
+
currentLocalTime: string;
|
|
9
|
+
startDay: number;
|
|
10
|
+
startDayName: string;
|
|
11
|
+
weekStartDate: string;
|
|
12
|
+
weekEndDate: string;
|
|
13
|
+
currentWeekNumber: number;
|
|
14
|
+
completedToday: number;
|
|
15
|
+
dailyGoal: number;
|
|
16
|
+
weeklyGoal: number;
|
|
17
|
+
email: string;
|
|
18
|
+
plan: UserPlan;
|
|
19
|
+
};
|
|
20
|
+
declare const userInfo: {
|
|
21
|
+
name: "user-info";
|
|
22
|
+
description: string;
|
|
23
|
+
parameters: {};
|
|
24
|
+
execute(_args: {}, client: TodoistApi): Promise<{
|
|
25
|
+
content: {
|
|
26
|
+
type: "text";
|
|
27
|
+
text: string;
|
|
28
|
+
}[];
|
|
29
|
+
structuredContent: UserInfoStructured;
|
|
30
|
+
} | {
|
|
31
|
+
content: ({
|
|
32
|
+
type: "text";
|
|
33
|
+
text: string;
|
|
34
|
+
mimeType?: undefined;
|
|
35
|
+
} | {
|
|
36
|
+
type: "text";
|
|
37
|
+
mimeType: string;
|
|
38
|
+
text: string;
|
|
39
|
+
})[];
|
|
40
|
+
structuredContent?: undefined;
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
43
|
+
export { userInfo, type UserInfoStructured };
|
|
44
|
+
//# sourceMappingURL=user-info.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-info.d.ts","sourceRoot":"","sources":["../../src/tools/user-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAO/D,KAAK,QAAQ,GAAG,cAAc,GAAG,aAAa,GAAG,kBAAkB,CAAA;AAEnE,KAAK,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAChD,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,CAAA;CACjB,CAAA;AAoJD,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;CAa4B,CAAA;AAE1C,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,CAAA"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { getToolOutput } from '../mcp-helpers.js';
|
|
2
|
+
import { ToolNames } from '../utils/tool-names.js';
|
|
3
|
+
const ArgsSchema = {};
|
|
4
|
+
function getUserPlan(user) {
|
|
5
|
+
if (user.businessAccountId) {
|
|
6
|
+
return 'Todoist Business';
|
|
7
|
+
}
|
|
8
|
+
if (user.isPremium) {
|
|
9
|
+
return 'Todoist Pro';
|
|
10
|
+
}
|
|
11
|
+
return 'Todoist Free';
|
|
12
|
+
}
|
|
13
|
+
// Helper functions for date and time calculations
|
|
14
|
+
function getWeekStartDate(date, startDay) {
|
|
15
|
+
const currentDay = date.getDay() || 7; // Convert Sunday (0) to 7 for ISO format
|
|
16
|
+
const daysFromStart = (currentDay - startDay + 7) % 7;
|
|
17
|
+
const weekStart = new Date(date);
|
|
18
|
+
weekStart.setDate(date.getDate() - daysFromStart);
|
|
19
|
+
return weekStart;
|
|
20
|
+
}
|
|
21
|
+
function getWeekEndDate(weekStart) {
|
|
22
|
+
const weekEnd = new Date(weekStart);
|
|
23
|
+
weekEnd.setDate(weekStart.getDate() + 6);
|
|
24
|
+
return weekEnd;
|
|
25
|
+
}
|
|
26
|
+
function getWeekNumber(date) {
|
|
27
|
+
const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
|
|
28
|
+
const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000;
|
|
29
|
+
return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
|
|
30
|
+
}
|
|
31
|
+
function getDayName(dayNumber) {
|
|
32
|
+
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
33
|
+
// Convert ISO day number (1=Monday, 7=Sunday) to array index (0=Sunday, 6=Saturday)
|
|
34
|
+
const index = dayNumber === 7 ? 0 : dayNumber;
|
|
35
|
+
return days[index] ?? 'Unknown';
|
|
36
|
+
}
|
|
37
|
+
function formatDate(date) {
|
|
38
|
+
return date.toISOString().split('T')[0] ?? '';
|
|
39
|
+
}
|
|
40
|
+
function isValidTimezone(timezone) {
|
|
41
|
+
try {
|
|
42
|
+
// Test if the timezone is valid by attempting to format a date with it
|
|
43
|
+
new Intl.DateTimeFormat('en-US', { timeZone: timezone });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function getSafeTimezone(timezone) {
|
|
51
|
+
return isValidTimezone(timezone) ? timezone : 'UTC';
|
|
52
|
+
}
|
|
53
|
+
function formatLocalTime(date, timezone) {
|
|
54
|
+
const safeTimezone = getSafeTimezone(timezone);
|
|
55
|
+
return date.toLocaleString('en-US', {
|
|
56
|
+
timeZone: safeTimezone,
|
|
57
|
+
year: 'numeric',
|
|
58
|
+
month: '2-digit',
|
|
59
|
+
day: '2-digit',
|
|
60
|
+
hour: '2-digit',
|
|
61
|
+
minute: '2-digit',
|
|
62
|
+
second: '2-digit',
|
|
63
|
+
hour12: false,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function generateUserInfo(client) {
|
|
67
|
+
// Get user information from Todoist API
|
|
68
|
+
const user = await client.getUser();
|
|
69
|
+
// Parse timezone from user data and ensure it's valid
|
|
70
|
+
const rawTimezone = user.tzInfo?.timezone ?? 'UTC';
|
|
71
|
+
const timezone = getSafeTimezone(rawTimezone);
|
|
72
|
+
// Get current time in user's timezone
|
|
73
|
+
const now = new Date();
|
|
74
|
+
const localTime = formatLocalTime(now, timezone);
|
|
75
|
+
// Calculate week information based on user's start day
|
|
76
|
+
const startDay = user.startDay ?? 1; // Default to Monday if not set
|
|
77
|
+
const startDayName = getDayName(startDay);
|
|
78
|
+
// Determine user's plan
|
|
79
|
+
const plan = getUserPlan(user);
|
|
80
|
+
// Create a date object in user's timezone for accurate week calculations
|
|
81
|
+
const userDate = new Date(now.toLocaleString('en-US', { timeZone: timezone }));
|
|
82
|
+
const weekStart = getWeekStartDate(userDate, startDay);
|
|
83
|
+
const weekEnd = getWeekEndDate(weekStart);
|
|
84
|
+
const weekNumber = getWeekNumber(userDate);
|
|
85
|
+
// Generate markdown text content
|
|
86
|
+
const lines = [
|
|
87
|
+
'# User Information',
|
|
88
|
+
'',
|
|
89
|
+
`**User ID:** ${user.id}`,
|
|
90
|
+
`**Full Name:** ${user.fullName}`,
|
|
91
|
+
`**Email:** ${user.email}`,
|
|
92
|
+
`**Timezone:** ${timezone}`,
|
|
93
|
+
`**Current Local Time:** ${localTime}`,
|
|
94
|
+
'',
|
|
95
|
+
'## Week Settings',
|
|
96
|
+
`**Week Start Day:** ${startDayName} (${startDay})`,
|
|
97
|
+
`**Current Week:** Week ${weekNumber}`,
|
|
98
|
+
`**Week Start Date:** ${formatDate(weekStart)}`,
|
|
99
|
+
`**Week End Date:** ${formatDate(weekEnd)}`,
|
|
100
|
+
'',
|
|
101
|
+
'## Daily Progress',
|
|
102
|
+
`**Completed Today:** ${user.completedToday}`,
|
|
103
|
+
`**Daily Goal:** ${user.dailyGoal}`,
|
|
104
|
+
`**Weekly Goal:** ${user.weeklyGoal}`,
|
|
105
|
+
'',
|
|
106
|
+
'## Account Info',
|
|
107
|
+
`**Plan:** ${plan}`,
|
|
108
|
+
];
|
|
109
|
+
const textContent = lines.join('\n');
|
|
110
|
+
// Generate structured content
|
|
111
|
+
const structuredContent = {
|
|
112
|
+
type: 'user_info',
|
|
113
|
+
userId: user.id,
|
|
114
|
+
fullName: user.fullName,
|
|
115
|
+
timezone: timezone,
|
|
116
|
+
currentLocalTime: localTime,
|
|
117
|
+
startDay: startDay,
|
|
118
|
+
startDayName: startDayName,
|
|
119
|
+
weekStartDate: formatDate(weekStart),
|
|
120
|
+
weekEndDate: formatDate(weekEnd),
|
|
121
|
+
currentWeekNumber: weekNumber,
|
|
122
|
+
completedToday: user.completedToday,
|
|
123
|
+
dailyGoal: user.dailyGoal,
|
|
124
|
+
weeklyGoal: user.weeklyGoal,
|
|
125
|
+
email: user.email,
|
|
126
|
+
plan: plan,
|
|
127
|
+
};
|
|
128
|
+
return { textContent, structuredContent };
|
|
129
|
+
}
|
|
130
|
+
const userInfo = {
|
|
131
|
+
name: ToolNames.USER_INFO,
|
|
132
|
+
description: 'Get comprehensive user information including user ID, full name, email, timezone with current local time, week start day preferences, current week dates, daily/weekly goal progress, and user plan (Free/Pro/Business).',
|
|
133
|
+
parameters: ArgsSchema,
|
|
134
|
+
async execute(_args, client) {
|
|
135
|
+
const result = await generateUserInfo(client);
|
|
136
|
+
return getToolOutput({
|
|
137
|
+
textContent: result.textContent,
|
|
138
|
+
structuredContent: result.structuredContent,
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
export { userInfo };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const LABELS_OPERATORS: readonly ["and", "or"];
|
|
3
|
+
type LabelsOperator = (typeof LABELS_OPERATORS)[number];
|
|
4
|
+
export declare const LabelsSchema: {
|
|
5
|
+
labels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
6
|
+
labelsOperator: z.ZodOptional<z.ZodEnum<["and", "or"]>>;
|
|
7
|
+
};
|
|
8
|
+
export declare function generateLabelsFilter(labels?: string[], labelsOperator?: LabelsOperator): string;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=labels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../../src/utils/labels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,QAAA,MAAM,gBAAgB,wBAAyB,CAAA;AAC/C,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvD,eAAO,MAAM,YAAY;;;CAQxB,CAAA;AAED,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,MAAM,EAAO,EAAE,cAAc,GAAE,cAAqB,UAOhG"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const LABELS_OPERATORS = ['and', 'or'];
|
|
3
|
+
export const LabelsSchema = {
|
|
4
|
+
labels: z.string().array().optional().describe('The labels to filter the tasks by'),
|
|
5
|
+
labelsOperator: z
|
|
6
|
+
.enum(LABELS_OPERATORS)
|
|
7
|
+
.optional()
|
|
8
|
+
.describe('The operator to use when filtering by labels. This will dictate whether a task has all labels, or some of them. Default is "or".'),
|
|
9
|
+
};
|
|
10
|
+
export function generateLabelsFilter(labels = [], labelsOperator = 'or') {
|
|
11
|
+
if (labels.length === 0)
|
|
12
|
+
return '';
|
|
13
|
+
const operator = labelsOperator === 'and' ? ' & ' : ' | ';
|
|
14
|
+
// Add @ prefix to labels for Todoist API query
|
|
15
|
+
const prefixedLabels = labels.map((label) => (label.startsWith('@') ? label : `@${label}`));
|
|
16
|
+
const labelStr = prefixedLabels.join(` ${operator} `);
|
|
17
|
+
return `(${labelStr})`;
|
|
18
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAAA;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,GAAE,OAAO,CAAC,IAAI,CAAM,GAAG,IAAI,CA8BlE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,OAAO,CAAM,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAAA;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,GAAE,OAAO,CAAC,IAAI,CAAM,GAAG,IAAI,CA8BlE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,OAAO,CAAM,GAAG,OAAO,CAgB3E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,eAAe,CAAM,GAAG,eAAe,CAuB3F;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACnC,OAAO,EAAE,CAAC,EAAE,EACZ,UAAU,GAAE,MAAM,GAAG,IAAW,GACjC;IACC,OAAO,EAAE,CAAC,EAAE,CAAA;IACZ,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B,CAKA;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,UAAU,CAehF;AAED;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;CAKd,CAAA;AAEV;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAC1C,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC;UAAjC,MAAM;WAAS,CAAC;eAAa,CAAC;IAGtD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,CAqB9D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,GACzC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB;AAED;;GAEG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;CAUX,CAAA;AAEV;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAG,YAAqB,CAAA"}
|
|
@@ -23,6 +23,7 @@ export declare const ToolNames: {
|
|
|
23
23
|
readonly FIND_COMMENTS: "find-comments";
|
|
24
24
|
readonly GET_OVERVIEW: "get-overview";
|
|
25
25
|
readonly DELETE_OBJECT: "delete-object";
|
|
26
|
+
readonly USER_INFO: "user-info";
|
|
26
27
|
};
|
|
27
28
|
export type ToolName = (typeof ToolNames)[keyof typeof ToolNames];
|
|
28
29
|
//# sourceMappingURL=tool-names.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-names.d.ts","sourceRoot":"","sources":["../../src/utils/tool-names.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,SAAS
|
|
1
|
+
{"version":3,"file":"tool-names.d.ts","sourceRoot":"","sources":["../../src/utils/tool-names.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;CA4BZ,CAAA;AAGV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAA"}
|
package/dist/utils/tool-names.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/todoist-ai",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"prepare": "husky"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@doist/todoist-api-typescript": "5.
|
|
47
|
+
"@doist/todoist-api-typescript": "5.4.0",
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.11.1",
|
|
49
49
|
"date-fns": "^4.1.0",
|
|
50
50
|
"dotenv": "^16.5.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"concurrently": "^8.2.2",
|
|
60
60
|
"express": "^4.19.2",
|
|
61
61
|
"husky": "^9.1.7",
|
|
62
|
-
"jest": "30.
|
|
62
|
+
"jest": "30.1.3",
|
|
63
63
|
"lint-staged": "^16.0.0",
|
|
64
64
|
"morgan": "^1.10.0",
|
|
65
65
|
"nodemon": "^3.1.10",
|