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,880 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { Command } from 'commander';
3
+ import { resolveGraphAuth } from '../lib/graph-auth.js';
4
+ import {
5
+ copyMailMessage,
6
+ createContact,
7
+ createMailFolder,
8
+ createMailForwardDraft,
9
+ createMailReplyAllDraft,
10
+ createMailReplyDraft,
11
+ deleteContact,
12
+ deleteMailFolder,
13
+ deleteMailMessage,
14
+ downloadMailMessageAttachmentBytes,
15
+ getContact,
16
+ getMailFolder,
17
+ getMailMessageAttachment,
18
+ getMessage,
19
+ listChildMailFolders,
20
+ listContacts,
21
+ listMailboxMessages,
22
+ listMailFolders,
23
+ listMailMessageAttachments,
24
+ listMessagesInFolder,
25
+ type MessagesQueryOptions,
26
+ moveMailMessage,
27
+ patchMailMessage,
28
+ type RootMailboxMessagesQuery,
29
+ sendMail,
30
+ sendMailMessage,
31
+ updateContact,
32
+ updateMailFolder
33
+ } from '../lib/outlook-graph-client.js';
34
+ import { checkReadOnly } from '../lib/utils.js';
35
+
36
+ export const outlookGraphCommand = new Command('outlook-graph').description(
37
+ 'Microsoft Graph Outlook REST: mail folders, messages (list/send/patch/move/copy/attachments/reply), contacts (distinct from EWS mail/folders)'
38
+ );
39
+
40
+ outlookGraphCommand
41
+ .command('list-folders')
42
+ .description('List mail folders (Graph GET /mailFolders)')
43
+ .option('--json', 'Output as JSON')
44
+ .option('--token <token>', 'Use a specific token')
45
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
46
+ .option('--user <email>', 'Target user (Graph delegation)')
47
+ .action(async (opts: { json?: boolean; token?: string; identity?: string; user?: string }) => {
48
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
49
+ if (!auth.success) {
50
+ console.error(`Auth error: ${auth.error}`);
51
+ process.exit(1);
52
+ }
53
+ const r = await listMailFolders(auth.token!, opts.user);
54
+ if (!r.ok || !r.data) {
55
+ console.error(`Error: ${r.error?.message}`);
56
+ process.exit(1);
57
+ }
58
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
59
+ else {
60
+ for (const f of r.data) {
61
+ console.log(`${f.displayName}\t${f.id}`);
62
+ }
63
+ }
64
+ });
65
+
66
+ outlookGraphCommand
67
+ .command('child-folders')
68
+ .description('List child folders under a parent folder id')
69
+ .requiredOption('-p, --parent <folderId>', 'Parent folder id (e.g. Inbox id from list-folders)')
70
+ .option('--json', 'Output as JSON')
71
+ .option('--token <token>', 'Use a specific token')
72
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
73
+ .option('--user <email>', 'Target user (Graph delegation)')
74
+ .action(async (opts: { parent: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
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 r = await listChildMailFolders(auth.token!, opts.parent, opts.user);
81
+ if (!r.ok || !r.data) {
82
+ console.error(`Error: ${r.error?.message}`);
83
+ process.exit(1);
84
+ }
85
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
86
+ else {
87
+ for (const f of r.data) {
88
+ console.log(`${f.displayName}\t${f.id}`);
89
+ }
90
+ }
91
+ });
92
+
93
+ outlookGraphCommand
94
+ .command('get-folder')
95
+ .description('Get one mail folder by id (well-known: inbox, sentitems, drafts, deleteditems, archive, junkemail)')
96
+ .argument('<folderId>', 'Folder id')
97
+ .option('--json', 'Output as JSON')
98
+ .option('--token <token>', 'Use a specific token')
99
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
100
+ .option('--user <email>', 'Target user (Graph delegation)')
101
+ .action(async (folderId: string, opts: { json?: boolean; token?: string; identity?: string; user?: string }) => {
102
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
103
+ if (!auth.success) {
104
+ console.error(`Auth error: ${auth.error}`);
105
+ process.exit(1);
106
+ }
107
+ const r = await getMailFolder(auth.token!, folderId, opts.user);
108
+ if (!r.ok || !r.data) {
109
+ console.error(`Error: ${r.error?.message}`);
110
+ process.exit(1);
111
+ }
112
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
113
+ else console.log(JSON.stringify(r.data, null, 2));
114
+ });
115
+
116
+ outlookGraphCommand
117
+ .command('create-folder')
118
+ .description('Create a mail folder (optionally under --parent)')
119
+ .requiredOption('-n, --name <displayName>', 'Folder display name')
120
+ .option('-p, --parent <folderId>', 'Parent folder id (omit for root-level where allowed)')
121
+ .option('--json', 'Output as JSON')
122
+ .option('--token <token>', 'Use a specific token')
123
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
124
+ .option('--user <email>', 'Target user (Graph delegation)')
125
+ .action(
126
+ async (
127
+ opts: { name: string; parent?: string; json?: boolean; token?: string; identity?: string; user?: string },
128
+ cmd: any
129
+ ) => {
130
+ checkReadOnly(cmd);
131
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
132
+ if (!auth.success) {
133
+ console.error(`Auth error: ${auth.error}`);
134
+ process.exit(1);
135
+ }
136
+ const r = await createMailFolder(auth.token!, opts.name, opts.parent, opts.user);
137
+ if (!r.ok || !r.data) {
138
+ console.error(`Error: ${r.error?.message}`);
139
+ process.exit(1);
140
+ }
141
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
142
+ else console.log(`Created folder: ${r.data.displayName} (${r.data.id})`);
143
+ }
144
+ );
145
+
146
+ outlookGraphCommand
147
+ .command('update-folder')
148
+ .description('Rename a mail folder (PATCH displayName)')
149
+ .argument('<folderId>', 'Folder id')
150
+ .requiredOption('-n, --name <displayName>', 'New display name')
151
+ .option('--json', 'Output as JSON')
152
+ .option('--token <token>', 'Use a specific token')
153
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
154
+ .option('--user <email>', 'Target user (Graph delegation)')
155
+ .action(
156
+ async (
157
+ folderId: string,
158
+ opts: { name: string; json?: boolean; token?: string; identity?: string; user?: string },
159
+ cmd: any
160
+ ) => {
161
+ checkReadOnly(cmd);
162
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
163
+ if (!auth.success) {
164
+ console.error(`Auth error: ${auth.error}`);
165
+ process.exit(1);
166
+ }
167
+ const r = await updateMailFolder(auth.token!, folderId, opts.name, opts.user);
168
+ if (!r.ok || !r.data) {
169
+ console.error(`Error: ${r.error?.message}`);
170
+ process.exit(1);
171
+ }
172
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
173
+ else console.log(`Updated folder: ${r.data.displayName}`);
174
+ }
175
+ );
176
+
177
+ outlookGraphCommand
178
+ .command('delete-folder')
179
+ .description('Delete a mail folder (not allowed for default folders)')
180
+ .argument('<folderId>', 'Folder id')
181
+ .option('--confirm', 'Confirm delete')
182
+ .option('--token <token>', 'Use a specific token')
183
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
184
+ .option('--user <email>', 'Target user (Graph delegation)')
185
+ .action(
186
+ async (
187
+ folderId: string,
188
+ opts: { confirm?: boolean; token?: string; identity?: string; user?: string },
189
+ cmd: any
190
+ ) => {
191
+ checkReadOnly(cmd);
192
+ if (!opts.confirm) {
193
+ console.error('Refusing to delete without --confirm');
194
+ process.exit(1);
195
+ }
196
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
197
+ if (!auth.success) {
198
+ console.error(`Auth error: ${auth.error}`);
199
+ process.exit(1);
200
+ }
201
+ const r = await deleteMailFolder(auth.token!, folderId, opts.user);
202
+ if (!r.ok) {
203
+ console.error(`Error: ${r.error?.message}`);
204
+ process.exit(1);
205
+ }
206
+ console.log('Deleted folder.');
207
+ }
208
+ );
209
+
210
+ outlookGraphCommand
211
+ .command('list-messages')
212
+ .description('List messages in a folder (Graph; use well-known id e.g. inbox)')
213
+ .requiredOption('-f, --folder <folderId>', 'Folder id (e.g. inbox)')
214
+ .option('--top <n>', 'Page size (default 25). Omit with --all to page entire folder', '25')
215
+ .option('--all', 'Follow all pages (may be slow/large)')
216
+ .option('--filter <odata>', 'OData $filter')
217
+ .option('--orderby <odata>', 'OData $orderby')
218
+ .option('--select <fields>', 'OData $select (comma-separated)')
219
+ .option('--json', 'Output as JSON')
220
+ .option('--token <token>', 'Use a specific token')
221
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
222
+ .option('--user <email>', 'Target user (Graph delegation)')
223
+ .action(
224
+ async (opts: {
225
+ folder: string;
226
+ top?: string;
227
+ all?: boolean;
228
+ filter?: string;
229
+ orderby?: string;
230
+ select?: string;
231
+ json?: boolean;
232
+ token?: string;
233
+ identity?: string;
234
+ user?: string;
235
+ }) => {
236
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
237
+ if (!auth.success) {
238
+ console.error(`Auth error: ${auth.error}`);
239
+ process.exit(1);
240
+ }
241
+ const q: MessagesQueryOptions = {};
242
+ if (opts.filter) q.filter = opts.filter;
243
+ if (opts.orderby) q.orderby = opts.orderby;
244
+ if (opts.select) q.select = opts.select;
245
+ if (opts.all) {
246
+ // fetch all pages — no $top
247
+ } else {
248
+ q.top = Math.max(1, parseInt(opts.top ?? '25', 10) || 25);
249
+ }
250
+ const r = await listMessagesInFolder(auth.token!, opts.folder, opts.user, q);
251
+ if (!r.ok || !r.data) {
252
+ console.error(`Error: ${r.error?.message}`);
253
+ process.exit(1);
254
+ }
255
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
256
+ else {
257
+ for (const m of r.data) {
258
+ const from = m.from?.emailAddress?.address ?? '';
259
+ const sub = m.subject ?? '(no subject)';
260
+ console.log(`${m.receivedDateTime ?? ''}\t${from}\t${sub}\t${m.id}`);
261
+ }
262
+ }
263
+ }
264
+ );
265
+
266
+ outlookGraphCommand
267
+ .command('list-mail')
268
+ .description('List messages mailbox-wide (Graph GET /messages; use --folder on list-messages for one folder)')
269
+ .option('--top <n>', 'Page size (default 25). Omit with --all to page entire result set', '25')
270
+ .option('--all', 'Follow all pages (may be slow/large)')
271
+ .option('--filter <odata>', 'OData $filter (do not combine with --search)')
272
+ .option('--orderby <odata>', 'OData $orderby')
273
+ .option('--select <fields>', 'OData $select (comma-separated)')
274
+ .option('--skip <n>', 'OData $skip')
275
+ .option('--search <text>', 'Keyword search ($search; adds ConsistencyLevel; do not combine with --filter)')
276
+ .option('--json', 'Output as JSON')
277
+ .option('--token <token>', 'Use a specific token')
278
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
279
+ .option('--user <email>', 'Target user (Graph delegation)')
280
+ .action(
281
+ async (opts: {
282
+ top?: string;
283
+ all?: boolean;
284
+ filter?: string;
285
+ orderby?: string;
286
+ select?: string;
287
+ skip?: string;
288
+ search?: string;
289
+ json?: boolean;
290
+ token?: string;
291
+ identity?: string;
292
+ user?: string;
293
+ }) => {
294
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
295
+ if (!auth.success) {
296
+ console.error(`Auth error: ${auth.error}`);
297
+ process.exit(1);
298
+ }
299
+ const q: RootMailboxMessagesQuery = {};
300
+ if (opts.filter) q.filter = opts.filter;
301
+ if (opts.orderby) q.orderby = opts.orderby;
302
+ if (opts.select) q.select = opts.select;
303
+ if (opts.search) q.search = opts.search;
304
+ if (opts.skip !== undefined) q.skip = Math.max(0, parseInt(opts.skip, 10) || 0);
305
+ if (opts.all) {
306
+ // full pagination
307
+ } else {
308
+ q.top = Math.max(1, parseInt(opts.top ?? '25', 10) || 25);
309
+ }
310
+ const r = await listMailboxMessages(auth.token!, opts.user, q);
311
+ if (!r.ok || !r.data) {
312
+ console.error(`Error: ${r.error?.message}`);
313
+ process.exit(1);
314
+ }
315
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
316
+ else {
317
+ for (const m of r.data) {
318
+ const from = m.from?.emailAddress?.address ?? '';
319
+ const sub = m.subject ?? '(no subject)';
320
+ console.log(`${m.receivedDateTime ?? ''}\t${from}\t${sub}\t${m.id}`);
321
+ }
322
+ }
323
+ }
324
+ );
325
+
326
+ outlookGraphCommand
327
+ .command('get-message')
328
+ .description('Get one message by id (Graph GET /messages/{id})')
329
+ .requiredOption('-i, --id <messageId>', 'Message id')
330
+ .option('--select <fields>', 'OData $select')
331
+ .option('--json', 'Output as JSON')
332
+ .option('--token <token>', 'Use a specific token')
333
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
334
+ .option('--user <email>', 'Target user (Graph delegation)')
335
+ .action(
336
+ async (opts: { id: string; select?: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
337
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
338
+ if (!auth.success) {
339
+ console.error(`Auth error: ${auth.error}`);
340
+ process.exit(1);
341
+ }
342
+ const r = await getMessage(auth.token!, opts.id, opts.user, opts.select);
343
+ if (!r.ok || !r.data) {
344
+ console.error(`Error: ${r.error?.message}`);
345
+ process.exit(1);
346
+ }
347
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
348
+ else console.log(JSON.stringify(r.data, null, 2));
349
+ }
350
+ );
351
+
352
+ outlookGraphCommand
353
+ .command('send-mail')
354
+ .description('Send email in one request (Graph POST /sendMail; JSON body with message + optional saveToSentItems)')
355
+ .requiredOption('--json-file <path>', 'Path to JSON: { "message": { ... }, "saveToSentItems": true }')
356
+ .option('--token <token>', 'Use a specific token')
357
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
358
+ .option('--user <email>', 'Target user (Graph delegation)')
359
+ .action(async (opts: { jsonFile: string; token?: string; identity?: string; user?: string }, cmd: any) => {
360
+ checkReadOnly(cmd);
361
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
362
+ if (!auth.success) {
363
+ console.error(`Auth error: ${auth.error}`);
364
+ process.exit(1);
365
+ }
366
+ const raw = await readFile(opts.jsonFile, 'utf-8');
367
+ const body = JSON.parse(raw) as { message: Record<string, unknown>; saveToSentItems?: boolean };
368
+ if (!body.message) {
369
+ console.error('JSON must include a "message" object');
370
+ process.exit(1);
371
+ }
372
+ const r = await sendMail(auth.token!, body, opts.user);
373
+ if (!r.ok) {
374
+ console.error(`Error: ${r.error?.message}`);
375
+ process.exit(1);
376
+ }
377
+ console.log('Sent.');
378
+ });
379
+
380
+ outlookGraphCommand
381
+ .command('patch-message')
382
+ .description('PATCH a message (e.g. isRead, flag, categories — Graph PATCH /messages/{id})')
383
+ .argument('<messageId>', 'Message id')
384
+ .requiredOption('--json-file <path>', 'Path to JSON patch body')
385
+ .option('--json', 'Echo updated message as JSON')
386
+ .option('--token <token>', 'Use a specific token')
387
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
388
+ .option('--user <email>', 'Target user (Graph delegation)')
389
+ .action(
390
+ async (
391
+ messageId: string,
392
+ opts: { jsonFile: string; json?: boolean; token?: string; identity?: string; user?: string },
393
+ cmd: any
394
+ ) => {
395
+ checkReadOnly(cmd);
396
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
397
+ if (!auth.success) {
398
+ console.error(`Auth error: ${auth.error}`);
399
+ process.exit(1);
400
+ }
401
+ const raw = await readFile(opts.jsonFile, 'utf-8');
402
+ const patch = JSON.parse(raw) as Record<string, unknown>;
403
+ const r = await patchMailMessage(auth.token!, messageId, patch, opts.user);
404
+ if (!r.ok || !r.data) {
405
+ console.error(`Error: ${r.error?.message}`);
406
+ process.exit(1);
407
+ }
408
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
409
+ else console.log(`Updated message ${r.data.id}`);
410
+ }
411
+ );
412
+
413
+ outlookGraphCommand
414
+ .command('delete-message')
415
+ .description('Delete a message (Graph DELETE /messages/{id})')
416
+ .argument('<messageId>', 'Message id')
417
+ .option('--confirm', 'Confirm delete')
418
+ .option('--token <token>', 'Use a specific token')
419
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
420
+ .option('--user <email>', 'Target user (Graph delegation)')
421
+ .action(
422
+ async (
423
+ messageId: string,
424
+ opts: { confirm?: boolean; token?: string; identity?: string; user?: string },
425
+ cmd: any
426
+ ) => {
427
+ checkReadOnly(cmd);
428
+ if (!opts.confirm) {
429
+ console.error('Refusing to delete without --confirm');
430
+ process.exit(1);
431
+ }
432
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
433
+ if (!auth.success) {
434
+ console.error(`Auth error: ${auth.error}`);
435
+ process.exit(1);
436
+ }
437
+ const r = await deleteMailMessage(auth.token!, messageId, opts.user);
438
+ if (!r.ok) {
439
+ console.error(`Error: ${r.error?.message}`);
440
+ process.exit(1);
441
+ }
442
+ console.log('Deleted message.');
443
+ }
444
+ );
445
+
446
+ outlookGraphCommand
447
+ .command('move-message')
448
+ .description('Move a message to another folder (Graph POST .../move)')
449
+ .argument('<messageId>', 'Message id')
450
+ .requiredOption('-d, --destination <folderId>', 'Destination folder id (e.g. deleteditems)')
451
+ .option('--json', 'Echo moved message as JSON')
452
+ .option('--token <token>', 'Use a specific token')
453
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
454
+ .option('--user <email>', 'Target user (Graph delegation)')
455
+ .action(
456
+ async (
457
+ messageId: string,
458
+ opts: { destination: string; json?: boolean; token?: string; identity?: string; user?: string },
459
+ cmd: any
460
+ ) => {
461
+ checkReadOnly(cmd);
462
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
463
+ if (!auth.success) {
464
+ console.error(`Auth error: ${auth.error}`);
465
+ process.exit(1);
466
+ }
467
+ const r = await moveMailMessage(auth.token!, messageId, opts.destination, opts.user);
468
+ if (!r.ok || !r.data) {
469
+ console.error(`Error: ${r.error?.message}`);
470
+ process.exit(1);
471
+ }
472
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
473
+ else console.log(`Moved to folder; message id: ${r.data.id}`);
474
+ }
475
+ );
476
+
477
+ outlookGraphCommand
478
+ .command('copy-message')
479
+ .description('Copy a message to another folder (Graph POST .../copy)')
480
+ .argument('<messageId>', 'Message id')
481
+ .requiredOption('-d, --destination <folderId>', 'Destination folder id')
482
+ .option('--json', 'Echo copy as JSON')
483
+ .option('--token <token>', 'Use a specific token')
484
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
485
+ .option('--user <email>', 'Target user (Graph delegation)')
486
+ .action(
487
+ async (
488
+ messageId: string,
489
+ opts: { destination: string; json?: boolean; token?: string; identity?: string; user?: string },
490
+ cmd: any
491
+ ) => {
492
+ checkReadOnly(cmd);
493
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
494
+ if (!auth.success) {
495
+ console.error(`Auth error: ${auth.error}`);
496
+ process.exit(1);
497
+ }
498
+ const r = await copyMailMessage(auth.token!, messageId, opts.destination, opts.user);
499
+ if (!r.ok || !r.data) {
500
+ console.error(`Error: ${r.error?.message}`);
501
+ process.exit(1);
502
+ }
503
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
504
+ else console.log(`Copy id: ${r.data.id}`);
505
+ }
506
+ );
507
+
508
+ outlookGraphCommand
509
+ .command('list-message-attachments')
510
+ .description('List attachments on a message (Graph GET .../messages/{id}/attachments)')
511
+ .requiredOption('-i, --id <messageId>', 'Message id')
512
+ .option('--json', 'Output as JSON')
513
+ .option('--token <token>', 'Use a specific token')
514
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
515
+ .option('--user <email>', 'Target user (Graph delegation)')
516
+ .action(async (opts: { id: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
517
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
518
+ if (!auth.success) {
519
+ console.error(`Auth error: ${auth.error}`);
520
+ process.exit(1);
521
+ }
522
+ const r = await listMailMessageAttachments(auth.token!, opts.id, opts.user);
523
+ if (!r.ok || !r.data) {
524
+ console.error(`Error: ${r.error?.message}`);
525
+ process.exit(1);
526
+ }
527
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
528
+ else {
529
+ for (const a of r.data) {
530
+ console.log(`${a.name ?? a.id}\t${a.contentType ?? ''}\t${a.id}`);
531
+ }
532
+ }
533
+ });
534
+
535
+ outlookGraphCommand
536
+ .command('get-message-attachment')
537
+ .description('Get attachment metadata (Graph GET .../attachments/{id})')
538
+ .requiredOption('-i, --id <messageId>', 'Message id')
539
+ .requiredOption('-a, --attachment <attachmentId>', 'Attachment id')
540
+ .option('--json', 'Output as JSON')
541
+ .option('--token <token>', 'Use a specific token')
542
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
543
+ .option('--user <email>', 'Target user (Graph delegation)')
544
+ .action(
545
+ async (opts: {
546
+ id: string;
547
+ attachment: string;
548
+ json?: boolean;
549
+ token?: string;
550
+ identity?: string;
551
+ user?: string;
552
+ }) => {
553
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
554
+ if (!auth.success) {
555
+ console.error(`Auth error: ${auth.error}`);
556
+ process.exit(1);
557
+ }
558
+ const r = await getMailMessageAttachment(auth.token!, opts.id, opts.attachment, opts.user);
559
+ if (!r.ok || !r.data) {
560
+ console.error(`Error: ${r.error?.message}`);
561
+ process.exit(1);
562
+ }
563
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
564
+ else console.log(JSON.stringify(r.data, null, 2));
565
+ }
566
+ );
567
+
568
+ outlookGraphCommand
569
+ .command('download-message-attachment')
570
+ .description('Download file attachment bytes (Graph GET .../attachments/{id}/$value)')
571
+ .requiredOption('-i, --id <messageId>', 'Message id')
572
+ .requiredOption('-a, --attachment <attachmentId>', 'Attachment id')
573
+ .requiredOption('-o, --output <path>', 'Output file path')
574
+ .option('--token <token>', 'Use a specific token')
575
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
576
+ .option('--user <email>', 'Target user (Graph delegation)')
577
+ .action(
578
+ async (opts: {
579
+ id: string;
580
+ attachment: string;
581
+ output: string;
582
+ token?: string;
583
+ identity?: string;
584
+ user?: string;
585
+ }) => {
586
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
587
+ if (!auth.success) {
588
+ console.error(`Auth error: ${auth.error}`);
589
+ process.exit(1);
590
+ }
591
+ const r = await downloadMailMessageAttachmentBytes(auth.token!, opts.id, opts.attachment, opts.user);
592
+ if (!r.ok || !r.data) {
593
+ console.error(`Error: ${r.error?.message}`);
594
+ process.exit(1);
595
+ }
596
+ await writeFile(opts.output, r.data);
597
+ console.log(`Wrote ${r.data.byteLength} bytes to ${opts.output}`);
598
+ }
599
+ );
600
+
601
+ outlookGraphCommand
602
+ .command('create-reply')
603
+ .description('Create a reply draft (Graph POST .../createReply); then patch body and outlook-graph send-message')
604
+ .argument('<messageId>', 'Message id to reply to')
605
+ .option('--comment <text>', 'Optional comment included in the draft')
606
+ .option('--json', 'Output draft message as JSON')
607
+ .option('--token <token>', 'Use a specific token')
608
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
609
+ .option('--user <email>', 'Target user (Graph delegation)')
610
+ .action(
611
+ async (
612
+ messageId: string,
613
+ opts: { comment?: string; json?: boolean; token?: string; identity?: string; user?: string },
614
+ cmd: any
615
+ ) => {
616
+ checkReadOnly(cmd);
617
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
618
+ if (!auth.success) {
619
+ console.error(`Auth error: ${auth.error}`);
620
+ process.exit(1);
621
+ }
622
+ const r = await createMailReplyDraft(auth.token!, messageId, opts.user, opts.comment);
623
+ if (!r.ok || !r.data) {
624
+ console.error(`Error: ${r.error?.message}`);
625
+ process.exit(1);
626
+ }
627
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
628
+ else console.log(`Draft message id: ${r.data.id}`);
629
+ }
630
+ );
631
+
632
+ outlookGraphCommand
633
+ .command('create-reply-all')
634
+ .description('Create a reply-all draft (Graph POST .../createReplyAll)')
635
+ .argument('<messageId>', 'Message id')
636
+ .option('--comment <text>', 'Optional comment')
637
+ .option('--json', 'Output draft message as JSON')
638
+ .option('--token <token>', 'Use a specific token')
639
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
640
+ .option('--user <email>', 'Target user (Graph delegation)')
641
+ .action(
642
+ async (
643
+ messageId: string,
644
+ opts: { comment?: string; json?: boolean; token?: string; identity?: string; user?: string },
645
+ cmd: any
646
+ ) => {
647
+ checkReadOnly(cmd);
648
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
649
+ if (!auth.success) {
650
+ console.error(`Auth error: ${auth.error}`);
651
+ process.exit(1);
652
+ }
653
+ const r = await createMailReplyAllDraft(auth.token!, messageId, opts.user, opts.comment);
654
+ if (!r.ok || !r.data) {
655
+ console.error(`Error: ${r.error?.message}`);
656
+ process.exit(1);
657
+ }
658
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
659
+ else console.log(`Draft message id: ${r.data.id}`);
660
+ }
661
+ );
662
+
663
+ outlookGraphCommand
664
+ .command('create-forward')
665
+ .description('Create a forward draft (Graph POST .../createForward)')
666
+ .argument('<messageId>', 'Message id')
667
+ .requiredOption('--to <emails>', 'Comma-separated recipient addresses')
668
+ .option('--comment <text>', 'Optional comment')
669
+ .option('--json', 'Output draft message as JSON')
670
+ .option('--token <token>', 'Use a specific token')
671
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
672
+ .option('--user <email>', 'Target user (Graph delegation)')
673
+ .action(
674
+ async (
675
+ messageId: string,
676
+ opts: {
677
+ to: string;
678
+ comment?: string;
679
+ json?: boolean;
680
+ token?: string;
681
+ identity?: string;
682
+ user?: string;
683
+ },
684
+ cmd: any
685
+ ) => {
686
+ checkReadOnly(cmd);
687
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
688
+ if (!auth.success) {
689
+ console.error(`Auth error: ${auth.error}`);
690
+ process.exit(1);
691
+ }
692
+ const recipients = opts.to
693
+ .split(',')
694
+ .map((s) => s.trim())
695
+ .filter(Boolean);
696
+ if (recipients.length === 0) {
697
+ console.error('Provide at least one address in --to');
698
+ process.exit(1);
699
+ }
700
+ const r = await createMailForwardDraft(auth.token!, messageId, recipients, opts.user, opts.comment);
701
+ if (!r.ok || !r.data) {
702
+ console.error(`Error: ${r.error?.message}`);
703
+ process.exit(1);
704
+ }
705
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
706
+ else console.log(`Draft message id: ${r.data.id}`);
707
+ }
708
+ );
709
+
710
+ outlookGraphCommand
711
+ .command('send-message')
712
+ .description('Send a draft message (Graph POST /messages/{id}/send)')
713
+ .argument('<messageId>', 'Draft message id')
714
+ .option('--token <token>', 'Use a specific token')
715
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
716
+ .option('--user <email>', 'Target user (Graph delegation)')
717
+ .action(async (messageId: string, opts: { token?: string; identity?: string; user?: string }, cmd: any) => {
718
+ checkReadOnly(cmd);
719
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
720
+ if (!auth.success) {
721
+ console.error(`Auth error: ${auth.error}`);
722
+ process.exit(1);
723
+ }
724
+ const r = await sendMailMessage(auth.token!, messageId, opts.user);
725
+ if (!r.ok) {
726
+ console.error(`Error: ${r.error?.message}`);
727
+ process.exit(1);
728
+ }
729
+ console.log('Sent.');
730
+ });
731
+
732
+ outlookGraphCommand
733
+ .command('list-contacts')
734
+ .description('List personal contacts (Graph GET /contacts)')
735
+ .option('--json', 'Output as JSON')
736
+ .option('--token <token>', 'Use a specific token')
737
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
738
+ .option('--user <email>', 'Target user (Graph delegation)')
739
+ .action(async (opts: { json?: boolean; token?: string; identity?: string; user?: string }) => {
740
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
741
+ if (!auth.success) {
742
+ console.error(`Auth error: ${auth.error}`);
743
+ process.exit(1);
744
+ }
745
+ const r = await listContacts(auth.token!, opts.user);
746
+ if (!r.ok || !r.data) {
747
+ console.error(`Error: ${r.error?.message}`);
748
+ process.exit(1);
749
+ }
750
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
751
+ else {
752
+ for (const c of r.data) {
753
+ const em = c.emailAddresses?.[0]?.address ?? '';
754
+ console.log(`${c.displayName ?? '(no name)'}\t${em}\t${c.id}`);
755
+ }
756
+ }
757
+ });
758
+
759
+ outlookGraphCommand
760
+ .command('get-contact')
761
+ .description('Get one contact by id')
762
+ .argument('<contactId>', 'Contact id')
763
+ .option('--select <fields>', 'OData $select')
764
+ .option('--json', 'Output as JSON')
765
+ .option('--token <token>', 'Use a specific token')
766
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
767
+ .option('--user <email>', 'Target user (Graph delegation)')
768
+ .action(
769
+ async (
770
+ contactId: string,
771
+ opts: { select?: string; json?: boolean; token?: string; identity?: string; user?: string }
772
+ ) => {
773
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
774
+ if (!auth.success) {
775
+ console.error(`Auth error: ${auth.error}`);
776
+ process.exit(1);
777
+ }
778
+ const r = await getContact(auth.token!, contactId, opts.user, opts.select);
779
+ if (!r.ok || !r.data) {
780
+ console.error(`Error: ${r.error?.message}`);
781
+ process.exit(1);
782
+ }
783
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
784
+ else console.log(JSON.stringify(r.data, null, 2));
785
+ }
786
+ );
787
+
788
+ outlookGraphCommand
789
+ .command('create-contact')
790
+ .description('Create a contact (JSON body per Graph contact resource)')
791
+ .requiredOption('--json-file <path>', 'Path to JSON file')
792
+ .option('--json', 'Echo created contact as JSON')
793
+ .option('--token <token>', 'Use a specific token')
794
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
795
+ .option('--user <email>', 'Target user (Graph delegation)')
796
+ .action(
797
+ async (opts: { jsonFile: string; json?: boolean; token?: string; identity?: string; user?: string }, cmd: any) => {
798
+ checkReadOnly(cmd);
799
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
800
+ if (!auth.success) {
801
+ console.error(`Auth error: ${auth.error}`);
802
+ process.exit(1);
803
+ }
804
+ const raw = await readFile(opts.jsonFile, 'utf-8');
805
+ const body = JSON.parse(raw) as Record<string, unknown>;
806
+ const r = await createContact(auth.token!, body, opts.user);
807
+ if (!r.ok || !r.data) {
808
+ console.error(`Error: ${r.error?.message}`);
809
+ process.exit(1);
810
+ }
811
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
812
+ else console.log(`Created contact: ${r.data.displayName ?? r.data.id} (${r.data.id})`);
813
+ }
814
+ );
815
+
816
+ outlookGraphCommand
817
+ .command('update-contact')
818
+ .description('PATCH a contact (merge JSON from file)')
819
+ .argument('<contactId>', 'Contact id')
820
+ .requiredOption('--json-file <path>', 'Path to JSON patch body')
821
+ .option('--json', 'Echo updated contact as JSON')
822
+ .option('--token <token>', 'Use a specific token')
823
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
824
+ .option('--user <email>', 'Target user (Graph delegation)')
825
+ .action(
826
+ async (
827
+ contactId: string,
828
+ opts: { jsonFile: string; json?: boolean; token?: string; identity?: string; user?: string },
829
+ cmd: any
830
+ ) => {
831
+ checkReadOnly(cmd);
832
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
833
+ if (!auth.success) {
834
+ console.error(`Auth error: ${auth.error}`);
835
+ process.exit(1);
836
+ }
837
+ const raw = await readFile(opts.jsonFile, 'utf-8');
838
+ const patch = JSON.parse(raw) as Record<string, unknown>;
839
+ const r = await updateContact(auth.token!, contactId, patch, opts.user);
840
+ if (!r.ok || !r.data) {
841
+ console.error(`Error: ${r.error?.message}`);
842
+ process.exit(1);
843
+ }
844
+ if (opts.json) console.log(JSON.stringify(r.data, null, 2));
845
+ else console.log(`Updated contact: ${r.data.id}`);
846
+ }
847
+ );
848
+
849
+ outlookGraphCommand
850
+ .command('delete-contact')
851
+ .description('Delete a contact')
852
+ .argument('<contactId>', 'Contact id')
853
+ .option('--confirm', 'Confirm delete')
854
+ .option('--token <token>', 'Use a specific token')
855
+ .option('--identity <name>', 'Graph token cache identity (default: default)')
856
+ .option('--user <email>', 'Target user (Graph delegation)')
857
+ .action(
858
+ async (
859
+ contactId: string,
860
+ opts: { confirm?: boolean; token?: string; identity?: string; user?: string },
861
+ cmd: any
862
+ ) => {
863
+ checkReadOnly(cmd);
864
+ if (!opts.confirm) {
865
+ console.error('Refusing to delete without --confirm');
866
+ process.exit(1);
867
+ }
868
+ const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
869
+ if (!auth.success) {
870
+ console.error(`Auth error: ${auth.error}`);
871
+ process.exit(1);
872
+ }
873
+ const r = await deleteContact(auth.token!, contactId, opts.user);
874
+ if (!r.ok) {
875
+ console.error(`Error: ${r.error?.message}`);
876
+ process.exit(1);
877
+ }
878
+ console.log('Deleted contact.');
879
+ }
880
+ );