@awolve/myoffice 1.6.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.
Files changed (94) hide show
  1. package/README.md +271 -0
  2. package/dist/auth/cache-plugin.d.ts +20 -0
  3. package/dist/auth/cache-plugin.d.ts.map +1 -0
  4. package/dist/auth/cache-plugin.js +49 -0
  5. package/dist/auth/cache-plugin.js.map +1 -0
  6. package/dist/auth/config.d.ts +40 -0
  7. package/dist/auth/config.d.ts.map +1 -0
  8. package/dist/auth/config.js +135 -0
  9. package/dist/auth/config.js.map +1 -0
  10. package/dist/auth/device-code.d.ts +2 -0
  11. package/dist/auth/device-code.d.ts.map +1 -0
  12. package/dist/auth/device-code.js +40 -0
  13. package/dist/auth/device-code.js.map +1 -0
  14. package/dist/auth/index.d.ts +4 -0
  15. package/dist/auth/index.d.ts.map +1 -0
  16. package/dist/auth/index.js +4 -0
  17. package/dist/auth/index.js.map +1 -0
  18. package/dist/auth/login.d.ts +7 -0
  19. package/dist/auth/login.d.ts.map +1 -0
  20. package/dist/auth/login.js +22 -0
  21. package/dist/auth/login.js.map +1 -0
  22. package/dist/auth/token-manager.d.ts +4 -0
  23. package/dist/auth/token-manager.d.ts.map +1 -0
  24. package/dist/auth/token-manager.js +85 -0
  25. package/dist/auth/token-manager.js.map +1 -0
  26. package/dist/cli/formatter.d.ts +5 -0
  27. package/dist/cli/formatter.d.ts.map +1 -0
  28. package/dist/cli/formatter.js +317 -0
  29. package/dist/cli/formatter.js.map +1 -0
  30. package/dist/cli.d.ts +3 -0
  31. package/dist/cli.d.ts.map +1 -0
  32. package/dist/cli.js +973 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/core/handler.d.ts +8 -0
  35. package/dist/core/handler.d.ts.map +1 -0
  36. package/dist/core/handler.js +327 -0
  37. package/dist/core/handler.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +924 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/tools/calendar.d.ts +124 -0
  43. package/dist/tools/calendar.d.ts.map +1 -0
  44. package/dist/tools/calendar.js +129 -0
  45. package/dist/tools/calendar.js.map +1 -0
  46. package/dist/tools/chats.d.ts +66 -0
  47. package/dist/tools/chats.d.ts.map +1 -0
  48. package/dist/tools/chats.js +102 -0
  49. package/dist/tools/chats.js.map +1 -0
  50. package/dist/tools/contacts.d.ts +138 -0
  51. package/dist/tools/contacts.d.ts.map +1 -0
  52. package/dist/tools/contacts.js +189 -0
  53. package/dist/tools/contacts.js.map +1 -0
  54. package/dist/tools/index.d.ts +10 -0
  55. package/dist/tools/index.d.ts.map +1 -0
  56. package/dist/tools/index.js +10 -0
  57. package/dist/tools/index.js.map +1 -0
  58. package/dist/tools/mail.d.ts +138 -0
  59. package/dist/tools/mail.d.ts.map +1 -0
  60. package/dist/tools/mail.js +187 -0
  61. package/dist/tools/mail.js.map +1 -0
  62. package/dist/tools/onedrive.d.ts +125 -0
  63. package/dist/tools/onedrive.d.ts.map +1 -0
  64. package/dist/tools/onedrive.js +203 -0
  65. package/dist/tools/onedrive.js.map +1 -0
  66. package/dist/tools/planner.d.ts +390 -0
  67. package/dist/tools/planner.d.ts.map +1 -0
  68. package/dist/tools/planner.js +693 -0
  69. package/dist/tools/planner.js.map +1 -0
  70. package/dist/tools/sharepoint.d.ts +138 -0
  71. package/dist/tools/sharepoint.d.ts.map +1 -0
  72. package/dist/tools/sharepoint.js +156 -0
  73. package/dist/tools/sharepoint.js.map +1 -0
  74. package/dist/tools/tasks.d.ts +107 -0
  75. package/dist/tools/tasks.d.ts.map +1 -0
  76. package/dist/tools/tasks.js +131 -0
  77. package/dist/tools/tasks.js.map +1 -0
  78. package/dist/tools/teams.d.ts +66 -0
  79. package/dist/tools/teams.d.ts.map +1 -0
  80. package/dist/tools/teams.js +69 -0
  81. package/dist/tools/teams.js.map +1 -0
  82. package/dist/utils/graph-client.d.ts +15 -0
  83. package/dist/utils/graph-client.d.ts.map +1 -0
  84. package/dist/utils/graph-client.js +126 -0
  85. package/dist/utils/graph-client.js.map +1 -0
  86. package/dist/utils/signature.d.ts +2 -0
  87. package/dist/utils/signature.d.ts.map +1 -0
  88. package/dist/utils/signature.js +17 -0
  89. package/dist/utils/signature.js.map +1 -0
  90. package/dist/utils/version.d.ts +2 -0
  91. package/dist/utils/version.d.ts.map +1 -0
  92. package/dist/utils/version.js +20 -0
  93. package/dist/utils/version.js.map +1 -0
  94. package/package.json +55 -0
