m365-agent-cli 1.2.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 (92) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +916 -0
  3. package/package.json +50 -0
  4. package/src/cli.ts +100 -0
  5. package/src/commands/auto-reply.ts +182 -0
  6. package/src/commands/calendar.ts +576 -0
  7. package/src/commands/counter.ts +87 -0
  8. package/src/commands/create-event.ts +544 -0
  9. package/src/commands/delegates.ts +286 -0
  10. package/src/commands/delete-event.ts +321 -0
  11. package/src/commands/drafts.ts +502 -0
  12. package/src/commands/files.ts +532 -0
  13. package/src/commands/find.ts +195 -0
  14. package/src/commands/findtime.ts +270 -0
  15. package/src/commands/folders.ts +177 -0
  16. package/src/commands/forward-event.ts +49 -0
  17. package/src/commands/graph-calendar.ts +217 -0
  18. package/src/commands/login.ts +195 -0
  19. package/src/commands/mail.ts +950 -0
  20. package/src/commands/oof.ts +263 -0
  21. package/src/commands/outlook-categories.ts +173 -0
  22. package/src/commands/outlook-graph.ts +880 -0
  23. package/src/commands/planner.ts +1678 -0
  24. package/src/commands/respond.ts +291 -0
  25. package/src/commands/rooms.ts +210 -0
  26. package/src/commands/rules.ts +511 -0
  27. package/src/commands/schedule.ts +109 -0
  28. package/src/commands/send.ts +204 -0
  29. package/src/commands/serve.ts +14 -0
  30. package/src/commands/sharepoint.ts +179 -0
  31. package/src/commands/site-pages.ts +163 -0
  32. package/src/commands/subscribe.ts +103 -0
  33. package/src/commands/subscriptions.ts +29 -0
  34. package/src/commands/suggest.ts +155 -0
  35. package/src/commands/todo.ts +2092 -0
  36. package/src/commands/update-event.ts +608 -0
  37. package/src/commands/update.ts +88 -0
  38. package/src/commands/verify-token.ts +62 -0
  39. package/src/commands/whoami.ts +74 -0
  40. package/src/index.ts +190 -0
  41. package/src/lib/atomic-write.ts +20 -0
  42. package/src/lib/attach-link-spec.test.ts +24 -0
  43. package/src/lib/attach-link-spec.ts +70 -0
  44. package/src/lib/attachments.ts +79 -0
  45. package/src/lib/auth.ts +192 -0
  46. package/src/lib/calendar-range.test.ts +41 -0
  47. package/src/lib/calendar-range.ts +103 -0
  48. package/src/lib/dates.test.ts +74 -0
  49. package/src/lib/dates.ts +137 -0
  50. package/src/lib/delegate-client.test.ts +74 -0
  51. package/src/lib/delegate-client.ts +322 -0
  52. package/src/lib/ews-client.ts +3418 -0
  53. package/src/lib/git-commit.ts +4 -0
  54. package/src/lib/glitchtip-eligibility.ts +220 -0
  55. package/src/lib/glitchtip.ts +253 -0
  56. package/src/lib/global-env.ts +3 -0
  57. package/src/lib/graph-auth.ts +223 -0
  58. package/src/lib/graph-calendar-client.test.ts +118 -0
  59. package/src/lib/graph-calendar-client.ts +112 -0
  60. package/src/lib/graph-client.test.ts +107 -0
  61. package/src/lib/graph-client.ts +1058 -0
  62. package/src/lib/graph-constants.ts +12 -0
  63. package/src/lib/graph-directory.ts +116 -0
  64. package/src/lib/graph-event.ts +134 -0
  65. package/src/lib/graph-schedule.ts +173 -0
  66. package/src/lib/graph-subscriptions.ts +94 -0
  67. package/src/lib/graph-user-path.ts +13 -0
  68. package/src/lib/jwt-utils.ts +34 -0
  69. package/src/lib/markdown.test.ts +21 -0
  70. package/src/lib/markdown.ts +174 -0
  71. package/src/lib/mime-type.ts +106 -0
  72. package/src/lib/oof-client.test.ts +59 -0
  73. package/src/lib/oof-client.ts +122 -0
  74. package/src/lib/outlook-graph-client.test.ts +146 -0
  75. package/src/lib/outlook-graph-client.ts +649 -0
  76. package/src/lib/outlook-master-categories.ts +145 -0
  77. package/src/lib/package-info.ts +59 -0
  78. package/src/lib/places-client.ts +144 -0
  79. package/src/lib/planner-client.ts +1226 -0
  80. package/src/lib/rules-client.ts +178 -0
  81. package/src/lib/sharepoint-client.ts +101 -0
  82. package/src/lib/site-pages-client.ts +73 -0
  83. package/src/lib/todo-client.test.ts +298 -0
  84. package/src/lib/todo-client.ts +1309 -0
  85. package/src/lib/url-validation.ts +40 -0
  86. package/src/lib/utils.ts +45 -0
  87. package/src/lib/webhook-server.ts +51 -0
  88. package/src/test/auth.test.ts +104 -0
  89. package/src/test/cli.integration.test.ts +1083 -0
  90. package/src/test/ews-client.test.ts +268 -0
  91. package/src/test/mocks/index.ts +375 -0
  92. package/src/test/mocks/responses.ts +861 -0
