@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
package/email/delta.js ADDED
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Delta sync functionality for email
3
+ *
4
+ * Provides incremental sync using Microsoft Graph delta queries.
5
+ * Returns only changes since last sync, with deltaToken for next call.
6
+ */
7
+ const { callGraphAPI } = require('../utils/graph-api');
8
+ const { ensureAuthenticated } = require('../auth');
9
+ const { formatEmailList, VERBOSITY } = require('../utils/response-formatter');
10
+ const { getEmailFields } = require('../utils/field-presets');
11
+
12
+ /**
13
+ * List emails delta handler - incremental sync
14
+ * @param {object} args - Tool arguments
15
+ * @param {string} [args.folder] - Folder to sync (default: inbox)
16
+ * @param {string} [args.deltaToken] - Token from previous delta call (omit for initial sync)
17
+ * @param {number} [args.maxResults] - Max results per page (default: 100)
18
+ * @param {string} [args.outputVerbosity] - Output detail level
19
+ * @returns {object} - MCP response with emails, deltaToken, and change summary
20
+ */
21
+ async function handleListEmailsDelta(args) {
22
+ const folder = args.folder || 'inbox';
23
+ const deltaToken = args.deltaToken;
24
+ const maxResults = Math.min(args.maxResults || 100, 200);
25
+ const verbosity = args.outputVerbosity || 'standard';
26
+
27
+ try {
28
+ const accessToken = await ensureAuthenticated();
29
+
30
+ // Build delta query
31
+ let endpoint;
32
+ let queryParams = {};
33
+
34
+ if (deltaToken) {
35
+ // Continue from previous sync - use deltaLink directly
36
+ endpoint = deltaToken;
37
+ } else {
38
+ // Initial sync - start fresh
39
+ endpoint = `me/mailFolders/${folder}/messages/delta`;
40
+ queryParams = {
41
+ $select: getEmailFields('delta'),
42
+ $top: maxResults.toString(),
43
+ };
44
+ }
45
+
46
+ // Fetch delta results
47
+ const response = await callGraphAPI(
48
+ accessToken,
49
+ 'GET',
50
+ endpoint,
51
+ null,
52
+ deltaToken ? {} : queryParams
53
+ );
54
+
55
+ // Process results
56
+ const emails = response.value || [];
57
+ const nextLink = response['@odata.nextLink'];
58
+ const deltaLink = response['@odata.deltaLink'];
59
+
60
+ // Categorise changes
61
+ const changesSummary = {
62
+ created: 0,
63
+ updated: 0,
64
+ deleted: 0,
65
+ };
66
+
67
+ const processedEmails = [];
68
+ for (const email of emails) {
69
+ if (email['@removed']) {
70
+ // Deleted email
71
+ changesSummary.deleted++;
72
+ processedEmails.push({
73
+ id: email.id,
74
+ removed: true,
75
+ reason: email['@removed'].reason || 'deleted',
76
+ });
77
+ } else if (deltaToken) {
78
+ // With deltaToken, all non-removed items are changes
79
+ // We can't reliably distinguish created vs updated via delta
80
+ changesSummary.updated++;
81
+ processedEmails.push(email);
82
+ } else {
83
+ // Initial sync - all items are "created" for our purposes
84
+ changesSummary.created++;
85
+ processedEmails.push(email);
86
+ }
87
+ }
88
+
89
+ // Build response
90
+ const isInitialSync = !deltaToken;
91
+ const hasMoreChanges = !!nextLink;
92
+ const newDeltaToken = deltaLink || nextLink;
93
+
94
+ // Format output based on verbosity
95
+ let resultText;
96
+ if (verbosity === 'minimal') {
97
+ resultText = `## Delta Sync\n\n`;
98
+ resultText += `| Metric | Value |\n`;
99
+ resultText += `|--------|-------|\n`;
100
+ resultText += `| Items | ${processedEmails.length} |\n`;
101
+ resultText += `| Type | ${isInitialSync ? 'Initial' : 'Incremental'} |\n`;
102
+ resultText += `| More | ${hasMoreChanges ? 'Yes' : 'No'} |\n`;
103
+ if (newDeltaToken) {
104
+ resultText += `\n**Delta Token** (save for next call):\n\`\`\`\n${newDeltaToken}\n\`\`\`\n`;
105
+ }
106
+ } else {
107
+ resultText = `## Delta Sync ${isInitialSync ? '(Initial)' : '(Incremental)'}\n\n`;
108
+
109
+ // Changes summary
110
+ resultText += `### Changes Summary\n\n`;
111
+ resultText += `| Change Type | Count |\n`;
112
+ resultText += `|-------------|-------|\n`;
113
+ if (!isInitialSync) {
114
+ resultText += `| Created/Updated | ${changesSummary.updated} |\n`;
115
+ resultText += `| Deleted | ${changesSummary.deleted} |\n`;
116
+ } else {
117
+ resultText += `| Synced | ${changesSummary.created} |\n`;
118
+ }
119
+ resultText += `| Total | ${processedEmails.length} |\n`;
120
+
121
+ // Emails list (non-deleted only)
122
+ const activeEmails = processedEmails.filter((e) => !e.removed);
123
+ if (activeEmails.length > 0) {
124
+ resultText += `\n### Emails\n\n`;
125
+ resultText += formatEmailList(
126
+ activeEmails,
127
+ verbosity === 'full' ? VERBOSITY.FULL : VERBOSITY.STANDARD
128
+ );
129
+ }
130
+
131
+ // Deleted items
132
+ const deletedEmails = processedEmails.filter((e) => e.removed);
133
+ if (deletedEmails.length > 0) {
134
+ resultText += `\n### Deleted Items (${deletedEmails.length})\n\n`;
135
+ for (const del of deletedEmails.slice(0, 10)) {
136
+ resultText += `- ID: \`${del.id}\`\n`;
137
+ }
138
+ if (deletedEmails.length > 10) {
139
+ resultText += `- ... and ${deletedEmails.length - 10} more\n`;
140
+ }
141
+ }
142
+
143
+ // Pagination info
144
+ if (hasMoreChanges) {
145
+ resultText += `\n### More Changes Available\n`;
146
+ resultText += `Use the deltaToken below to fetch next page.\n`;
147
+ }
148
+
149
+ // Delta token
150
+ if (newDeltaToken) {
151
+ resultText += `\n### Delta Token\n`;
152
+ resultText += `**Save this token for next sync call:**\n\`\`\`\n${newDeltaToken}\n\`\`\`\n`;
153
+ }
154
+ }
155
+
156
+ return {
157
+ content: [
158
+ {
159
+ type: 'text',
160
+ text: resultText,
161
+ },
162
+ ],
163
+ _meta: {
164
+ syncType: isInitialSync ? 'initial' : 'incremental',
165
+ folder: folder,
166
+ itemCount: processedEmails.length,
167
+ hasMoreChanges: hasMoreChanges,
168
+ changesSummary: changesSummary,
169
+ deltaToken: newDeltaToken,
170
+ },
171
+ };
172
+ } catch (error) {
173
+ if (error.message === 'Authentication required') {
174
+ return {
175
+ content: [
176
+ {
177
+ type: 'text',
178
+ text: "Authentication required. Please use the 'authenticate' tool first.",
179
+ },
180
+ ],
181
+ };
182
+ }
183
+
184
+ // Handle expired delta token
185
+ if (
186
+ error.message.includes('410') ||
187
+ error.message.includes('resyncRequired')
188
+ ) {
189
+ return {
190
+ content: [
191
+ {
192
+ type: 'text',
193
+ text: `## Delta Token Expired\n\nThe provided delta token has expired. Please start a new initial sync by calling without a deltaToken.\n\n**Error:** ${error.message}`,
194
+ },
195
+ ],
196
+ };
197
+ }
198
+
199
+ return {
200
+ content: [
201
+ {
202
+ type: 'text',
203
+ text: `Delta sync failed: ${error.message}`,
204
+ },
205
+ ],
206
+ };
207
+ }
208
+ }
209
+
210
+ module.exports = handleListEmailsDelta;