@@ -0,0 +1,693 @@
1
+ import { z } from 'zod';
2
+ import { basename } from 'path';
3
+ import { readFile } from 'fs/promises';
4
+ import { graphRequest, graphList, graphUpload, graphUploadLarge } from '../utils/graph-client.js';
5
+ // ============================================================================
6
+ // Schemas
7
+ // ============================================================================
8
+ // Plans (read-only)
9
+ export const listPlansSchema = z.object({
10
+ maxItems: z.number().optional().describe('Maximum number of plans. Default: 50'),
11
+ });
12
+ export const getPlanSchema = z.object({
13
+ planId: z.string().describe('The plan ID'),
14
+ });
15
+ // Buckets
16
+ export const listBucketsSchema = z.object({
17
+ planId: z.string().describe('The plan ID'),
18
+ });
19
+ export const createBucketSchema = z.object({
20
+ planId: z.string().describe('The plan ID'),
21
+ name: z.string().describe('Bucket name'),
22
+ });
23
+ export const updateBucketSchema = z.object({
24
+ bucketId: z.string().describe('The bucket ID'),
25
+ name: z.string().describe('New bucket name'),
26
+ });
27
+ export const deleteBucketSchema = z.object({
28
+ bucketId: z.string().describe('The bucket ID'),
29
+ });
30
+ // Tasks
31
+ export const listPlannerTasksSchema = z.object({
32
+ planId: z.string().describe('The plan ID'),
33
+ bucketId: z.string().optional().describe('Filter by bucket ID'),
34
+ maxItems: z.number().optional().describe('Maximum number of tasks. Default: 100'),
35
+ });
36
+ export const getPlannerTaskSchema = z.object({
37
+ taskId: z.string().describe('The task ID'),
38
+ });
39
+ export const createPlannerTaskSchema = z.object({
40
+ planId: z.string().describe('The plan ID'),
41
+ title: z.string().describe('Task title'),
42
+ bucketId: z.string().optional().describe('Bucket ID to place the task in'),
43
+ assignments: z
44
+ .array(z.string())
45
+ .optional()
46
+ .describe('Email addresses of users to assign'),
47
+ dueDateTime: z.string().optional().describe('Due date (ISO format)'),
48
+ startDateTime: z.string().optional().describe('Start date (ISO format)'),
49
+ priority: z
50
+ .enum(['urgent', 'important', 'medium', 'low'])
51
+ .optional()
52
+ .describe('Task priority'),
53
+ progress: z
54
+ .enum(['notStarted', 'inProgress', 'completed'])
55
+ .optional()
56
+ .describe('Task progress'),
57
+ });
58
+ export const updatePlannerTaskSchema = z.object({
59
+ taskId: z.string().describe('The task ID'),
60
+ title: z.string().optional().describe('New task title'),
61
+ bucketId: z.string().optional().describe('Move to different bucket'),
62
+ assignments: z
63
+ .array(z.string())
64
+ .optional()
65
+ .describe('Email addresses of users to assign (replaces existing)'),
66
+ clearAssignments: z.boolean().optional().describe('Remove all assignments'),
67
+ dueDateTime: z.string().optional().describe('New due date (ISO format)'),
68
+ clearDue: z.boolean().optional().describe('Clear the due date'),
69
+ startDateTime: z.string().optional().describe('New start date (ISO format)'),
70
+ priority: z
71
+ .enum(['urgent', 'important', 'medium', 'low'])
72
+ .optional()
73
+ .describe('New priority'),
74
+ progress: z
75
+ .enum(['notStarted', 'inProgress', 'completed'])
76
+ .optional()
77
+ .describe('New progress'),
78
+ });
79
+ export const deletePlannerTaskSchema = z.object({
80
+ taskId: z.string().describe('The task ID'),
81
+ });
82
+ // Task Details
83
+ export const getPlannerTaskDetailsSchema = z.object({
84
+ taskId: z.string().describe('The task ID'),
85
+ });
86
+ export const updatePlannerTaskDetailsSchema = z.object({
87
+ taskId: z.string().describe('The task ID'),
88
+ description: z.string().optional().describe('Task description'),
89
+ checklist: z
90
+ .array(z.object({
91
+ title: z.string().describe('Checklist item title'),
92
+ isChecked: z.boolean().optional().describe('Whether item is checked'),
93
+ }))
94
+ .optional()
95
+ .describe('Checklist items (replaces existing checklist)'),
96
+ });
97
+ // Checklist operations
98
+ export const addChecklistItemSchema = z.object({
99
+ taskId: z.string().describe('The task ID'),
100
+ title: z.string().describe('Checklist item title'),
101
+ isChecked: z.boolean().optional().describe('Whether item is checked. Default: false'),
102
+ });
103
+ export const removeChecklistItemSchema = z.object({
104
+ taskId: z.string().describe('The task ID'),
105
+ itemId: z.string().describe('The checklist item ID'),
106
+ });
107
+ export const toggleChecklistItemSchema = z.object({
108
+ taskId: z.string().describe('The task ID'),
109
+ itemId: z.string().describe('The checklist item ID'),
110
+ });
111
+ // References (attachments)
112
+ export const addPlannerTaskReferenceSchema = z.object({
113
+ taskId: z.string().describe('The task ID'),
114
+ url: z.string().url().describe('URL of the file or link to attach'),
115
+ alias: z.string().optional().describe('Display name for the reference'),
116
+ type: z
117
+ .enum(['Word', 'Excel', 'PowerPoint', 'OneNote', 'Project', 'Visio', 'Pdf', 'TeamsHostedApp', 'Other'])
118
+ .optional()
119
+ .describe('Type of reference (auto-detected if not provided)'),
120
+ });
121
+ export const removePlannerTaskReferenceSchema = z.object({
122
+ taskId: z.string().describe('The task ID'),
123
+ url: z.string().url().describe('URL of the reference to remove'),
124
+ });
125
+ export const uploadAndAttachSchema = z.object({
126
+ taskId: z.string().describe('The Planner task ID'),
127
+ localPath: z.string().describe('Local file path to upload'),
128
+ remotePath: z
129
+ .string()
130
+ .optional()
131
+ .describe('Destination path in OneDrive. If omitted, uploads to "Planner Attachments/<filename>"'),
132
+ alias: z.string().optional().describe('Display name for the attachment'),
133
+ });
134
+ // ============================================================================
135
+ // Helpers
136
+ // ============================================================================
137
+ // Priority mapping: semantic -> numeric
138
+ const PRIORITY_MAP = {
139
+ urgent: 1,
140
+ important: 3,
141
+ medium: 5,
142
+ low: 9,
143
+ };
144
+ // Reverse priority mapping for display
145
+ const PRIORITY_REVERSE = {
146
+ 1: 'urgent',
147
+ 3: 'important',
148
+ 5: 'medium',
149
+ 9: 'low',
150
+ };
151
+ // Progress mapping: semantic -> percentComplete
152
+ const PROGRESS_MAP = {
153
+ notStarted: 0,
154
+ inProgress: 50,
155
+ completed: 100,
156
+ };
157
+ // Reverse progress mapping for display
158
+ function progressToSemantic(percentComplete) {
159
+ if (percentComplete === 0)
160
+ return 'notStarted';
161
+ if (percentComplete === 100)
162
+ return 'completed';
163
+ return 'inProgress';
164
+ }
165
+ // Reference type detection from URL
166
+ // Valid types per Microsoft API: Other, Word, Excel, PowerPoint, OneNote, Project, Visio, Pdf, TeamsHostedApp
167
+ function detectReferenceType(url) {
168
+ const lowerUrl = url.toLowerCase();
169
+ if (lowerUrl.includes('.docx') || lowerUrl.includes('.doc'))
170
+ return 'Word';
171
+ if (lowerUrl.includes('.xlsx') || lowerUrl.includes('.xls'))
172
+ return 'Excel';
173
+ if (lowerUrl.includes('.pptx') || lowerUrl.includes('.ppt'))
174
+ return 'PowerPoint';
175
+ if (lowerUrl.includes('.one') || lowerUrl.includes('onenote'))
176
+ return 'OneNote';
177
+ if (lowerUrl.includes('.pdf'))
178
+ return 'Pdf';
179
+ if (lowerUrl.includes('.mpp'))
180
+ return 'Project';
181
+ if (lowerUrl.includes('.vsdx') || lowerUrl.includes('.vsd'))
182
+ return 'Visio';
183
+ // All other files (images, SharePoint URLs, OneDrive URLs, etc.) use 'Other'
184
+ return 'Other';
185
+ }
186
+ // Encode URL for use as reference key (special encoding required by Planner)
187
+ // Only encode characters that are problematic as JSON object keys, preserve URL structure
188
+ function encodeReferenceUrl(url) {
189
+ // Microsoft Planner requires periods and some special chars to be encoded
190
+ // but the URL must remain parseable (don't encode :, /, ?, &, =, etc.)
191
+ return url
192
+ .replace(/%/g, '%25') // Encode existing percent signs first
193
+ .replace(/\./g, '%2E') // Periods must be encoded
194
+ .replace(/#/g, '%23') // Hash/fragment
195
+ .replace(/\s/g, '%20'); // Spaces
196
+ }
197
+ // Get ETag for a resource
198
+ async function getETag(endpoint) {
199
+ const resource = await graphRequest(endpoint);
200
+ const etag = resource['@odata.etag'];
201
+ if (!etag) {
202
+ throw new Error('Resource does not have an ETag');
203
+ }
204
+ return etag;
205
+ }
206
+ // Simple cache for user ID lookups within a session
207
+ const userIdCache = new Map();
208
+ // Resolve email to user ID
209
+ async function resolveUserId(emailOrId) {
210
+ // If it looks like a GUID, assume it's already a user ID
211
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(emailOrId)) {
212
+ return emailOrId;
213
+ }
214
+ // Check cache
215
+ const cached = userIdCache.get(emailOrId.toLowerCase());
216
+ if (cached) {
217
+ return cached;
218
+ }
219
+ // Look up user by email
220
+ const user = await graphRequest(`/users/${encodeURIComponent(emailOrId)}`);
221
+ userIdCache.set(emailOrId.toLowerCase(), user.id);
222
+ return user.id;
223
+ }
224
+ // ============================================================================
225
+ // Plans (Read-Only)
226
+ // ============================================================================
227
+ export async function listPlans(params) {
228
+ const { maxItems = 50 } = params;
229
+ const plans = await graphList('/me/planner/plans', { maxItems });
230
+ return plans.map((p) => ({
231
+ id: p.id,
232
+ title: p.title,
233
+ owner: p.owner,
234
+ createdDateTime: p.createdDateTime,
235
+ }));
236
+ }
237
+ export async function getPlan(params) {
238
+ const { planId } = params;
239
+ const plan = await graphRequest(`/planner/plans/${planId}`);
240
+ return {
241
+ id: plan.id,
242
+ title: plan.title,
243
+ owner: plan.owner,
244
+ createdDateTime: plan.createdDateTime,
245
+ createdBy: plan.createdBy?.user,
246
+ };
247
+ }
248
+ // ============================================================================
249
+ // Buckets
250
+ // ============================================================================
251
+ export async function listBuckets(params) {
252
+ const { planId } = params;
253
+ const buckets = await graphList(`/planner/plans/${planId}/buckets`);
254
+ return buckets.map((b) => ({
255
+ id: b.id,
256
+ name: b.name,
257
+ planId: b.planId,
258
+ orderHint: b.orderHint,
259
+ }));
260
+ }
261
+ export async function createBucket(params) {
262
+ const { planId, name } = params;
263
+ const bucket = await graphRequest('/planner/buckets', {
264
+ method: 'POST',
265
+ body: { planId, name },
266
+ });
267
+ return {
268
+ success: true,
269
+ bucketId: bucket.id,
270
+ name: bucket.name,
271
+ };
272
+ }
273
+ export async function updateBucket(params) {
274
+ const { bucketId, name } = params;
275
+ // Fetch ETag first
276
+ const etag = await getETag(`/planner/buckets/${bucketId}`);
277
+ const bucket = await graphRequest(`/planner/buckets/${bucketId}`, {
278
+ method: 'PATCH',
279
+ body: { name },
280
+ headers: { 'If-Match': etag },
281
+ });
282
+ return {
283
+ success: true,
284
+ bucketId: bucket.id,
285
+ name: bucket.name,
286
+ };
287
+ }
288
+ export async function deleteBucket(params) {
289
+ const { bucketId } = params;
290
+ // Fetch ETag first
291
+ const etag = await getETag(`/planner/buckets/${bucketId}`);
292
+ await graphRequest(`/planner/buckets/${bucketId}`, {
293
+ method: 'DELETE',
294
+ headers: { 'If-Match': etag },
295
+ });
296
+ return { success: true, message: 'Bucket deleted' };
297
+ }
298
+ // ============================================================================
299
+ // Tasks
300
+ // ============================================================================
301
+ export async function listPlannerTasks(params) {
302
+ const { planId, bucketId, maxItems = 100 } = params;
303
+ let tasks = await graphList(`/planner/plans/${planId}/tasks`, { maxItems });
304
+ // Filter by bucket if specified
305
+ if (bucketId) {
306
+ tasks = tasks.filter((t) => t.bucketId === bucketId);
307
+ }
308
+ return tasks.map((t) => ({
309
+ id: t.id,
310
+ title: t.title,
311
+ bucketId: t.bucketId,
312
+ progress: progressToSemantic(t.percentComplete),
313
+ percentComplete: t.percentComplete,
314
+ priority: PRIORITY_REVERSE[t.priority] || 'medium',
315
+ dueDateTime: t.dueDateTime,
316
+ startDateTime: t.startDateTime,
317
+ assignedTo: Object.keys(t.assignments || {}),
318
+ createdDateTime: t.createdDateTime,
319
+ }));
320
+ }
321
+ export async function getPlannerTask(params) {
322
+ const { taskId } = params;
323
+ const task = await graphRequest(`/planner/tasks/${taskId}`);
324
+ return {
325
+ id: task.id,
326
+ planId: task.planId,
327
+ bucketId: task.bucketId,
328
+ title: task.title,
329
+ progress: progressToSemantic(task.percentComplete),
330
+ percentComplete: task.percentComplete,
331
+ priority: PRIORITY_REVERSE[task.priority] || 'medium',
332
+ dueDateTime: task.dueDateTime,
333
+ startDateTime: task.startDateTime,
334
+ assignedTo: Object.keys(task.assignments || {}),
335
+ createdDateTime: task.createdDateTime,
336
+ };
337
+ }
338
+ export async function createPlannerTask(params) {
339
+ const { planId, title, bucketId, assignments, dueDateTime, startDateTime, priority, progress } = params;
340
+ const body = {
341
+ planId,
342
+ title,
343
+ };
344
+ if (bucketId)
345
+ body.bucketId = bucketId;
346
+ if (dueDateTime)
347
+ body.dueDateTime = dueDateTime;
348
+ if (startDateTime)
349
+ body.startDateTime = startDateTime;
350
+ if (priority)
351
+ body.priority = PRIORITY_MAP[priority];
352
+ if (progress)
353
+ body.percentComplete = PROGRESS_MAP[progress];
354
+ // Resolve assignments to user IDs
355
+ if (assignments && assignments.length > 0) {
356
+ const assignmentObj = {};
357
+ for (const email of assignments) {
358
+ const userId = await resolveUserId(email);
359
+ assignmentObj[userId] = {
360
+ '@odata.type': '#microsoft.graph.plannerAssignment',
361
+ orderHint: ' !',
362
+ };
363
+ }
364
+ body.assignments = assignmentObj;
365
+ }
366
+ const task = await graphRequest('/planner/tasks', {
367
+ method: 'POST',
368
+ body,
369
+ });
370
+ return {
371
+ success: true,
372
+ taskId: task.id,
373
+ title: task.title,
374
+ };
375
+ }
376
+ export async function updatePlannerTask(params) {
377
+ const { taskId, title, bucketId, assignments, clearAssignments, dueDateTime, clearDue, startDateTime, priority, progress } = params;
378
+ // Fetch task to get ETag and current assignments (if clearing)
379
+ const currentTask = await graphRequest(`/planner/tasks/${taskId}`);
380
+ const etag = currentTask['@odata.etag'];
381
+ const body = {};
382
+ if (title)
383
+ body.title = title;
384
+ if (bucketId)
385
+ body.bucketId = bucketId;
386
+ if (clearDue) {
387
+ body.dueDateTime = null;
388
+ }
389
+ else if (dueDateTime) {
390
+ body.dueDateTime = dueDateTime;
391
+ }
392
+ if (startDateTime)
393
+ body.startDateTime = startDateTime;
394
+ if (priority)
395
+ body.priority = PRIORITY_MAP[priority];
396
+ if (progress)
397
+ body.percentComplete = PROGRESS_MAP[progress];
398
+ // Handle assignments
399
+ if (clearAssignments) {
400
+ // Set all current assignments to null to remove them
401
+ const assignmentObj = {};
402
+ const currentAssignments = currentTask.assignments || {};
403
+ for (const userId of Object.keys(currentAssignments)) {
404
+ assignmentObj[userId] = null;
405
+ }
406
+ if (Object.keys(assignmentObj).length > 0) {
407
+ body.assignments = assignmentObj;
408
+ }
409
+ }
410
+ else if (assignments) {
411
+ // Resolve assignments to user IDs
412
+ const assignmentObj = {};
413
+ for (const email of assignments) {
414
+ const userId = await resolveUserId(email);
415
+ assignmentObj[userId] = {
416
+ '@odata.type': '#microsoft.graph.plannerAssignment',
417
+ orderHint: ' !',
418
+ };
419
+ }
420
+ body.assignments = assignmentObj;
421
+ }
422
+ const task = await graphRequest(`/planner/tasks/${taskId}`, {
423
+ method: 'PATCH',
424
+ body,
425
+ headers: { 'If-Match': etag },
426
+ });
427
+ return {
428
+ success: true,
429
+ taskId: task.id,
430
+ title: task.title,
431
+ };
432
+ }
433
+ export async function deletePlannerTask(params) {
434
+ const { taskId } = params;
435
+ // Fetch ETag first
436
+ const etag = await getETag(`/planner/tasks/${taskId}`);
437
+ await graphRequest(`/planner/tasks/${taskId}`, {
438
+ method: 'DELETE',
439
+ headers: { 'If-Match': etag },
440
+ });
441
+ return { success: true, message: 'Task deleted' };
442
+ }
443
+ // ============================================================================
444
+ // Task Details
445
+ // ============================================================================
446
+ export async function getPlannerTaskDetails(params) {
447
+ const { taskId } = params;
448
+ const details = await graphRequest(`/planner/tasks/${taskId}/details`);
449
+ // Transform checklist to array for easier consumption
450
+ const checklistItems = Object.entries(details.checklist || {}).map(([id, item]) => ({
451
+ id,
452
+ title: item.title,
453
+ isChecked: item.isChecked,
454
+ }));
455
+ // Transform references to array
456
+ const referenceItems = Object.entries(details.references || {}).map(([url, ref]) => ({
457
+ url: decodeURIComponent(url),
458
+ alias: ref.alias,
459
+ type: ref.type,
460
+ }));
461
+ return {
462
+ taskId,
463
+ description: details.description,
464
+ checklist: checklistItems,
465
+ references: referenceItems,
466
+ };
467
+ }
468
+ export async function updatePlannerTaskDetails(params) {
469
+ const { taskId, description, checklist } = params;
470
+ // Fetch ETag first
471
+ const etag = await getETag(`/planner/tasks/${taskId}/details`);
472
+ const body = {};
473
+ if (description !== undefined) {
474
+ body.description = description;
475
+ }
476
+ // Transform checklist array to object format
477
+ if (checklist) {
478
+ const checklistObj = {};
479
+ checklist.forEach((item, index) => {
480
+ // Generate a simple GUID-like key for new items
481
+ const key = crypto.randomUUID();
482
+ checklistObj[key] = {
483
+ '@odata.type': 'microsoft.graph.plannerChecklistItem',
484
+ title: item.title,
485
+ isChecked: item.isChecked ?? false,
486
+ };
487
+ });
488
+ body.checklist = checklistObj;
489
+ }
490
+ await graphRequest(`/planner/tasks/${taskId}/details`, {
491
+ method: 'PATCH',
492
+ body,
493
+ headers: { 'If-Match': etag },
494
+ });
495
+ return { success: true, message: 'Task details updated' };
496
+ }
497
+ // ============================================================================
498
+ // Checklist Operations
499
+ // ============================================================================
500
+ export async function addChecklistItem(params) {
501
+ const { taskId, title, isChecked = false } = params;
502
+ // Fetch ETag first
503
+ const etag = await getETag(`/planner/tasks/${taskId}/details`);
504
+ // Generate a new item ID
505
+ const itemId = crypto.randomUUID();
506
+ const body = {
507
+ checklist: {
508
+ [itemId]: {
509
+ '@odata.type': 'microsoft.graph.plannerChecklistItem',
510
+ title,
511
+ isChecked,
512
+ },
513
+ },
514
+ };
515
+ await graphRequest(`/planner/tasks/${taskId}/details`, {
516
+ method: 'PATCH',
517
+ body,
518
+ headers: { 'If-Match': etag },
519
+ });
520
+ return {
521
+ success: true,
522
+ message: 'Checklist item added',
523
+ itemId,
524
+ title,
525
+ isChecked,
526
+ };
527
+ }
528
+ export async function removeChecklistItem(params) {
529
+ const { taskId, itemId } = params;
530
+ // Fetch ETag first
531
+ const etag = await getETag(`/planner/tasks/${taskId}/details`);
532
+ // Set to null to remove
533
+ const body = {
534
+ checklist: {
535
+ [itemId]: null,
536
+ },
537
+ };
538
+ await graphRequest(`/planner/tasks/${taskId}/details`, {
539
+ method: 'PATCH',
540
+ body,
541
+ headers: { 'If-Match': etag },
542
+ });
543
+ return {
544
+ success: true,
545
+ message: 'Checklist item removed',
546
+ itemId,
547
+ };
548
+ }
549
+ export async function toggleChecklistItem(params) {
550
+ const { taskId, itemId } = params;
551
+ // Fetch current details to get the current state
552
+ const details = await graphRequest(`/planner/tasks/${taskId}/details`);
553
+ const etag = details['@odata.etag'];
554
+ if (!etag) {
555
+ throw new Error('Could not get ETag for task details');
556
+ }
557
+ const currentItem = details.checklist?.[itemId];
558
+ if (!currentItem) {
559
+ throw new Error(`Checklist item ${itemId} not found`);
560
+ }
561
+ const newCheckedState = !currentItem.isChecked;
562
+ const body = {
563
+ checklist: {
564
+ [itemId]: {
565
+ '@odata.type': 'microsoft.graph.plannerChecklistItem',
566
+ title: currentItem.title,
567
+ isChecked: newCheckedState,
568
+ },
569
+ },
570
+ };
571
+ await graphRequest(`/planner/tasks/${taskId}/details`, {
572
+ method: 'PATCH',
573
+ body,
574
+ headers: { 'If-Match': etag },
575
+ });
576
+ return {
577
+ success: true,
578
+ message: `Checklist item ${newCheckedState ? 'checked' : 'unchecked'}`,
579
+ itemId,
580
+ title: currentItem.title,
581
+ isChecked: newCheckedState,
582
+ };
583
+ }
584
+ // ============================================================================
585
+ // References (Attachments)
586
+ // ============================================================================
587
+ export async function addPlannerTaskReference(params) {
588
+ const { taskId, url, alias, type } = params;
589
+ // Fetch ETag first
590
+ const etag = await getETag(`/planner/tasks/${taskId}/details`);
591
+ // Encode URL for use as object key
592
+ const encodedUrl = encodeReferenceUrl(url);
593
+ // Auto-detect type if not provided
594
+ const referenceType = type || detectReferenceType(url);
595
+ // Build reference object
596
+ const reference = {
597
+ '@odata.type': 'microsoft.graph.plannerExternalReference',
598
+ type: referenceType,
599
+ previewPriority: ' !',
600
+ };
601
+ if (alias) {
602
+ reference.alias = alias;
603
+ }
604
+ const body = {
605
+ references: {
606
+ [encodedUrl]: reference,
607
+ },
608
+ };
609
+ await graphRequest(`/planner/tasks/${taskId}/details`, {
610
+ method: 'PATCH',
611
+ body,
612
+ headers: { 'If-Match': etag },
613
+ });
614
+ return {
615
+ success: true,
616
+ message: 'Reference added to task',
617
+ url,
618
+ alias: alias || url,
619
+ type: referenceType,
620
+ };
621
+ }
622
+ export async function removePlannerTaskReference(params) {
623
+ const { taskId, url } = params;
624
+ // Fetch ETag first
625
+ const etag = await getETag(`/planner/tasks/${taskId}/details`);
626
+ // Encode URL for use as object key
627
+ const encodedUrl = encodeReferenceUrl(url);
628
+ // Set to null to remove
629
+ const body = {
630
+ references: {
631
+ [encodedUrl]: null,
632
+ },
633
+ };
634
+ await graphRequest(`/planner/tasks/${taskId}/details`, {
635
+ method: 'PATCH',
636
+ body,
637
+ headers: { 'If-Match': etag },
638
+ });
639
+ return {
640
+ success: true,
641
+ message: 'Reference removed from task',
642
+ url,
643
+ };
644
+ }
645
+ export async function uploadAndAttach(params) {
646
+ const { taskId, localPath, remotePath, alias } = params;
647
+ const filename = basename(localPath);
648
+ // Step 1: Get task to find planId
649
+ const task = await graphRequest(`/planner/tasks/${taskId}`);
650
+ // Step 2: Get plan to find group (owner) and plan title
651
+ const plan = await graphRequest(`/planner/plans/${task.planId}`);
652
+ const groupId = plan.owner;
653
+ const planTitle = plan.title.replace(/[/\\?%*:|"<>]/g, '-'); // Sanitize for folder name
654
+ // Step 3: Get the group's default drive (SharePoint document library)
655
+ const drive = await graphRequest(`/groups/${groupId}/drive`);
656
+ // Step 4: Read local file
657
+ const content = await readFile(localPath);
658
+ // Step 5: Determine destination path in SharePoint
659
+ const destPath = remotePath || `Planner Attachments/${planTitle}/${filename}`;
660
+ // Step 6: Upload to group's SharePoint
661
+ const MAX_SIMPLE_UPLOAD = 4 * 1024 * 1024;
662
+ let uploaded;
663
+ if (content.length <= MAX_SIMPLE_UPLOAD) {
664
+ uploaded = await graphUpload(`/drives/${drive.id}/root:/${destPath}:/content`, content);
665
+ }
666
+ else {
667
+ uploaded = await graphUploadLarge(`/drives/${drive.id}/root:/${destPath}`, content);
668
+ }
669
+ // Step 7: Attach the uploaded file's URL to the task
670
+ const attachResult = await addPlannerTaskReference({
671
+ taskId,
672
+ url: uploaded.webUrl,
673
+ alias: alias || filename,
674
+ });
675
+ return {
676
+ success: true,
677
+ message: 'File uploaded to SharePoint and attached to task',
678
+ file: {
679
+ name: uploaded.name,
680
+ size: uploaded.size,
681
+ webUrl: uploaded.webUrl,
682
+ },
683
+ attachment: {
684
+ alias: alias || filename,
685
+ type: attachResult.type,
686
+ },
687
+ location: {
688
+ sharePointSite: drive.webUrl,
689
+ path: destPath,
690
+ },
691
+ };
692
+ }
693
+ //# sourceMappingURL=planner.js.map