@@ -0,0 +1,263 @@
1
+ import { Command } from 'commander';
2
+ import { resolveGraphAuth } from '../lib/graph-auth.js';
3
+ import { type DateTimeTimeZone, getMailboxSettings, type OofStatus, setMailboxSettings } from '../lib/oof-client.js';
4
+ import { checkReadOnly } from '../lib/utils.js';
5
+
6
+ function formatStatus(status: OofStatus): string {
7
+ switch (status) {
8
+ case 'alwaysEnabled':
9
+ return 'Always On';
10
+ case 'scheduled':
11
+ return 'Scheduled';
12
+ case 'disabled':
13
+ return 'Disabled';
14
+ default:
15
+ return status;
16
+ }
17
+ }
18
+
19
+ function parseIsoDateTime(value: string): string {
20
+ const d = new Date(value);
21
+ if (!Number.isFinite(d.getTime())) {
22
+ throw new Error(`Invalid ISO datetime: ${value}`);
23
+ }
24
+ return d.toISOString();
25
+ }
26
+
27
+ export const oofCommand = new Command('oof')
28
+ .description('Get or set out-of-office (automatic reply) settings via Microsoft Graph')
29
+ .option('--status <status>', 'OOF status: always, scheduled, disabled')
30
+ .option('--internal-message <text>', 'Auto-reply message for internal senders')
31
+ .option('--external-message <text>', 'Auto-reply message for external senders')
32
+ .option('--start <datetime>', 'Scheduled start datetime (ISO 8601, e.g. 2025-12-01T00:00:00)')
33
+ .option('--end <datetime>', 'Scheduled end datetime (ISO 8601, e.g. 2025-12-15T23:59:59)')
34
+ .option('--json', 'Output as JSON')
35
+ .option('--token <token>', 'Use a specific Graph token')
36
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
37
+ .option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
38
+ .action(async (options: any, cmd: any) => {
39
+ const authResult = await resolveGraphAuth({ token: options.token, identity: options.identity });
40
+ if (!authResult.success || !authResult.token) {
41
+ const msg = authResult.error || 'Graph authentication failed';
42
+ if (options.json) {
43
+ console.log(JSON.stringify({ error: msg }, null, 2));
44
+ } else {
45
+ console.error(`Error: ${msg}`);
46
+ }
47
+ process.exit(1);
48
+ }
49
+
50
+ const token = authResult.token;
51
+
52
+ // --- READ mode: no write options provided ---
53
+ if (
54
+ !options.status &&
55
+ options.internalMessage === undefined &&
56
+ options.externalMessage === undefined &&
57
+ !options.start &&
58
+ !options.end
59
+ ) {
60
+ const res = await getMailboxSettings(token, options.user);
61
+ if (!res.ok || !res.data) {
62
+ const msg = res.error?.message || 'Failed to retrieve mailbox settings';
63
+ if (options.json) {
64
+ console.log(JSON.stringify({ error: msg }, null, 2));
65
+ } else {
66
+ console.error(`Error: ${msg}`);
67
+ }
68
+ process.exit(1);
69
+ }
70
+
71
+ const oof = res.data.automaticRepliesSetting;
72
+ if (!oof) {
73
+ if (options.json) {
74
+ console.log(JSON.stringify({ status: 'disabled', automaticRepliesSetting: null }, null, 2));
75
+ } else {
76
+ console.log('Out-of-office is disabled and no message is configured.');
77
+ }
78
+ return;
79
+ }
80
+
81
+ if (options.json) {
82
+ console.log(
83
+ JSON.stringify(
84
+ {
85
+ status: oof.status,
86
+ scheduledStartDateTime: oof.scheduledStartDateTime ?? null,
87
+ scheduledEndDateTime: oof.scheduledEndDateTime ?? null,
88
+ internalReplyMessage: oof.internalReplyMessage ?? null,
89
+ externalReplyMessage: oof.externalReplyMessage ?? null
90
+ },
91
+ null,
92
+ 2
93
+ )
94
+ );
95
+ } else {
96
+ console.log('Out-of-Office Settings:');
97
+ console.log(` Status: ${formatStatus(oof.status)}`);
98
+ if (oof.status === 'scheduled') {
99
+ const startStr = oof.scheduledStartDateTime
100
+ ? typeof oof.scheduledStartDateTime === 'string'
101
+ ? oof.scheduledStartDateTime
102
+ : `${oof.scheduledStartDateTime.dateTime} (${oof.scheduledStartDateTime.timeZone})`
103
+ : '?';
104
+ const endStr = oof.scheduledEndDateTime
105
+ ? typeof oof.scheduledEndDateTime === 'string'
106
+ ? oof.scheduledEndDateTime
107
+ : `${oof.scheduledEndDateTime.dateTime} (${oof.scheduledEndDateTime.timeZone})`
108
+ : '?';
109
+ console.log(` Scheduled: ${startStr} → ${endStr}`);
110
+ }
111
+ if (oof.internalReplyMessage) {
112
+ console.log(`\n Internal Reply:\n ${oof.internalReplyMessage}`);
113
+ }
114
+ if (oof.externalReplyMessage) {
115
+ console.log(`\n External Reply:\n ${oof.externalReplyMessage}`);
116
+ }
117
+ }
118
+ return;
119
+ }
120
+
121
+ // --- WRITE mode: validate inputs ---
122
+ checkReadOnly(cmd);
123
+ const errors: string[] = [];
124
+
125
+ let status: OofStatus | undefined;
126
+ if (options.status) {
127
+ const raw = options.status.toLowerCase();
128
+ if (raw === 'always' || raw === 'alwaysenabled') {
129
+ status = 'alwaysEnabled';
130
+ } else if (raw === 'scheduled') {
131
+ status = 'scheduled';
132
+ } else if (raw === 'disabled') {
133
+ status = 'disabled';
134
+ } else {
135
+ errors.push('--status must be one of: always, scheduled, disabled');
136
+ }
137
+ }
138
+
139
+ let scheduledStartDateTime: string | DateTimeTimeZone | undefined;
140
+ let scheduledEndDateTime: string | DateTimeTimeZone | undefined;
141
+
142
+ if (options.start) {
143
+ try {
144
+ scheduledStartDateTime = parseIsoDateTime(options.start);
145
+ } catch {
146
+ errors.push('--start must be a valid ISO 8601 datetime (e.g. 2025-12-01T00:00:00)');
147
+ }
148
+ }
149
+
150
+ if (options.end) {
151
+ try {
152
+ scheduledEndDateTime = parseIsoDateTime(options.end);
153
+ } catch {
154
+ errors.push('--end must be a valid ISO 8601 datetime (e.g. 2025-12-15T23:59:59)');
155
+ }
156
+ }
157
+
158
+ // If start/end are provided without explicit --status, default to scheduled
159
+ if ((scheduledStartDateTime || scheduledEndDateTime) && !status) {
160
+ status = 'scheduled';
161
+ }
162
+
163
+ if (errors.length > 0) {
164
+ for (const e of errors) {
165
+ if (options.json) {
166
+ console.log(JSON.stringify({ error: e }, null, 2));
167
+ } else {
168
+ console.error(`Error: ${e}`);
169
+ }
170
+ }
171
+ process.exit(1);
172
+ }
173
+
174
+ // Fetch existing settings if we are not explicitly overriding status,
175
+ // because Graph requires status in the PATCH payload or resets it to disabled.
176
+ let statusWasUndefined = false;
177
+ if (!status && (options.internalMessage !== undefined || options.externalMessage !== undefined)) {
178
+ statusWasUndefined = true;
179
+ const currentRes = await getMailboxSettings(token, options.user);
180
+ if (currentRes.ok && currentRes.data?.automaticRepliesSetting) {
181
+ status = currentRes.data.automaticRepliesSetting.status;
182
+ if (status === 'scheduled') {
183
+ scheduledStartDateTime =
184
+ scheduledStartDateTime ?? currentRes.data.automaticRepliesSetting.scheduledStartDateTime;
185
+ scheduledEndDateTime = scheduledEndDateTime ?? currentRes.data.automaticRepliesSetting.scheduledEndDateTime;
186
+ }
187
+ } else {
188
+ status = 'disabled'; // fallback if we couldn't fetch
189
+ }
190
+ }
191
+
192
+ // --- Apply updates ---
193
+ const patchResult = await setMailboxSettings(
194
+ token,
195
+ {
196
+ status,
197
+ internalReplyMessage: options.internalMessage,
198
+ externalReplyMessage: options.externalMessage,
199
+ scheduledStartDateTime,
200
+ scheduledEndDateTime
201
+ },
202
+ options.user
203
+ );
204
+
205
+ if (!patchResult.ok) {
206
+ const msg = patchResult.error?.message || 'Failed to update mailbox settings';
207
+ if (options.json) {
208
+ console.log(JSON.stringify({ error: msg }, null, 2));
209
+ } else {
210
+ console.error(`Error: ${msg}`);
211
+ }
212
+ process.exit(1);
213
+ }
214
+
215
+ if (options.json) {
216
+ const normalizeDateTime = (dt: string | DateTimeTimeZone | undefined): string | null => {
217
+ if (!dt) return null;
218
+ if (typeof dt === 'string') return dt;
219
+ return dt.dateTime;
220
+ };
221
+
222
+ const responseBody: any = {
223
+ status: 'success',
224
+ automaticRepliesSetting: {
225
+ scheduledStartDateTime: normalizeDateTime(scheduledStartDateTime),
226
+ scheduledEndDateTime: normalizeDateTime(scheduledEndDateTime),
227
+ internalReplyMessage: options.internalMessage ?? null,
228
+ externalReplyMessage: options.externalMessage ?? null
229
+ }
230
+ };
231
+ if (status !== undefined) {
232
+ responseBody.automaticRepliesSetting.status = status;
233
+ }
234
+ console.log(JSON.stringify(responseBody, null, 2));
235
+ } else {
236
+ console.log('Out-of-office settings updated.');
237
+ if (statusWasUndefined && status !== undefined) {
238
+ console.log(` Status: ${formatStatus(status)} (unchanged)`);
239
+ } else if (status !== undefined) {
240
+ console.log(` Status: ${formatStatus(status)}`);
241
+ } else {
242
+ console.log(` Status: (unchanged)`);
243
+ }
244
+ if (status === 'scheduled' || (status === undefined && (scheduledStartDateTime || scheduledEndDateTime))) {
245
+ if (scheduledStartDateTime) {
246
+ const startStr =
247
+ typeof scheduledStartDateTime === 'string'
248
+ ? scheduledStartDateTime
249
+ : `${scheduledStartDateTime.dateTime} (${scheduledStartDateTime.timeZone})`;
250
+ console.log(` Start: ${startStr}`);
251
+ }
252
+ if (scheduledEndDateTime) {
253
+ const endStr =
254
+ typeof scheduledEndDateTime === 'string'
255
+ ? scheduledEndDateTime
256
+ : `${scheduledEndDateTime.dateTime} (${scheduledEndDateTime.timeZone})`;
257
+ console.log(` End: ${endStr}`);
258
+ }
259
+ }
260
+ if (options.internalMessage) console.log(` Internal message: ${options.internalMessage}`);
261
+ if (options.externalMessage) console.log(` External message: ${options.externalMessage}`);
262
+ }
263
+ });
@@ -0,0 +1,173 @@
1
+ import { Command } from 'commander';
2
+ import { resolveGraphAuth } from '../lib/graph-auth.js';
3
+ import {
4
+ createOutlookMasterCategory,
5
+ deleteOutlookMasterCategory,
6
+ isValidOutlookCategoryColor,
7
+ listOutlookMasterCategories,
8
+ OUTLOOK_CATEGORY_COLOR_PRESETS,
9
+ updateOutlookMasterCategory
10
+ } from '../lib/outlook-master-categories.js';
11
+ import { checkReadOnly } from '../lib/utils.js';
12
+
13
+ /**
14
+ * Mailbox master category list (display names + preset colors). Same names are used on
15
+ * mail/calendar items when you set categories via EWS (`--category`); To Do uses separate string categories.
16
+ */
17
+ export const outlookCategoriesCommand = new Command('outlook-categories').description(
18
+ 'List, create, update, or delete Outlook mailbox master categories (names and colors; Graph)'
19
+ );
20
+
21
+ outlookCategoriesCommand
22
+ .command('list')
23
+ .description('Show master categories for the signed-in mailbox')
24
+ .option('--json', 'Output as JSON')
25
+ .option('--token <token>', 'Use a specific token')
26
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
27
+ .option('--user <email>', 'Target user (Graph delegation)')
28
+ .action(async (opts: { json?: boolean; token?: string; identity?: string; user?: string }) => {
29
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
30
+ if (!auth.success) {
31
+ console.error(`Auth error: ${auth.error}`);
32
+ process.exit(1);
33
+ }
34
+ const result = await listOutlookMasterCategories(auth.token!, opts.user);
35
+ if (!result.ok || !result.data) {
36
+ console.error(`Error: ${result.error?.message}`);
37
+ process.exit(1);
38
+ }
39
+ if (opts.json) {
40
+ console.log(JSON.stringify(result.data, null, 2));
41
+ return;
42
+ }
43
+ if (result.data.length === 0) {
44
+ console.log('No master categories defined.');
45
+ return;
46
+ }
47
+ console.log(`\nOutlook master categories (${result.data.length}):\n`);
48
+ for (const c of result.data) {
49
+ console.log(` ${c.displayName}`);
50
+ console.log(` color: ${c.color} id: ${c.id}`);
51
+ }
52
+ console.log('');
53
+ });
54
+
55
+ outlookCategoriesCommand
56
+ .command('create')
57
+ .description('Add a category to the mailbox master list (requires MailboxSettings.ReadWrite)')
58
+ .requiredOption('--name <text>', 'Display name (unique in the list)')
59
+ .requiredOption('--color <preset>', `Color: ${OUTLOOK_CATEGORY_COLOR_PRESETS.join(', ')}`)
60
+ .option('--json', 'Output as JSON')
61
+ .option('--token <token>', 'Use a specific token')
62
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
63
+ .option('--user <email>', 'Target user (Graph delegation)')
64
+ .action(
65
+ async (
66
+ opts: { name: string; color: string; json?: boolean; token?: string; identity?: string; user?: string },
67
+ cmd: any
68
+ ) => {
69
+ checkReadOnly(cmd);
70
+ const color = opts.color.trim();
71
+ if (!isValidOutlookCategoryColor(color)) {
72
+ console.error(`Invalid --color "${color}". Use one of: ${OUTLOOK_CATEGORY_COLOR_PRESETS.join(', ')}`);
73
+ process.exit(1);
74
+ }
75
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
76
+ if (!auth.success) {
77
+ console.error(`Auth error: ${auth.error}`);
78
+ process.exit(1);
79
+ }
80
+ const result = await createOutlookMasterCategory(auth.token!, opts.name, color, opts.user);
81
+ if (!result.ok || !result.data) {
82
+ console.error(`Error: ${result.error?.message}`);
83
+ process.exit(1);
84
+ }
85
+ if (opts.json) console.log(JSON.stringify(result.data, null, 2));
86
+ else {
87
+ console.log(`\nCreated: ${result.data.displayName} (${result.data.color})`);
88
+ console.log(` id: ${result.data.id}\n`);
89
+ }
90
+ }
91
+ );
92
+
93
+ outlookCategoriesCommand
94
+ .command('update')
95
+ .description('Rename or recolor a master category by id (from list)')
96
+ .requiredOption('--id <id>', 'Category id')
97
+ .option('--name <text>', 'New display name')
98
+ .option('--color <preset>', `New color: ${OUTLOOK_CATEGORY_COLOR_PRESETS.slice(0, 5).join(', ')}, ... preset24`)
99
+ .option('--json', 'Output as JSON')
100
+ .option('--token <token>', 'Use a specific token')
101
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
102
+ .option('--user <email>', 'Target user (Graph delegation)')
103
+ .action(
104
+ async (
105
+ opts: {
106
+ id: string;
107
+ name?: string;
108
+ color?: string;
109
+ json?: boolean;
110
+ token?: string;
111
+ identity?: string;
112
+ user?: string;
113
+ },
114
+ cmd: any
115
+ ) => {
116
+ checkReadOnly(cmd);
117
+ if (!opts.name && !opts.color) {
118
+ console.error('Error: specify --name and/or --color');
119
+ process.exit(1);
120
+ }
121
+ if (opts.color !== undefined && !isValidOutlookCategoryColor(opts.color.trim())) {
122
+ console.error(`Invalid --color. Use one of: ${OUTLOOK_CATEGORY_COLOR_PRESETS.join(', ')}`);
123
+ process.exit(1);
124
+ }
125
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
126
+ if (!auth.success) {
127
+ console.error(`Auth error: ${auth.error}`);
128
+ process.exit(1);
129
+ }
130
+ const result = await updateOutlookMasterCategory(
131
+ auth.token!,
132
+ opts.id.trim(),
133
+ {
134
+ displayName: opts.name,
135
+ color: opts.color?.trim()
136
+ },
137
+ opts.user
138
+ );
139
+ if (!result.ok || !result.data) {
140
+ console.error(`Error: ${result.error?.message}`);
141
+ process.exit(1);
142
+ }
143
+ if (opts.json) console.log(JSON.stringify(result.data, null, 2));
144
+ else {
145
+ console.log(`\nUpdated: ${result.data.displayName} (${result.data.color})`);
146
+ console.log(` id: ${result.data.id}\n`);
147
+ }
148
+ }
149
+ );
150
+
151
+ outlookCategoriesCommand
152
+ .command('delete')
153
+ .description('Remove a category from the master list (does not remove labels from existing items)')
154
+ .requiredOption('--id <id>', 'Category id')
155
+ .option('--json', 'Output as JSON')
156
+ .option('--token <token>', 'Use a specific token')
157
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
158
+ .option('--user <email>', 'Target user (Graph delegation)')
159
+ .action(async (opts: { id: string; json?: boolean; token?: string; identity?: string; user?: string }, cmd: any) => {
160
+ checkReadOnly(cmd);
161
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
162
+ if (!auth.success) {
163
+ console.error(`Auth error: ${auth.error}`);
164
+ process.exit(1);
165
+ }
166
+ const result = await deleteOutlookMasterCategory(auth.token!, opts.id.trim(), opts.user);
167
+ if (!result.ok) {
168
+ console.error(`Error: ${result.error?.message}`);
169
+ process.exit(1);
170
+ }
171
+ if (opts.json) console.log(JSON.stringify({ success: true }, null, 2));
172
+ else console.log(`\nDeleted category id: ${opts.id.trim()}\n`);
173
+ });