@doist/todoist-ai 4.15.1 → 4.16.1
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/filter-helpers.d.ts +1 -1
- package/dist/index.d.ts +175 -175
- package/dist/index.js +61 -81
- package/dist/main.js +15 -23
- package/dist/mcp-helpers.d.ts +4 -4
- package/dist/mcp-server-6tm7Rhyz.js +2840 -0
- package/dist/todoist-tool.d.ts +2 -2
- package/dist/tool-helpers.d.ts +1 -1
- package/dist/tools/add-comments.d.ts +1 -1
- package/dist/tools/add-comments.d.ts.map +1 -1
- package/dist/tools/add-projects.d.ts +4 -4
- package/dist/tools/add-projects.d.ts.map +1 -1
- package/dist/tools/add-sections.d.ts +1 -1
- package/dist/tools/add-sections.d.ts.map +1 -1
- package/dist/tools/add-tasks.d.ts +4 -4
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/complete-tasks.d.ts +1 -1
- package/dist/tools/complete-tasks.d.ts.map +1 -1
- package/dist/tools/delete-object.d.ts +3 -3
- package/dist/tools/delete-object.d.ts.map +1 -1
- package/dist/tools/fetch.d.ts +1 -1
- package/dist/tools/find-activity.d.ts +5 -5
- package/dist/tools/find-activity.d.ts.map +1 -1
- package/dist/tools/find-comments.d.ts +2 -2
- package/dist/tools/find-comments.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.d.ts +3 -3
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-project-collaborators.d.ts +2 -2
- package/dist/tools/find-projects.d.ts +1 -1
- package/dist/tools/find-projects.d.ts.map +1 -1
- package/dist/tools/find-sections.d.ts +1 -1
- package/dist/tools/find-sections.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.d.ts +1 -1
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks.d.ts +3 -3
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/get-overview.d.ts +1 -1
- package/dist/tools/manage-assignments.d.ts +1 -1
- package/dist/tools/search.d.ts +1 -1
- package/dist/tools/update-comments.d.ts +4 -4
- package/dist/tools/update-comments.d.ts.map +1 -1
- package/dist/tools/update-projects.d.ts +1 -1
- package/dist/tools/update-projects.d.ts.map +1 -1
- package/dist/tools/update-sections.d.ts +4 -4
- package/dist/tools/update-sections.d.ts.map +1 -1
- package/dist/tools/update-tasks.d.ts +7 -7
- package/dist/tools/update-tasks.d.ts.map +1 -1
- package/dist/tools/user-info.d.ts +1 -1
- package/dist/utils/assignment-validator.d.ts +2 -2
- package/dist/utils/response-builders.d.ts +1 -3
- package/dist/utils/response-builders.d.ts.map +1 -1
- package/dist/utils/test-helpers.d.ts +1 -1
- package/dist/utils/user-resolver.d.ts +1 -1
- package/package.json +11 -9
- package/dist/filter-helpers.js +0 -79
- package/dist/mcp-helpers.js +0 -71
- package/dist/mcp-server.js +0 -142
- package/dist/todoist-tool.js +0 -1
- package/dist/tool-helpers.js +0 -125
- package/dist/tool-helpers.test.d.ts +0 -2
- package/dist/tool-helpers.test.d.ts.map +0 -1
- package/dist/tool-helpers.test.js +0 -223
- package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-comments.test.js +0 -241
- package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-projects.test.js +0 -174
- package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-sections.test.js +0 -185
- package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-tasks.test.js +0 -533
- package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
- package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
- package/dist/tools/__tests__/assignment-integration.test.js +0 -428
- package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/complete-tasks.test.js +0 -206
- package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
- package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
- package/dist/tools/__tests__/delete-object.test.js +0 -110
- package/dist/tools/__tests__/fetch.test.d.ts +0 -2
- package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
- package/dist/tools/__tests__/fetch.test.js +0 -279
- package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
- package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-activity.test.js +0 -229
- package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-comments.test.js +0 -236
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-completed-tasks.test.js +0 -324
- package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-projects.test.js +0 -154
- package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-sections.test.js +0 -245
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
- package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks.test.js +0 -771
- package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
- package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
- package/dist/tools/__tests__/get-overview.test.js +0 -225
- package/dist/tools/__tests__/search.test.d.ts +0 -2
- package/dist/tools/__tests__/search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/search.test.js +0 -206
- package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-comments.test.js +0 -294
- package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-projects.test.js +0 -217
- package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-sections.test.js +0 -169
- package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-tasks.test.js +0 -788
- package/dist/tools/__tests__/user-info.test.d.ts +0 -2
- package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
- package/dist/tools/__tests__/user-info.test.js +0 -139
- package/dist/tools/add-comments.js +0 -79
- package/dist/tools/add-projects.js +0 -63
- package/dist/tools/add-sections.js +0 -61
- package/dist/tools/add-tasks.js +0 -160
- package/dist/tools/complete-tasks.js +0 -68
- package/dist/tools/delete-object.js +0 -79
- package/dist/tools/fetch.js +0 -102
- package/dist/tools/find-activity.js +0 -221
- package/dist/tools/find-comments.js +0 -143
- package/dist/tools/find-completed-tasks.js +0 -161
- package/dist/tools/find-project-collaborators.js +0 -151
- package/dist/tools/find-projects.js +0 -101
- package/dist/tools/find-sections.js +0 -96
- package/dist/tools/find-tasks-by-date.js +0 -198
- package/dist/tools/find-tasks.js +0 -329
- package/dist/tools/get-overview.js +0 -249
- package/dist/tools/manage-assignments.js +0 -337
- package/dist/tools/search.js +0 -65
- package/dist/tools/update-comments.js +0 -82
- package/dist/tools/update-projects.js +0 -84
- package/dist/tools/update-sections.js +0 -70
- package/dist/tools/update-tasks.js +0 -170
- package/dist/tools/user-info.js +0 -142
- package/dist/utils/assignment-validator.js +0 -253
- package/dist/utils/constants.js +0 -45
- package/dist/utils/duration-parser.js +0 -96
- package/dist/utils/duration-parser.test.d.ts +0 -2
- package/dist/utils/duration-parser.test.d.ts.map +0 -1
- package/dist/utils/duration-parser.test.js +0 -147
- package/dist/utils/labels.js +0 -18
- package/dist/utils/priorities.js +0 -20
- package/dist/utils/response-builders.js +0 -210
- package/dist/utils/sanitize-data.js +0 -37
- package/dist/utils/sanitize-data.test.d.ts +0 -2
- package/dist/utils/sanitize-data.test.d.ts.map +0 -1
- package/dist/utils/sanitize-data.test.js +0 -93
- package/dist/utils/test-helpers.js +0 -237
- package/dist/utils/tool-names.js +0 -40
- package/dist/utils/user-resolver.js +0 -179
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getToolOutput } from '../mcp-helpers.js';
|
|
3
|
-
import { createMoveTaskArgs, mapTask } from '../tool-helpers.js';
|
|
4
|
-
import { assignmentValidator } from '../utils/assignment-validator.js';
|
|
5
|
-
import { DurationParseError, parseDuration } from '../utils/duration-parser.js';
|
|
6
|
-
import { convertPriorityToNumber, PrioritySchema } from '../utils/priorities.js';
|
|
7
|
-
import { summarizeTaskOperation } from '../utils/response-builders.js';
|
|
8
|
-
import { ToolNames } from '../utils/tool-names.js';
|
|
9
|
-
const { FIND_TASKS_BY_DATE, GET_OVERVIEW } = ToolNames;
|
|
10
|
-
const TasksUpdateSchema = z.object({
|
|
11
|
-
id: z.string().min(1).describe('The ID of the task to update.'),
|
|
12
|
-
content: z
|
|
13
|
-
.string()
|
|
14
|
-
.optional()
|
|
15
|
-
.describe('The new task name/title. Should be concise and actionable (e.g., "Review PR #123", "Call dentist"). For longer content, use the description field instead. Supports Markdown.'),
|
|
16
|
-
description: z
|
|
17
|
-
.string()
|
|
18
|
-
.optional()
|
|
19
|
-
.describe('New additional details, notes, or context for the task. Use this for longer content rather than putting it in the task name. Supports Markdown.'),
|
|
20
|
-
projectId: z.string().optional().describe('The new project ID for the task.'),
|
|
21
|
-
sectionId: z.string().optional().describe('The new section ID for the task.'),
|
|
22
|
-
parentId: z.string().optional().describe('The new parent task ID (for subtasks).'),
|
|
23
|
-
order: z.number().optional().describe('The new order of the task within its parent/section.'),
|
|
24
|
-
priority: PrioritySchema.optional().describe('The new priority of the task: p1 (highest), p2 (high), p3 (medium), p4 (lowest/default).'),
|
|
25
|
-
dueString: z
|
|
26
|
-
.string()
|
|
27
|
-
.optional()
|
|
28
|
-
.describe("The new due date for the task, in natural language (e.g., 'tomorrow at 5pm')."),
|
|
29
|
-
deadlineDate: z
|
|
30
|
-
.string()
|
|
31
|
-
.optional()
|
|
32
|
-
.describe('The new deadline date for the task in ISO 8601 format (YYYY-MM-DD, e.g., "2025-12-31"). Deadlines are immovable constraints shown with a different indicator than due dates. Use "remove" to clear the deadline.'),
|
|
33
|
-
duration: z
|
|
34
|
-
.string()
|
|
35
|
-
.optional()
|
|
36
|
-
.describe('The duration of the task. Use format: "2h" (hours), "90m" (minutes), "2h30m" (combined), or "1.5h" (decimal hours). Max 24h.'),
|
|
37
|
-
responsibleUser: z
|
|
38
|
-
.string()
|
|
39
|
-
.optional()
|
|
40
|
-
.describe('Change task assignment. Use "unassign" to remove assignment. Can be user ID, name, or email. User must be a project collaborator.'),
|
|
41
|
-
labels: z
|
|
42
|
-
.array(z.string())
|
|
43
|
-
.optional()
|
|
44
|
-
.describe('The new labels for the task. Replaces all existing labels.'),
|
|
45
|
-
});
|
|
46
|
-
const ArgsSchema = {
|
|
47
|
-
tasks: z.array(TasksUpdateSchema).min(1).describe('The tasks to update.'),
|
|
48
|
-
};
|
|
49
|
-
const updateTasks = {
|
|
50
|
-
name: ToolNames.UPDATE_TASKS,
|
|
51
|
-
description: 'Update existing tasks including content, dates, priorities, and assignments.',
|
|
52
|
-
parameters: ArgsSchema,
|
|
53
|
-
async execute(args, client) {
|
|
54
|
-
const { tasks } = args;
|
|
55
|
-
const updateTasksPromises = tasks.map(async (task) => {
|
|
56
|
-
if (!hasUpdatesToMake(task)) {
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
const { id, projectId, sectionId, parentId, duration: durationStr, responsibleUser, priority, labels, deadlineDate, ...otherUpdateArgs } = task;
|
|
60
|
-
let updateArgs = {
|
|
61
|
-
...otherUpdateArgs,
|
|
62
|
-
...(labels !== undefined && { labels }),
|
|
63
|
-
};
|
|
64
|
-
// Handle priority conversion if provided
|
|
65
|
-
if (priority) {
|
|
66
|
-
updateArgs.priority = convertPriorityToNumber(priority);
|
|
67
|
-
}
|
|
68
|
-
// Handle deadline changes if provided
|
|
69
|
-
if (deadlineDate !== undefined) {
|
|
70
|
-
if (deadlineDate === null || deadlineDate === 'remove') {
|
|
71
|
-
// Remove deadline - support both legacy null and new "remove" string
|
|
72
|
-
updateArgs = { ...updateArgs, deadlineDate: null };
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
// Set new deadline
|
|
76
|
-
updateArgs = { ...updateArgs, deadlineDate };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// Parse duration if provided
|
|
80
|
-
if (durationStr) {
|
|
81
|
-
try {
|
|
82
|
-
const { minutes } = parseDuration(durationStr);
|
|
83
|
-
updateArgs = {
|
|
84
|
-
...updateArgs,
|
|
85
|
-
duration: minutes,
|
|
86
|
-
durationUnit: 'minute',
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
if (error instanceof DurationParseError) {
|
|
91
|
-
throw new Error(`Task ${id}: ${error.message}`);
|
|
92
|
-
}
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// Handle assignment changes if provided
|
|
97
|
-
if (responsibleUser !== undefined) {
|
|
98
|
-
if (responsibleUser === null || responsibleUser === 'unassign') {
|
|
99
|
-
// Unassign task - no validation needed
|
|
100
|
-
updateArgs = { ...updateArgs, assigneeId: null };
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
// Validate assignment using comprehensive validator
|
|
104
|
-
const validation = await assignmentValidator.validateTaskUpdateAssignment(client, id, responsibleUser);
|
|
105
|
-
if (!validation.isValid) {
|
|
106
|
-
const errorMsg = validation.error?.message || 'Assignment validation failed';
|
|
107
|
-
const suggestions = validation.error?.suggestions?.join('. ') || '';
|
|
108
|
-
throw new Error(`Task ${id}: ${errorMsg}${suggestions ? `. ${suggestions}` : ''}`);
|
|
109
|
-
}
|
|
110
|
-
// Use the validated assignee ID
|
|
111
|
-
updateArgs = { ...updateArgs, assigneeId: validation.resolvedUser?.userId };
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// If no move parameters are provided, use updateTask without moveTask
|
|
115
|
-
if (!projectId && !sectionId && !parentId) {
|
|
116
|
-
return await client.updateTask(id, updateArgs);
|
|
117
|
-
}
|
|
118
|
-
const moveArgs = createMoveTaskArgs(id, projectId, sectionId, parentId);
|
|
119
|
-
const movedTask = await client.moveTask(id, moveArgs);
|
|
120
|
-
if (Object.keys(updateArgs).length > 0) {
|
|
121
|
-
return await client.updateTask(id, updateArgs);
|
|
122
|
-
}
|
|
123
|
-
return movedTask;
|
|
124
|
-
});
|
|
125
|
-
const updatedTasks = (await Promise.all(updateTasksPromises)).filter((task) => task !== undefined);
|
|
126
|
-
const mappedTasks = updatedTasks.map(mapTask);
|
|
127
|
-
const textContent = generateTextContent({
|
|
128
|
-
tasks: mappedTasks,
|
|
129
|
-
args,
|
|
130
|
-
});
|
|
131
|
-
return getToolOutput({
|
|
132
|
-
textContent,
|
|
133
|
-
structuredContent: {
|
|
134
|
-
tasks: mappedTasks,
|
|
135
|
-
totalCount: mappedTasks.length,
|
|
136
|
-
updatedTaskIds: updatedTasks.map((task) => task.id),
|
|
137
|
-
appliedOperations: {
|
|
138
|
-
updateCount: mappedTasks.length,
|
|
139
|
-
skippedCount: tasks.length - mappedTasks.length,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
function generateTextContent({ tasks, args, }) {
|
|
146
|
-
const totalRequested = args.tasks.length;
|
|
147
|
-
const actuallyUpdated = tasks.length;
|
|
148
|
-
const skipped = totalRequested - actuallyUpdated;
|
|
149
|
-
let context = '';
|
|
150
|
-
if (skipped > 0) {
|
|
151
|
-
context = ` (${skipped} skipped - no changes)`;
|
|
152
|
-
}
|
|
153
|
-
const nextSteps = [];
|
|
154
|
-
if (tasks.length > 0) {
|
|
155
|
-
nextSteps.push(`Use ${FIND_TASKS_BY_DATE} to see your updated schedule`);
|
|
156
|
-
nextSteps.push(`Use ${GET_OVERVIEW} to see updated project organization`);
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
nextSteps.push(`Use ${FIND_TASKS_BY_DATE} to see current tasks`);
|
|
160
|
-
}
|
|
161
|
-
return summarizeTaskOperation('Updated', tasks, {
|
|
162
|
-
context,
|
|
163
|
-
nextSteps,
|
|
164
|
-
showDetails: tasks.length <= 5,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
function hasUpdatesToMake({ id, ...otherUpdateArgs }) {
|
|
168
|
-
return Object.keys(otherUpdateArgs).length > 0;
|
|
169
|
-
}
|
|
170
|
-
export { updateTasks };
|
package/dist/tools/user-info.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
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 };
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import { userResolver } from './user-resolver.js';
|
|
2
|
-
export const AssignmentErrorType = {
|
|
3
|
-
USER_NOT_FOUND: 'USER_NOT_FOUND',
|
|
4
|
-
USER_NOT_COLLABORATOR: 'USER_NOT_COLLABORATOR',
|
|
5
|
-
PROJECT_NOT_SHARED: 'PROJECT_NOT_SHARED',
|
|
6
|
-
TASK_NOT_ACCESSIBLE: 'TASK_NOT_ACCESSIBLE',
|
|
7
|
-
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
8
|
-
PROJECT_NOT_FOUND: 'PROJECT_NOT_FOUND',
|
|
9
|
-
TASK_NOT_FOUND: 'TASK_NOT_FOUND',
|
|
10
|
-
};
|
|
11
|
-
export class AssignmentValidator {
|
|
12
|
-
/**
|
|
13
|
-
* Validate a single assignment operation
|
|
14
|
-
*/
|
|
15
|
-
async validateAssignment(client, assignment) {
|
|
16
|
-
const { taskId, projectId, responsibleUid } = assignment;
|
|
17
|
-
// First, resolve the user
|
|
18
|
-
const resolvedUser = await userResolver.resolveUser(client, responsibleUid);
|
|
19
|
-
if (!resolvedUser) {
|
|
20
|
-
return {
|
|
21
|
-
isValid: false,
|
|
22
|
-
taskId,
|
|
23
|
-
projectId,
|
|
24
|
-
error: {
|
|
25
|
-
type: AssignmentErrorType.USER_NOT_FOUND,
|
|
26
|
-
message: `User "${responsibleUid}" not found`,
|
|
27
|
-
suggestions: [
|
|
28
|
-
'Check the spelling of the user name or email',
|
|
29
|
-
'Ensure the user is a collaborator on at least one shared project',
|
|
30
|
-
"Try using the user's full email address",
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
// Validate the project exists and is accessible
|
|
36
|
-
try {
|
|
37
|
-
const targetProject = await client.getProject(projectId);
|
|
38
|
-
// Check if project is shared (required for assignments)
|
|
39
|
-
if (!targetProject.isShared) {
|
|
40
|
-
return {
|
|
41
|
-
isValid: false,
|
|
42
|
-
taskId,
|
|
43
|
-
projectId,
|
|
44
|
-
resolvedUser,
|
|
45
|
-
error: {
|
|
46
|
-
type: AssignmentErrorType.PROJECT_NOT_SHARED,
|
|
47
|
-
message: `Project "${targetProject.name}" is not shared`,
|
|
48
|
-
suggestions: [
|
|
49
|
-
'Share the project with collaborators before assigning tasks',
|
|
50
|
-
'Only shared projects support task assignments',
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
// Validate the user is a collaborator on this specific project
|
|
56
|
-
const isCollaborator = await userResolver.validateProjectCollaborator(client, projectId, resolvedUser.userId);
|
|
57
|
-
if (!isCollaborator) {
|
|
58
|
-
return {
|
|
59
|
-
isValid: false,
|
|
60
|
-
taskId,
|
|
61
|
-
projectId,
|
|
62
|
-
resolvedUser,
|
|
63
|
-
error: {
|
|
64
|
-
type: AssignmentErrorType.USER_NOT_COLLABORATOR,
|
|
65
|
-
message: `User "${resolvedUser.displayName}" is not a collaborator on project "${targetProject.name}"`,
|
|
66
|
-
suggestions: [
|
|
67
|
-
'Invite the user to collaborate on this project first',
|
|
68
|
-
'Check if the user has been removed from the project',
|
|
69
|
-
'Verify you have permission to see project collaborators',
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
// If taskId is provided, validate the task exists and is accessible
|
|
75
|
-
if (taskId) {
|
|
76
|
-
try {
|
|
77
|
-
await client.getTask(taskId);
|
|
78
|
-
}
|
|
79
|
-
catch (_error) {
|
|
80
|
-
return {
|
|
81
|
-
isValid: false,
|
|
82
|
-
taskId,
|
|
83
|
-
projectId,
|
|
84
|
-
resolvedUser,
|
|
85
|
-
error: {
|
|
86
|
-
type: AssignmentErrorType.TASK_NOT_FOUND,
|
|
87
|
-
message: `Task "${taskId}" not found or not accessible`,
|
|
88
|
-
suggestions: [
|
|
89
|
-
'Verify the task ID is correct',
|
|
90
|
-
'Check if the task has been deleted',
|
|
91
|
-
'Ensure you have access to the task',
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
isValid: true,
|
|
99
|
-
taskId,
|
|
100
|
-
projectId,
|
|
101
|
-
resolvedUser,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
catch (_error) {
|
|
105
|
-
return {
|
|
106
|
-
isValid: false,
|
|
107
|
-
taskId,
|
|
108
|
-
projectId,
|
|
109
|
-
resolvedUser,
|
|
110
|
-
error: {
|
|
111
|
-
type: AssignmentErrorType.PERMISSION_DENIED,
|
|
112
|
-
message: 'Permission denied or API error occurred',
|
|
113
|
-
suggestions: [
|
|
114
|
-
'Check your API permissions',
|
|
115
|
-
'Verify you have access to the project',
|
|
116
|
-
'Try again later if this is a temporary API issue',
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Validate multiple assignment operations in bulk
|
|
124
|
-
*/
|
|
125
|
-
async validateBulkAssignment(client, assignments) {
|
|
126
|
-
// Process assignments in parallel for better performance
|
|
127
|
-
const validationPromises = assignments.map((assignment) => this.validateAssignment(client, assignment));
|
|
128
|
-
return Promise.all(validationPromises);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Validate assignment for task creation (no taskId required)
|
|
132
|
-
*/
|
|
133
|
-
async validateTaskCreationAssignment(client, projectId, responsibleUid) {
|
|
134
|
-
return this.validateAssignment(client, {
|
|
135
|
-
projectId,
|
|
136
|
-
responsibleUid,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Validate assignment for task update
|
|
141
|
-
*/
|
|
142
|
-
async validateTaskUpdateAssignment(client, taskId, responsibleUid) {
|
|
143
|
-
// If responsibleUid is null, it's an unassignment - always valid
|
|
144
|
-
if (responsibleUid === null) {
|
|
145
|
-
return {
|
|
146
|
-
isValid: true,
|
|
147
|
-
taskId,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
// Get the task to find its project
|
|
151
|
-
try {
|
|
152
|
-
const task = await client.getTask(taskId);
|
|
153
|
-
return this.validateAssignment(client, {
|
|
154
|
-
taskId,
|
|
155
|
-
projectId: task.projectId,
|
|
156
|
-
responsibleUid,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
catch (_error) {
|
|
160
|
-
return {
|
|
161
|
-
isValid: false,
|
|
162
|
-
taskId,
|
|
163
|
-
error: {
|
|
164
|
-
type: AssignmentErrorType.TASK_NOT_FOUND,
|
|
165
|
-
message: `Task "${taskId}" not found or not accessible`,
|
|
166
|
-
suggestions: [
|
|
167
|
-
'Verify the task ID is correct',
|
|
168
|
-
'Check if the task has been deleted',
|
|
169
|
-
'Ensure you have access to the task',
|
|
170
|
-
],
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Get detailed assignment eligibility information for troubleshooting
|
|
177
|
-
*/
|
|
178
|
-
async getAssignmentEligibility(client, projectId, responsibleUid, taskIds) {
|
|
179
|
-
const recommendations = [];
|
|
180
|
-
let canAssign = false;
|
|
181
|
-
// Get project information
|
|
182
|
-
let project;
|
|
183
|
-
try {
|
|
184
|
-
project = await client.getProject(projectId);
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return {
|
|
188
|
-
canAssign: false,
|
|
189
|
-
projectInfo: {
|
|
190
|
-
name: 'Unknown',
|
|
191
|
-
isShared: false,
|
|
192
|
-
collaboratorCount: 0,
|
|
193
|
-
},
|
|
194
|
-
recommendations: ['Project not found or not accessible'],
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
const collaborators = await userResolver.getProjectCollaborators(client, projectId);
|
|
198
|
-
const projectInfo = {
|
|
199
|
-
name: project.name,
|
|
200
|
-
isShared: project.isShared,
|
|
201
|
-
collaboratorCount: collaborators.length,
|
|
202
|
-
};
|
|
203
|
-
if (!project.isShared) {
|
|
204
|
-
recommendations.push('Share this project to enable task assignments');
|
|
205
|
-
return { canAssign: false, projectInfo, recommendations };
|
|
206
|
-
}
|
|
207
|
-
// Resolve user
|
|
208
|
-
const resolvedUser = await userResolver.resolveUser(client, responsibleUid);
|
|
209
|
-
if (!resolvedUser) {
|
|
210
|
-
recommendations.push('User not found - check spelling or invite to a shared project');
|
|
211
|
-
return { canAssign: false, projectInfo, recommendations };
|
|
212
|
-
}
|
|
213
|
-
const isCollaborator = collaborators.some((c) => c.id === resolvedUser.userId);
|
|
214
|
-
const userInfo = {
|
|
215
|
-
resolvedName: resolvedUser.displayName,
|
|
216
|
-
isCollaborator,
|
|
217
|
-
};
|
|
218
|
-
if (!isCollaborator) {
|
|
219
|
-
recommendations.push(`Invite ${resolvedUser.displayName} to collaborate on project "${project.name}"`);
|
|
220
|
-
return { canAssign: false, projectInfo, userInfo, recommendations };
|
|
221
|
-
}
|
|
222
|
-
// Check task accessibility if provided
|
|
223
|
-
let taskInfo;
|
|
224
|
-
if (taskIds && taskIds.length > 0) {
|
|
225
|
-
let accessible = 0;
|
|
226
|
-
let inaccessible = 0;
|
|
227
|
-
for (const taskId of taskIds) {
|
|
228
|
-
try {
|
|
229
|
-
await client.getTask(taskId);
|
|
230
|
-
accessible++;
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
inaccessible++;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
taskInfo = { accessibleTasks: accessible, inaccessibleTasks: inaccessible };
|
|
237
|
-
if (inaccessible > 0) {
|
|
238
|
-
recommendations.push(`${inaccessible} task(s) are not accessible`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
canAssign = true;
|
|
242
|
-
recommendations.push(`Ready to assign tasks to ${resolvedUser.displayName}`);
|
|
243
|
-
return {
|
|
244
|
-
canAssign,
|
|
245
|
-
projectInfo,
|
|
246
|
-
userInfo,
|
|
247
|
-
taskInfo,
|
|
248
|
-
recommendations,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// Export singleton instance
|
|
253
|
-
export const assignmentValidator = new AssignmentValidator();
|
package/dist/utils/constants.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Application-wide constants
|
|
3
|
-
*
|
|
4
|
-
* This module centralizes magic numbers and configuration values
|
|
5
|
-
* to improve maintainability and provide a single source of truth.
|
|
6
|
-
*/
|
|
7
|
-
// API Pagination Limits
|
|
8
|
-
export const ApiLimits = {
|
|
9
|
-
/** Default limit for task listings */
|
|
10
|
-
TASKS_DEFAULT: 10,
|
|
11
|
-
/** Maximum limit for task search and list operations */
|
|
12
|
-
TASKS_MAX: 100,
|
|
13
|
-
/** Default limit for completed tasks */
|
|
14
|
-
COMPLETED_TASKS_DEFAULT: 50,
|
|
15
|
-
/** Maximum limit for completed tasks */
|
|
16
|
-
COMPLETED_TASKS_MAX: 200,
|
|
17
|
-
/** Default limit for project listings */
|
|
18
|
-
PROJECTS_DEFAULT: 50,
|
|
19
|
-
/** Maximum limit for project listings */
|
|
20
|
-
PROJECTS_MAX: 100,
|
|
21
|
-
/** Batch size for fetching all tasks in a project */
|
|
22
|
-
TASKS_BATCH_SIZE: 50,
|
|
23
|
-
/** Default limit for comment listings */
|
|
24
|
-
COMMENTS_DEFAULT: 10,
|
|
25
|
-
/** Maximum limit for comment search and list operations */
|
|
26
|
-
COMMENTS_MAX: 10,
|
|
27
|
-
/** Default limit for activity log listings */
|
|
28
|
-
ACTIVITY_DEFAULT: 20,
|
|
29
|
-
/** Maximum limit for activity log search and list operations */
|
|
30
|
-
ACTIVITY_MAX: 100,
|
|
31
|
-
};
|
|
32
|
-
// UI Display Limits
|
|
33
|
-
export const DisplayLimits = {
|
|
34
|
-
/** Maximum number of failures to show in detailed error messages */
|
|
35
|
-
MAX_FAILURES_SHOWN: 3,
|
|
36
|
-
/** Threshold for suggesting batch operations */
|
|
37
|
-
BATCH_OPERATION_THRESHOLD: 10,
|
|
38
|
-
};
|
|
39
|
-
// Response Builder Configuration
|
|
40
|
-
export const ResponseConfig = {
|
|
41
|
-
/** Maximum characters per line in text responses */
|
|
42
|
-
MAX_LINE_LENGTH: 100,
|
|
43
|
-
/** Indentation for nested items */
|
|
44
|
-
INDENT_SIZE: 2,
|
|
45
|
-
};
|