@littlebearapps/outlook-assistant 3.3.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 (52) hide show
  1. package/.env.example +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +422 -0
  4. package/advanced/index.js +652 -0
  5. package/auth/index.js +32 -0
  6. package/auth/oauth-server.js +233 -0
  7. package/auth/token-manager.js +105 -0
  8. package/auth/token-storage.js +359 -0
  9. package/auth/tools.js +159 -0
  10. package/calendar/accept.js +72 -0
  11. package/calendar/cancel.js +72 -0
  12. package/calendar/create.js +115 -0
  13. package/calendar/decline.js +72 -0
  14. package/calendar/delete.js +67 -0
  15. package/calendar/index.js +130 -0
  16. package/calendar/list.js +108 -0
  17. package/categories/index.js +955 -0
  18. package/config.js +95 -0
  19. package/contacts/index.js +754 -0
  20. package/email/attachments.js +365 -0
  21. package/email/conversations.js +666 -0
  22. package/email/delta.js +210 -0
  23. package/email/export.js +572 -0
  24. package/email/folder-utils.js +192 -0
  25. package/email/headers.js +344 -0
  26. package/email/index.js +537 -0
  27. package/email/list.js +136 -0
  28. package/email/mark-as-read.js +114 -0
  29. package/email/mime.js +286 -0
  30. package/email/read.js +161 -0
  31. package/email/search.js +628 -0
  32. package/email/send.js +169 -0
  33. package/folder/create.js +137 -0
  34. package/folder/delete.js +108 -0
  35. package/folder/index.js +112 -0
  36. package/folder/list.js +289 -0
  37. package/folder/move.js +186 -0
  38. package/folder/stats.js +322 -0
  39. package/index.js +162 -0
  40. package/llms.txt +76 -0
  41. package/outlook-auth-server.js +384 -0
  42. package/package.json +97 -0
  43. package/rules/create.js +273 -0
  44. package/rules/index.js +276 -0
  45. package/rules/list.js +216 -0
  46. package/settings/index.js +678 -0
  47. package/utils/field-presets.js +311 -0
  48. package/utils/graph-api.js +268 -0
  49. package/utils/mock-data.js +154 -0
  50. package/utils/odata-helpers.js +33 -0
  51. package/utils/response-formatter.js +457 -0
  52. package/utils/safety.js +123 -0
@@ -0,0 +1,678 @@
1
+ /**
2
+ * Settings module for Outlook Assistant server
3
+ *
4
+ * Manages mailbox settings, automatic replies (out-of-office),
5
+ * and working hours configuration.
6
+ */
7
+ const { callGraphAPI } = require('../utils/graph-api');
8
+ const { ensureAuthenticated } = require('../auth');
9
+
10
+ // Days of the week for working hours
11
+ const DAYS_OF_WEEK = [
12
+ 'sunday',
13
+ 'monday',
14
+ 'tuesday',
15
+ 'wednesday',
16
+ 'thursday',
17
+ 'friday',
18
+ 'saturday',
19
+ ];
20
+
21
+ /**
22
+ * Format time for display (HH:MM)
23
+ */
24
+ function formatTime(timeString) {
25
+ if (!timeString) return 'Not set';
26
+ // Graph API returns time in ISO format like "08:00:00.0000000"
27
+ return timeString.substring(0, 5);
28
+ }
29
+
30
+ /**
31
+ * Format working hours for display
32
+ */
33
+ function formatWorkingHours(workingHours) {
34
+ if (!workingHours) return 'Not configured';
35
+
36
+ const lines = [];
37
+ lines.push(`**Time Zone**: ${workingHours.timeZone?.name || 'Not set'}`);
38
+ lines.push(`**Start Time**: ${formatTime(workingHours.startTime)}`);
39
+ lines.push(`**End Time**: ${formatTime(workingHours.endTime)}`);
40
+
41
+ if (workingHours.daysOfWeek && workingHours.daysOfWeek.length > 0) {
42
+ const days = workingHours.daysOfWeek.map(
43
+ (d) => d.charAt(0).toUpperCase() + d.slice(1)
44
+ );
45
+ lines.push(`**Work Days**: ${days.join(', ')}`);
46
+ }
47
+
48
+ return lines.join('\n');
49
+ }
50
+
51
+ /**
52
+ * Format automatic replies for display
53
+ */
54
+ function formatAutomaticReplies(settings) {
55
+ if (!settings) return 'Not configured';
56
+
57
+ const lines = [];
58
+ lines.push(
59
+ `**Status**: ${
60
+ settings.status === 'disabled'
61
+ ? '🔴 Disabled'
62
+ : settings.status === 'alwaysEnabled'
63
+ ? '🟢 Always Enabled'
64
+ : '📅 Scheduled'
65
+ }`
66
+ );
67
+
68
+ if (settings.status !== 'disabled') {
69
+ if (settings.scheduledStartDateTime) {
70
+ lines.push(
71
+ `**Scheduled Start**: ${new Date(settings.scheduledStartDateTime.dateTime).toLocaleString()}`
72
+ );
73
+ }
74
+ if (settings.scheduledEndDateTime) {
75
+ lines.push(
76
+ `**Scheduled End**: ${new Date(settings.scheduledEndDateTime.dateTime).toLocaleString()}`
77
+ );
78
+ }
79
+
80
+ if (settings.internalReplyMessage) {
81
+ lines.push(
82
+ `\n**Internal Reply**:\n${settings.internalReplyMessage.substring(0, 500)}${settings.internalReplyMessage.length > 500 ? '...' : ''}`
83
+ );
84
+ }
85
+
86
+ if (settings.externalReplyMessage) {
87
+ lines.push(
88
+ `\n**External Reply**:\n${settings.externalReplyMessage.substring(0, 500)}${settings.externalReplyMessage.length > 500 ? '...' : ''}`
89
+ );
90
+ }
91
+
92
+ lines.push(
93
+ `\n**External Audience**: ${settings.externalAudience || 'none'}`
94
+ );
95
+ }
96
+
97
+ return lines.join('\n');
98
+ }
99
+
100
+ /**
101
+ * Get mailbox settings handler
102
+ */
103
+ async function handleGetMailboxSettings(args) {
104
+ const section = args.section; // Optional: 'all', 'language', 'timezone', 'workingHours', 'automaticReplies'
105
+
106
+ try {
107
+ const accessToken = await ensureAuthenticated();
108
+
109
+ let endpoint = 'me/mailboxSettings';
110
+ if (section && section !== 'all') {
111
+ endpoint = `me/mailboxSettings/${section}`;
112
+ }
113
+
114
+ const settings = await callGraphAPI(accessToken, 'GET', endpoint);
115
+
116
+ let output = [];
117
+ output.push('# Mailbox Settings\n');
118
+
119
+ if (section && section !== 'all') {
120
+ // Single section — format based on section type
121
+ const sectionTitle = section.charAt(0).toUpperCase() + section.slice(1);
122
+ output.push(`## ${sectionTitle}\n`);
123
+
124
+ if (section === 'workingHours') {
125
+ output.push(formatWorkingHours(settings));
126
+ } else if (section === 'automaticRepliesSetting') {
127
+ output.push(formatAutomaticReplies(settings));
128
+ } else if (section === 'language') {
129
+ output.push(`**Locale**: ${settings.locale || 'Not set'}`);
130
+ output.push(`**Display Name**: ${settings.displayName || 'Not set'}`);
131
+ } else if (section === 'timeZone') {
132
+ output.push(`**Zone**: ${settings}`);
133
+ } else {
134
+ // Fallback for unknown sections
135
+ output.push('```json');
136
+ output.push(JSON.stringify(settings, null, 2));
137
+ output.push('```');
138
+ }
139
+ } else {
140
+ // All settings
141
+ if (settings.language) {
142
+ output.push('## Language');
143
+ output.push(`**Locale**: ${settings.language.locale || 'Not set'}`);
144
+ output.push(
145
+ `**Display Name**: ${settings.language.displayName || 'Not set'}`
146
+ );
147
+ output.push('');
148
+ }
149
+
150
+ if (settings.timeZone) {
151
+ output.push('## Time Zone');
152
+ output.push(`**Zone**: ${settings.timeZone}`);
153
+ output.push('');
154
+ }
155
+
156
+ if (settings.dateFormat) {
157
+ output.push('## Date/Time Format');
158
+ output.push(`**Date Format**: ${settings.dateFormat}`);
159
+ output.push(`**Time Format**: ${settings.timeFormat || 'Not set'}`);
160
+ output.push('');
161
+ }
162
+
163
+ if (settings.workingHours) {
164
+ output.push('## Working Hours');
165
+ output.push(formatWorkingHours(settings.workingHours));
166
+ output.push('');
167
+ }
168
+
169
+ if (settings.automaticRepliesSetting) {
170
+ output.push('## Automatic Replies (Out of Office)');
171
+ output.push(formatAutomaticReplies(settings.automaticRepliesSetting));
172
+ output.push('');
173
+ }
174
+
175
+ if (settings.delegateMeetingMessageDeliveryOptions) {
176
+ output.push('## Delegate Settings');
177
+ output.push(
178
+ `**Meeting Message Delivery**: ${settings.delegateMeetingMessageDeliveryOptions}`
179
+ );
180
+ }
181
+ }
182
+
183
+ return {
184
+ content: [
185
+ {
186
+ type: 'text',
187
+ text: output.join('\n'),
188
+ },
189
+ ],
190
+ _meta: {
191
+ settings,
192
+ },
193
+ };
194
+ } catch (error) {
195
+ if (error.message === 'Authentication required') {
196
+ return {
197
+ content: [
198
+ {
199
+ type: 'text',
200
+ text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
201
+ },
202
+ ],
203
+ };
204
+ }
205
+ return {
206
+ content: [
207
+ {
208
+ type: 'text',
209
+ text: `Error getting mailbox settings: ${error.message}`,
210
+ },
211
+ ],
212
+ };
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Set automatic replies (out of office) handler
218
+ */
219
+ async function handleSetAutomaticReplies(args) {
220
+ const {
221
+ enabled,
222
+ startDateTime,
223
+ endDateTime,
224
+ internalReplyMessage,
225
+ externalReplyMessage,
226
+ externalAudience,
227
+ } = args;
228
+
229
+ try {
230
+ const accessToken = await ensureAuthenticated();
231
+
232
+ // Build the settings object
233
+ const settings = {};
234
+
235
+ // Determine status
236
+ if (enabled === false) {
237
+ settings.status = 'disabled';
238
+ } else if (startDateTime && endDateTime) {
239
+ settings.status = 'scheduled';
240
+ settings.scheduledStartDateTime = {
241
+ dateTime: new Date(startDateTime).toISOString(),
242
+ timeZone: 'UTC',
243
+ };
244
+ settings.scheduledEndDateTime = {
245
+ dateTime: new Date(endDateTime).toISOString(),
246
+ timeZone: 'UTC',
247
+ };
248
+ } else if (enabled === true) {
249
+ settings.status = 'alwaysEnabled';
250
+ }
251
+
252
+ // Reply messages
253
+ if (internalReplyMessage !== undefined) {
254
+ settings.internalReplyMessage = internalReplyMessage;
255
+ }
256
+
257
+ if (externalReplyMessage !== undefined) {
258
+ settings.externalReplyMessage = externalReplyMessage;
259
+ }
260
+
261
+ // External audience
262
+ if (externalAudience) {
263
+ if (!['none', 'contactsOnly', 'all'].includes(externalAudience)) {
264
+ return {
265
+ content: [
266
+ {
267
+ type: 'text',
268
+ text: "externalAudience must be 'none', 'contactsOnly', or 'all'.",
269
+ },
270
+ ],
271
+ };
272
+ }
273
+ settings.externalAudience = externalAudience;
274
+ }
275
+
276
+ // Apply settings
277
+ await callGraphAPI(accessToken, 'PATCH', 'me/mailboxSettings', {
278
+ automaticRepliesSetting: settings,
279
+ });
280
+
281
+ // Get updated settings
282
+ const updated = await callGraphAPI(
283
+ accessToken,
284
+ 'GET',
285
+ 'me/mailboxSettings/automaticRepliesSetting'
286
+ );
287
+
288
+ let output = [];
289
+ output.push('Automatic replies updated!\n');
290
+ output.push(formatAutomaticReplies(updated));
291
+
292
+ // Warn if enabling without messages
293
+ if (
294
+ updated.status !== 'disabled' &&
295
+ !internalReplyMessage &&
296
+ !externalReplyMessage
297
+ ) {
298
+ if (updated.internalReplyMessage || updated.externalReplyMessage) {
299
+ const preview = (
300
+ updated.internalReplyMessage || updated.externalReplyMessage
301
+ ).substring(0, 100);
302
+ output.push(
303
+ `\n**Note**: Using previously configured message: "${preview}${preview.length >= 100 ? '...' : ''}"`
304
+ );
305
+ } else {
306
+ output.push(
307
+ '\n**Warning**: Auto-replies enabled with no message configured. Recipients will receive a blank reply.'
308
+ );
309
+ }
310
+ }
311
+
312
+ return {
313
+ content: [
314
+ {
315
+ type: 'text',
316
+ text: output.join('\n'),
317
+ },
318
+ ],
319
+ _meta: {
320
+ settings: updated,
321
+ },
322
+ };
323
+ } catch (error) {
324
+ if (error.message === 'Authentication required') {
325
+ return {
326
+ content: [
327
+ {
328
+ type: 'text',
329
+ text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
330
+ },
331
+ ],
332
+ };
333
+ }
334
+ return {
335
+ content: [
336
+ {
337
+ type: 'text',
338
+ text: `Error setting automatic replies: ${error.message}`,
339
+ },
340
+ ],
341
+ };
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Set working hours handler
347
+ */
348
+ async function handleSetWorkingHours(args) {
349
+ const { startTime, endTime, daysOfWeek, timeZone } = args;
350
+
351
+ // Validate inputs
352
+ if (!startTime && !endTime && !daysOfWeek && !timeZone) {
353
+ return {
354
+ content: [
355
+ {
356
+ type: 'text',
357
+ text: 'At least one of startTime, endTime, daysOfWeek, or timeZone is required.',
358
+ },
359
+ ],
360
+ };
361
+ }
362
+
363
+ // Validate time format (HH:MM or HH:MM:SS)
364
+ const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/;
365
+ if (startTime && !timeRegex.test(startTime)) {
366
+ return {
367
+ content: [
368
+ {
369
+ type: 'text',
370
+ text: "startTime must be in HH:MM or HH:MM:SS format (e.g., '09:00' or '09:00:00').",
371
+ },
372
+ ],
373
+ };
374
+ }
375
+ if (endTime && !timeRegex.test(endTime)) {
376
+ return {
377
+ content: [
378
+ {
379
+ type: 'text',
380
+ text: "endTime must be in HH:MM or HH:MM:SS format (e.g., '17:00' or '17:00:00').",
381
+ },
382
+ ],
383
+ };
384
+ }
385
+
386
+ // Validate days of week
387
+ if (daysOfWeek) {
388
+ const invalidDays = daysOfWeek.filter(
389
+ (d) => !DAYS_OF_WEEK.includes(d.toLowerCase())
390
+ );
391
+ if (invalidDays.length > 0) {
392
+ return {
393
+ content: [
394
+ {
395
+ type: 'text',
396
+ text: `Invalid days: ${invalidDays.join(', ')}. Valid days: ${DAYS_OF_WEEK.join(', ')}`,
397
+ },
398
+ ],
399
+ };
400
+ }
401
+ }
402
+
403
+ try {
404
+ const accessToken = await ensureAuthenticated();
405
+
406
+ // Get current settings first
407
+ const current = await callGraphAPI(
408
+ accessToken,
409
+ 'GET',
410
+ 'me/mailboxSettings/workingHours'
411
+ );
412
+
413
+ // Build update
414
+ const workingHours = {
415
+ startTime: startTime
416
+ ? startTime.length === 5
417
+ ? startTime + ':00.0000000'
418
+ : startTime + '.0000000'
419
+ : current.startTime,
420
+ endTime: endTime
421
+ ? endTime.length === 5
422
+ ? endTime + ':00.0000000'
423
+ : endTime + '.0000000'
424
+ : current.endTime,
425
+ daysOfWeek: daysOfWeek
426
+ ? daysOfWeek.map((d) => d.toLowerCase())
427
+ : current.daysOfWeek,
428
+ timeZone: timeZone ? { name: timeZone } : current.timeZone,
429
+ };
430
+
431
+ // Apply settings
432
+ await callGraphAPI(accessToken, 'PATCH', 'me/mailboxSettings', {
433
+ workingHours,
434
+ });
435
+
436
+ // Get updated settings
437
+ const updated = await callGraphAPI(
438
+ accessToken,
439
+ 'GET',
440
+ 'me/mailboxSettings/workingHours'
441
+ );
442
+
443
+ let output = [];
444
+ output.push('Working hours updated!\n');
445
+ output.push(formatWorkingHours(updated));
446
+
447
+ return {
448
+ content: [
449
+ {
450
+ type: 'text',
451
+ text: output.join('\n'),
452
+ },
453
+ ],
454
+ _meta: {
455
+ settings: updated,
456
+ },
457
+ };
458
+ } catch (error) {
459
+ if (error.message === 'Authentication required') {
460
+ return {
461
+ content: [
462
+ {
463
+ type: 'text',
464
+ text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
465
+ },
466
+ ],
467
+ };
468
+ }
469
+ return {
470
+ content: [
471
+ {
472
+ type: 'text',
473
+ text: `Error setting working hours: ${error.message}`,
474
+ },
475
+ ],
476
+ };
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Get automatic replies settings handler (standalone)
482
+ */
483
+ async function handleGetAutomaticReplies() {
484
+ try {
485
+ const accessToken = await ensureAuthenticated();
486
+ const settings = await callGraphAPI(
487
+ accessToken,
488
+ 'GET',
489
+ 'me/mailboxSettings/automaticRepliesSetting'
490
+ );
491
+
492
+ let output = [];
493
+ output.push('# Automatic Replies\n');
494
+ output.push(formatAutomaticReplies(settings));
495
+
496
+ return {
497
+ content: [{ type: 'text', text: output.join('\n') }],
498
+ _meta: { settings },
499
+ };
500
+ } catch (error) {
501
+ if (error.message === 'Authentication required') {
502
+ return {
503
+ content: [
504
+ {
505
+ type: 'text',
506
+ text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
507
+ },
508
+ ],
509
+ };
510
+ }
511
+ return {
512
+ content: [
513
+ {
514
+ type: 'text',
515
+ text: `Error getting automatic replies: ${error.message}`,
516
+ },
517
+ ],
518
+ };
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Get working hours settings handler (standalone)
524
+ */
525
+ async function handleGetWorkingHours() {
526
+ try {
527
+ const accessToken = await ensureAuthenticated();
528
+ const settings = await callGraphAPI(
529
+ accessToken,
530
+ 'GET',
531
+ 'me/mailboxSettings/workingHours'
532
+ );
533
+
534
+ let output = [];
535
+ output.push('# Working Hours\n');
536
+ output.push(formatWorkingHours(settings));
537
+
538
+ return {
539
+ content: [{ type: 'text', text: output.join('\n') }],
540
+ _meta: { settings },
541
+ };
542
+ } catch (error) {
543
+ if (error.message === 'Authentication required') {
544
+ return {
545
+ content: [
546
+ {
547
+ type: 'text',
548
+ text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
549
+ },
550
+ ],
551
+ };
552
+ }
553
+ return {
554
+ content: [
555
+ {
556
+ type: 'text',
557
+ text: `Error getting working hours: ${error.message}`,
558
+ },
559
+ ],
560
+ };
561
+ }
562
+ }
563
+
564
+ // Consolidated tool definition
565
+ const settingsTools = [
566
+ {
567
+ name: 'mailbox-settings',
568
+ description:
569
+ 'Manage mailbox settings. action=get (default) retrieves settings (language, timezone, working hours, auto-replies). action=set-auto-replies configures out-of-office. action=set-working-hours configures work schedule.',
570
+ annotations: {
571
+ title: 'Mailbox Settings',
572
+ readOnlyHint: false,
573
+ destructiveHint: false,
574
+ idempotentHint: true,
575
+ },
576
+ inputSchema: {
577
+ type: 'object',
578
+ properties: {
579
+ action: {
580
+ type: 'string',
581
+ enum: ['get', 'set-auto-replies', 'set-working-hours'],
582
+ description: 'Action to perform (default: get)',
583
+ },
584
+ // get params
585
+ section: {
586
+ type: 'string',
587
+ enum: [
588
+ 'all',
589
+ 'language',
590
+ 'timeZone',
591
+ 'workingHours',
592
+ 'automaticRepliesSetting',
593
+ ],
594
+ description:
595
+ 'Specific section to retrieve (action=get, default: all)',
596
+ },
597
+ // set-auto-replies params
598
+ enabled: {
599
+ type: 'boolean',
600
+ description:
601
+ 'Enable (true) or disable (false) automatic replies (action=set-auto-replies)',
602
+ },
603
+ startDateTime: {
604
+ type: 'string',
605
+ description:
606
+ 'Start date/time for scheduled mode, ISO 8601 format (action=set-auto-replies)',
607
+ },
608
+ endDateTime: {
609
+ type: 'string',
610
+ description:
611
+ 'End date/time for scheduled mode, ISO 8601 format (action=set-auto-replies)',
612
+ },
613
+ internalReplyMessage: {
614
+ type: 'string',
615
+ description:
616
+ 'Reply message for internal senders (action=set-auto-replies)',
617
+ },
618
+ externalReplyMessage: {
619
+ type: 'string',
620
+ description:
621
+ 'Reply message for external senders (action=set-auto-replies)',
622
+ },
623
+ externalAudience: {
624
+ type: 'string',
625
+ enum: ['none', 'contactsOnly', 'all'],
626
+ description: 'Who receives external reply (action=set-auto-replies)',
627
+ },
628
+ // set-working-hours params
629
+ startTime: {
630
+ type: 'string',
631
+ description:
632
+ "Work start time in HH:MM format, e.g. '09:00' (action=set-working-hours)",
633
+ },
634
+ endTime: {
635
+ type: 'string',
636
+ description:
637
+ "Work end time in HH:MM format, e.g. '17:00' (action=set-working-hours)",
638
+ },
639
+ daysOfWeek: {
640
+ type: 'array',
641
+ items: {
642
+ type: 'string',
643
+ enum: DAYS_OF_WEEK,
644
+ },
645
+ description:
646
+ "Work days, e.g. ['monday','tuesday','wednesday','thursday','friday'] (action=set-working-hours)",
647
+ },
648
+ timeZone: {
649
+ type: 'string',
650
+ description:
651
+ "Time zone name, e.g. 'Australia/Melbourne' (action=set-working-hours)",
652
+ },
653
+ },
654
+ required: [],
655
+ },
656
+ handler: async (args) => {
657
+ const action = args.action || 'get';
658
+ switch (action) {
659
+ case 'set-auto-replies':
660
+ return handleSetAutomaticReplies(args);
661
+ case 'set-working-hours':
662
+ return handleSetWorkingHours(args);
663
+ case 'get':
664
+ default:
665
+ return handleGetMailboxSettings(args);
666
+ }
667
+ },
668
+ },
669
+ ];
670
+
671
+ module.exports = {
672
+ settingsTools,
673
+ handleGetMailboxSettings,
674
+ handleGetAutomaticReplies,
675
+ handleSetAutomaticReplies,
676
+ handleGetWorkingHours,
677
+ handleSetWorkingHours,
678
+ };