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.
- package/LICENSE +22 -0
- package/README.md +916 -0
- package/package.json +50 -0
- package/src/cli.ts +100 -0
- package/src/commands/auto-reply.ts +182 -0
- package/src/commands/calendar.ts +576 -0
- package/src/commands/counter.ts +87 -0
- package/src/commands/create-event.ts +544 -0
- package/src/commands/delegates.ts +286 -0
- package/src/commands/delete-event.ts +321 -0
- package/src/commands/drafts.ts +502 -0
- package/src/commands/files.ts +532 -0
- package/src/commands/find.ts +195 -0
- package/src/commands/findtime.ts +270 -0
- package/src/commands/folders.ts +177 -0
- package/src/commands/forward-event.ts +49 -0
- package/src/commands/graph-calendar.ts +217 -0
- package/src/commands/login.ts +195 -0
- package/src/commands/mail.ts +950 -0
- package/src/commands/oof.ts +263 -0
- package/src/commands/outlook-categories.ts +173 -0
- package/src/commands/outlook-graph.ts +880 -0
- package/src/commands/planner.ts +1678 -0
- package/src/commands/respond.ts +291 -0
- package/src/commands/rooms.ts +210 -0
- package/src/commands/rules.ts +511 -0
- package/src/commands/schedule.ts +109 -0
- package/src/commands/send.ts +204 -0
- package/src/commands/serve.ts +14 -0
- package/src/commands/sharepoint.ts +179 -0
- package/src/commands/site-pages.ts +163 -0
- package/src/commands/subscribe.ts +103 -0
- package/src/commands/subscriptions.ts +29 -0
- package/src/commands/suggest.ts +155 -0
- package/src/commands/todo.ts +2092 -0
- package/src/commands/update-event.ts +608 -0
- package/src/commands/update.ts +88 -0
- package/src/commands/verify-token.ts +62 -0
- package/src/commands/whoami.ts +74 -0
- package/src/index.ts +190 -0
- package/src/lib/atomic-write.ts +20 -0
- package/src/lib/attach-link-spec.test.ts +24 -0
- package/src/lib/attach-link-spec.ts +70 -0
- package/src/lib/attachments.ts +79 -0
- package/src/lib/auth.ts +192 -0
- package/src/lib/calendar-range.test.ts +41 -0
- package/src/lib/calendar-range.ts +103 -0
- package/src/lib/dates.test.ts +74 -0
- package/src/lib/dates.ts +137 -0
- package/src/lib/delegate-client.test.ts +74 -0
- package/src/lib/delegate-client.ts +322 -0
- package/src/lib/ews-client.ts +3418 -0
- package/src/lib/git-commit.ts +4 -0
- package/src/lib/glitchtip-eligibility.ts +220 -0
- package/src/lib/glitchtip.ts +253 -0
- package/src/lib/global-env.ts +3 -0
- package/src/lib/graph-auth.ts +223 -0
- package/src/lib/graph-calendar-client.test.ts +118 -0
- package/src/lib/graph-calendar-client.ts +112 -0
- package/src/lib/graph-client.test.ts +107 -0
- package/src/lib/graph-client.ts +1058 -0
- package/src/lib/graph-constants.ts +12 -0
- package/src/lib/graph-directory.ts +116 -0
- package/src/lib/graph-event.ts +134 -0
- package/src/lib/graph-schedule.ts +173 -0
- package/src/lib/graph-subscriptions.ts +94 -0
- package/src/lib/graph-user-path.ts +13 -0
- package/src/lib/jwt-utils.ts +34 -0
- package/src/lib/markdown.test.ts +21 -0
- package/src/lib/markdown.ts +174 -0
- package/src/lib/mime-type.ts +106 -0
- package/src/lib/oof-client.test.ts +59 -0
- package/src/lib/oof-client.ts +122 -0
- package/src/lib/outlook-graph-client.test.ts +146 -0
- package/src/lib/outlook-graph-client.ts +649 -0
- package/src/lib/outlook-master-categories.ts +145 -0
- package/src/lib/package-info.ts +59 -0
- package/src/lib/places-client.ts +144 -0
- package/src/lib/planner-client.ts +1226 -0
- package/src/lib/rules-client.ts +178 -0
- package/src/lib/sharepoint-client.ts +101 -0
- package/src/lib/site-pages-client.ts +73 -0
- package/src/lib/todo-client.test.ts +298 -0
- package/src/lib/todo-client.ts +1309 -0
- package/src/lib/url-validation.ts +40 -0
- package/src/lib/utils.ts +45 -0
- package/src/lib/webhook-server.ts +51 -0
- package/src/test/auth.test.ts +104 -0
- package/src/test/cli.integration.test.ts +1083 -0
- package/src/test/ews-client.test.ts +268 -0
- package/src/test/mocks/index.ts +375 -0
- package/src/test/mocks/responses.ts +861 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { open, readFile } from 'node:fs/promises';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { AttachmentLinkSpecError, parseAttachLinkSpec } from '../lib/attach-link-spec.js';
|
|
4
|
+
import { AttachmentPathError, validateAttachmentPath } from '../lib/attachments.js';
|
|
5
|
+
import { resolveAuth } from '../lib/auth.js';
|
|
6
|
+
import {
|
|
7
|
+
addAttachmentToDraft,
|
|
8
|
+
addReferenceAttachmentToDraft,
|
|
9
|
+
createDraft,
|
|
10
|
+
deleteDraftById,
|
|
11
|
+
getEmail,
|
|
12
|
+
getEmails,
|
|
13
|
+
sendDraftById,
|
|
14
|
+
updateDraft
|
|
15
|
+
} from '../lib/ews-client.js';
|
|
16
|
+
import { markdownToHtml } from '../lib/markdown.js';
|
|
17
|
+
import { lookupMimeType } from '../lib/mime-type.js';
|
|
18
|
+
import { checkReadOnly } from '../lib/utils.js';
|
|
19
|
+
|
|
20
|
+
function formatDate(dateStr: string): string {
|
|
21
|
+
const date = new Date(dateStr);
|
|
22
|
+
const now = new Date();
|
|
23
|
+
const isToday = date.toDateString() === now.toDateString();
|
|
24
|
+
|
|
25
|
+
if (isToday) {
|
|
26
|
+
return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
27
|
+
} else {
|
|
28
|
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function truncate(str: string, maxLen: number): string {
|
|
33
|
+
if (!str) return '';
|
|
34
|
+
str = str.replace(/\s+/g, ' ').trim();
|
|
35
|
+
if (str.length <= maxLen) return str;
|
|
36
|
+
return `${str.substring(0, maxLen - 1)}\u2026`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const draftsCommand = new Command('drafts')
|
|
40
|
+
.description('Manage email drafts')
|
|
41
|
+
.option('-n, --limit <number>', 'Number of drafts to show', '10')
|
|
42
|
+
.option('-r, --read <id>', 'Read draft by ID')
|
|
43
|
+
.option('--create', 'Create a new draft')
|
|
44
|
+
.option('--edit <id>', 'Edit draft by ID')
|
|
45
|
+
.option('--send <id>', 'Send draft by ID')
|
|
46
|
+
.option('--delete <id>', 'Delete draft by ID')
|
|
47
|
+
.option('--to <emails>', 'Recipient(s) for create/edit, comma-separated')
|
|
48
|
+
.option('--cc <emails>', 'CC recipient(s), comma-separated')
|
|
49
|
+
.option('--subject <text>', 'Subject for create/edit')
|
|
50
|
+
.option('--body <text>', 'Body for create/edit')
|
|
51
|
+
.option('--attach <files>', 'Attach file(s), comma-separated paths')
|
|
52
|
+
.option(
|
|
53
|
+
'--attach-link <spec>',
|
|
54
|
+
'Attach link: "Title|https://url" or bare https URL (repeatable)',
|
|
55
|
+
(v: string, prev: string[]) => [...prev, v],
|
|
56
|
+
[] as string[]
|
|
57
|
+
)
|
|
58
|
+
.option('--markdown', 'Parse body as markdown')
|
|
59
|
+
.option('--html', 'Treat body as HTML')
|
|
60
|
+
.option(
|
|
61
|
+
'--category <name>',
|
|
62
|
+
'Outlook category (repeatable; colors follow mailbox master list)',
|
|
63
|
+
(v: string, prev: string[]) => [...prev, v],
|
|
64
|
+
[] as string[]
|
|
65
|
+
)
|
|
66
|
+
.option('--clear-categories', 'On --edit, remove all categories from the draft')
|
|
67
|
+
.option('--json', 'Output as JSON')
|
|
68
|
+
.option('--token <token>', 'Use a specific token')
|
|
69
|
+
.option('--identity <name>', 'Use a specific authentication identity (EWS; default: default)')
|
|
70
|
+
.option('--mailbox <email>', 'Delegated or shared mailbox drafts folder')
|
|
71
|
+
.action(
|
|
72
|
+
async (
|
|
73
|
+
options: {
|
|
74
|
+
limit: string;
|
|
75
|
+
read?: string;
|
|
76
|
+
create?: boolean;
|
|
77
|
+
edit?: string;
|
|
78
|
+
send?: string;
|
|
79
|
+
delete?: string;
|
|
80
|
+
to?: string;
|
|
81
|
+
cc?: string;
|
|
82
|
+
subject?: string;
|
|
83
|
+
body?: string;
|
|
84
|
+
attach?: string;
|
|
85
|
+
attachLink?: string[];
|
|
86
|
+
markdown?: boolean;
|
|
87
|
+
html?: boolean;
|
|
88
|
+
json?: boolean;
|
|
89
|
+
token?: string;
|
|
90
|
+
identity?: string;
|
|
91
|
+
mailbox?: string;
|
|
92
|
+
category?: string[];
|
|
93
|
+
clearCategories?: boolean;
|
|
94
|
+
},
|
|
95
|
+
cmd: any
|
|
96
|
+
) => {
|
|
97
|
+
if (options.send || options.delete || options.create || options.edit) {
|
|
98
|
+
checkReadOnly(cmd);
|
|
99
|
+
}
|
|
100
|
+
const authResult = await resolveAuth({
|
|
101
|
+
token: options.token,
|
|
102
|
+
identity: options.identity
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!authResult.success) {
|
|
106
|
+
if (options.json) {
|
|
107
|
+
console.log(JSON.stringify({ error: authResult.error }, null, 2));
|
|
108
|
+
} else {
|
|
109
|
+
console.error(`Error: ${authResult.error}`);
|
|
110
|
+
console.error('\nCheck your .env file for EWS_CLIENT_ID and EWS_REFRESH_TOKEN.');
|
|
111
|
+
}
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const limit = parseInt(options.limit, 10) || 10;
|
|
116
|
+
|
|
117
|
+
// Get drafts for listing
|
|
118
|
+
const draftsResult = await getEmails({
|
|
119
|
+
token: authResult.token!,
|
|
120
|
+
folder: 'drafts',
|
|
121
|
+
mailbox: options.mailbox,
|
|
122
|
+
top: limit
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (!draftsResult.ok || !draftsResult.data) {
|
|
126
|
+
if (options.json) {
|
|
127
|
+
console.log(JSON.stringify({ error: draftsResult.error?.message || 'Failed to fetch drafts' }, null, 2));
|
|
128
|
+
} else {
|
|
129
|
+
console.error(`Error: ${draftsResult.error?.message || 'Failed to fetch drafts'}`);
|
|
130
|
+
}
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const drafts = draftsResult.data.value;
|
|
135
|
+
|
|
136
|
+
// Handle create
|
|
137
|
+
if (options.create) {
|
|
138
|
+
const toList = options.to
|
|
139
|
+
? options.to
|
|
140
|
+
.split(',')
|
|
141
|
+
.map((e) => e.trim())
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
: undefined;
|
|
144
|
+
const ccList = options.cc
|
|
145
|
+
? options.cc
|
|
146
|
+
.split(',')
|
|
147
|
+
.map((e) => e.trim())
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
: undefined;
|
|
150
|
+
|
|
151
|
+
let body = options.body;
|
|
152
|
+
if (body) body = body.replace(/\\n/g, '\n');
|
|
153
|
+
let bodyType: 'Text' | 'HTML' = 'Text';
|
|
154
|
+
if (options.html && body) {
|
|
155
|
+
const escaped = body
|
|
156
|
+
.replace(/&/g, '&')
|
|
157
|
+
.replace(/</g, '<')
|
|
158
|
+
.replace(/>/g, '>')
|
|
159
|
+
.replace(/\n/g, '<br>');
|
|
160
|
+
body = body.match(/<\w+[^>]*>/) ? body : escaped;
|
|
161
|
+
bodyType = 'HTML';
|
|
162
|
+
} else if (options.markdown && body) {
|
|
163
|
+
body = markdownToHtml(body);
|
|
164
|
+
bodyType = 'HTML';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const cats = (options.category ?? []).map((c) => c.trim()).filter(Boolean);
|
|
168
|
+
const result = await createDraft(authResult.token!, {
|
|
169
|
+
to: toList,
|
|
170
|
+
cc: ccList,
|
|
171
|
+
subject: options.subject,
|
|
172
|
+
body,
|
|
173
|
+
bodyType,
|
|
174
|
+
mailbox: options.mailbox,
|
|
175
|
+
categories: cats.length ? cats : undefined
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (!result.ok || !result.data) {
|
|
179
|
+
console.error(`Error: ${result.error?.message || 'Failed to create draft'}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Add attachments if specified
|
|
184
|
+
const workingDirectory = process.cwd();
|
|
185
|
+
if (options.attach) {
|
|
186
|
+
const filePaths = options.attach
|
|
187
|
+
.split(',')
|
|
188
|
+
.map((f) => f.trim())
|
|
189
|
+
.filter(Boolean);
|
|
190
|
+
for (const filePath of filePaths) {
|
|
191
|
+
try {
|
|
192
|
+
const validated = await validateAttachmentPath(filePath, workingDirectory);
|
|
193
|
+
const fh = await open(validated.absolutePath, 'r');
|
|
194
|
+
let content: Buffer;
|
|
195
|
+
try {
|
|
196
|
+
const st = await fh.stat();
|
|
197
|
+
if (!st.isFile()) {
|
|
198
|
+
console.error(`Not a file: ${validated.absolutePath}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
if (st.size > 25 * 1024 * 1024) {
|
|
202
|
+
console.error(`File too large (>25MB): ${validated.absolutePath}`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
content = await fh.readFile();
|
|
206
|
+
} finally {
|
|
207
|
+
await fh.close();
|
|
208
|
+
}
|
|
209
|
+
const contentType = lookupMimeType(validated.fileName) || 'application/octet-stream';
|
|
210
|
+
|
|
211
|
+
const attachResult = await addAttachmentToDraft(
|
|
212
|
+
authResult.token!,
|
|
213
|
+
result.data.Id,
|
|
214
|
+
{
|
|
215
|
+
name: validated.fileName,
|
|
216
|
+
contentType,
|
|
217
|
+
contentBytes: content.toString('base64')
|
|
218
|
+
},
|
|
219
|
+
options.mailbox
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (!attachResult.ok) {
|
|
223
|
+
console.error(`Failed to attach ${validated.fileName}: ${attachResult.error?.message}`);
|
|
224
|
+
} else if (!options.json) {
|
|
225
|
+
console.log(` Attached: ${validated.fileName}`);
|
|
226
|
+
}
|
|
227
|
+
} catch (err) {
|
|
228
|
+
if (err instanceof AttachmentPathError) {
|
|
229
|
+
console.error(`Invalid attachment path: ${filePath}: ${err.message}`);
|
|
230
|
+
} else {
|
|
231
|
+
console.error(`Failed to attach: ${filePath}`);
|
|
232
|
+
}
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const linkSpecsCreate = options.attachLink ?? [];
|
|
239
|
+
for (const spec of linkSpecsCreate) {
|
|
240
|
+
try {
|
|
241
|
+
const { name, url } = parseAttachLinkSpec(spec);
|
|
242
|
+
const linkRes = await addReferenceAttachmentToDraft(
|
|
243
|
+
authResult.token!,
|
|
244
|
+
result.data.Id,
|
|
245
|
+
{ name, url, contentType: 'text/html' },
|
|
246
|
+
options.mailbox
|
|
247
|
+
);
|
|
248
|
+
if (!linkRes.ok) {
|
|
249
|
+
console.error(`Failed to attach link ${name}: ${linkRes.error?.message}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
if (!options.json) {
|
|
253
|
+
console.log(` Attached link: ${name}`);
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
const msg =
|
|
257
|
+
err instanceof AttachmentLinkSpecError ? err.message : err instanceof Error ? err.message : String(err);
|
|
258
|
+
console.error(`Invalid --attach-link: ${msg}`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (options.json) {
|
|
264
|
+
console.log(JSON.stringify({ success: true, draftId: result.data.Id }, null, 2));
|
|
265
|
+
} else {
|
|
266
|
+
console.log(`\n\u2713 Draft created`);
|
|
267
|
+
if (options.subject) console.log(` Subject: ${options.subject}`);
|
|
268
|
+
if (toList) console.log(` To: ${toList.join(', ')}`);
|
|
269
|
+
console.log();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Handle read
|
|
276
|
+
if (options.read) {
|
|
277
|
+
const id = options.read.trim();
|
|
278
|
+
const fullDraft = await getEmail(authResult.token!, id, options.mailbox);
|
|
279
|
+
|
|
280
|
+
if (!fullDraft.ok || !fullDraft.data) {
|
|
281
|
+
console.error(`Error: ${fullDraft.error?.message || 'Failed to fetch draft'}`);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const d = fullDraft.data;
|
|
286
|
+
|
|
287
|
+
if (options.json) {
|
|
288
|
+
console.log(JSON.stringify(d, null, 2));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log(`\n${'\u2500'.repeat(60)}`);
|
|
293
|
+
console.log(`To: ${d.ToRecipients?.map((r) => r.EmailAddress?.Address).join(', ') || '(none)'}`);
|
|
294
|
+
console.log(`Subject: ${d.Subject || '(no subject)'}`);
|
|
295
|
+
if (d.Categories?.length) console.log(`Categories: ${d.Categories.join(', ')}`);
|
|
296
|
+
console.log(`${'\u2500'.repeat(60)}\n`);
|
|
297
|
+
console.log(d.Body?.Content || d.BodyPreview || '(no content)');
|
|
298
|
+
console.log(`\n${'\u2500'.repeat(60)}\n`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Handle edit
|
|
303
|
+
if (options.edit) {
|
|
304
|
+
const id = options.edit.trim();
|
|
305
|
+
const toList = options.to
|
|
306
|
+
? options.to
|
|
307
|
+
.split(',')
|
|
308
|
+
.map((e) => e.trim())
|
|
309
|
+
.filter(Boolean)
|
|
310
|
+
: undefined;
|
|
311
|
+
const ccList = options.cc
|
|
312
|
+
? options.cc
|
|
313
|
+
.split(',')
|
|
314
|
+
.map((e) => e.trim())
|
|
315
|
+
.filter(Boolean)
|
|
316
|
+
: undefined;
|
|
317
|
+
|
|
318
|
+
let body = options.body;
|
|
319
|
+
if (body) body = body.replace(/\\n/g, '\n');
|
|
320
|
+
let bodyType: 'Text' | 'HTML' = 'Text';
|
|
321
|
+
if (options.html && body) {
|
|
322
|
+
const escaped = body
|
|
323
|
+
.replace(/&/g, '&')
|
|
324
|
+
.replace(/</g, '<')
|
|
325
|
+
.replace(/>/g, '>')
|
|
326
|
+
.replace(/\n/g, '<br>');
|
|
327
|
+
body = body.match(/<\w+[^>]*>/) ? body : escaped;
|
|
328
|
+
bodyType = 'HTML';
|
|
329
|
+
} else if (options.markdown && body) {
|
|
330
|
+
body = markdownToHtml(body);
|
|
331
|
+
bodyType = 'HTML';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const cats = (options.category ?? []).map((c) => c.trim()).filter(Boolean);
|
|
335
|
+
const result = await updateDraft(authResult.token!, id, {
|
|
336
|
+
to: toList,
|
|
337
|
+
cc: ccList,
|
|
338
|
+
subject: options.subject,
|
|
339
|
+
body,
|
|
340
|
+
bodyType,
|
|
341
|
+
mailbox: options.mailbox,
|
|
342
|
+
categories: cats.length ? cats : undefined,
|
|
343
|
+
clearCategories: options.clearCategories
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
if (!result.ok) {
|
|
347
|
+
console.error(`Error: ${result.error?.message || 'Failed to update draft'}`);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Add attachments if specified
|
|
352
|
+
const workingDirectory = process.cwd();
|
|
353
|
+
if (options.attach) {
|
|
354
|
+
const filePaths = options.attach
|
|
355
|
+
.split(',')
|
|
356
|
+
.map((f) => f.trim())
|
|
357
|
+
.filter(Boolean);
|
|
358
|
+
for (const filePath of filePaths) {
|
|
359
|
+
try {
|
|
360
|
+
const validated = await validateAttachmentPath(filePath, workingDirectory);
|
|
361
|
+
const content = await readFile(validated.absolutePath);
|
|
362
|
+
const contentType = lookupMimeType(validated.fileName) || 'application/octet-stream';
|
|
363
|
+
|
|
364
|
+
await addAttachmentToDraft(
|
|
365
|
+
authResult.token!,
|
|
366
|
+
id,
|
|
367
|
+
{
|
|
368
|
+
name: validated.fileName,
|
|
369
|
+
contentType,
|
|
370
|
+
contentBytes: content.toString('base64')
|
|
371
|
+
},
|
|
372
|
+
options.mailbox
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
if (!options.json) {
|
|
376
|
+
console.log(` Attached: ${validated.fileName}`);
|
|
377
|
+
}
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (err instanceof AttachmentPathError) {
|
|
380
|
+
console.error(`Invalid attachment path: ${filePath}: ${err.message}`);
|
|
381
|
+
} else {
|
|
382
|
+
console.error(`Failed to attach: ${filePath}`);
|
|
383
|
+
}
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const linkSpecsEdit = options.attachLink ?? [];
|
|
390
|
+
for (const spec of linkSpecsEdit) {
|
|
391
|
+
try {
|
|
392
|
+
const { name, url } = parseAttachLinkSpec(spec);
|
|
393
|
+
const linkRes = await addReferenceAttachmentToDraft(
|
|
394
|
+
authResult.token!,
|
|
395
|
+
id,
|
|
396
|
+
{ name, url, contentType: 'text/html' },
|
|
397
|
+
options.mailbox
|
|
398
|
+
);
|
|
399
|
+
if (!linkRes.ok) {
|
|
400
|
+
console.error(`Failed to attach link ${name}: ${linkRes.error?.message}`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
if (!options.json) {
|
|
404
|
+
console.log(` Attached link: ${name}`);
|
|
405
|
+
}
|
|
406
|
+
} catch (err) {
|
|
407
|
+
const msg =
|
|
408
|
+
err instanceof AttachmentLinkSpecError ? err.message : err instanceof Error ? err.message : String(err);
|
|
409
|
+
console.error(`Invalid --attach-link: ${msg}`);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
console.log(`\u2713 Draft updated: ${id}`);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Handle send
|
|
419
|
+
if (options.send) {
|
|
420
|
+
const id = options.send.trim();
|
|
421
|
+
const result = await sendDraftById(authResult.token!, id, options.mailbox);
|
|
422
|
+
|
|
423
|
+
if (!result.ok) {
|
|
424
|
+
console.error(`Error: ${result.error?.message || 'Failed to send draft'}`);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
console.log(`\u2713 Draft sent: ${id}`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Handle delete
|
|
433
|
+
if (options.delete) {
|
|
434
|
+
const id = options.delete.trim();
|
|
435
|
+
if (!id) {
|
|
436
|
+
console.error('Error: --delete requires a draft ID');
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
const result = await deleteDraftById(authResult.token!, id, options.mailbox);
|
|
440
|
+
|
|
441
|
+
if (!result.ok) {
|
|
442
|
+
console.error(`Error: ${result.error?.message || 'Failed to delete draft'}`);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
console.log(`\u2713 Draft deleted: ${id}`);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// List drafts
|
|
451
|
+
if (options.json) {
|
|
452
|
+
console.log(
|
|
453
|
+
JSON.stringify(
|
|
454
|
+
{
|
|
455
|
+
drafts: drafts.map((d, i) => ({
|
|
456
|
+
index: i + 1,
|
|
457
|
+
id: d.Id,
|
|
458
|
+
to: d.ToRecipients?.map((r) => r.EmailAddress?.Address),
|
|
459
|
+
subject: d.Subject,
|
|
460
|
+
preview: d.BodyPreview,
|
|
461
|
+
lastModified: d.ReceivedDateTime,
|
|
462
|
+
categories: d.Categories
|
|
463
|
+
}))
|
|
464
|
+
},
|
|
465
|
+
null,
|
|
466
|
+
2
|
|
467
|
+
)
|
|
468
|
+
);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
console.log(`\n\ud83d\udcdd Drafts${options.mailbox ? ` — ${options.mailbox}` : ''}:\n`);
|
|
473
|
+
console.log('\u2500'.repeat(70));
|
|
474
|
+
|
|
475
|
+
if (drafts.length === 0) {
|
|
476
|
+
console.log('\n No drafts found.\n');
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
for (let i = 0; i < drafts.length; i++) {
|
|
481
|
+
const draft = drafts[i];
|
|
482
|
+
const to = draft.ToRecipients?.map((r) => r.EmailAddress?.Address).join(', ') || '(no recipient)';
|
|
483
|
+
const subject = draft.Subject || '(no subject)';
|
|
484
|
+
const date = draft.ReceivedDateTime ? formatDate(draft.ReceivedDateTime) : '';
|
|
485
|
+
|
|
486
|
+
console.log(
|
|
487
|
+
` [${(i + 1).toString().padStart(2)}] ${truncate(to, 25).padEnd(25)} ${truncate(subject, 32).padEnd(32)} ${date}`
|
|
488
|
+
);
|
|
489
|
+
console.log(` ID: ${draft.Id}`);
|
|
490
|
+
if (draft.Categories?.length) console.log(` Categories: ${draft.Categories.join(', ')}`);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
console.log(`\n${'\u2500'.repeat(70)}`);
|
|
494
|
+
console.log('\nCommands:');
|
|
495
|
+
console.log(' m365-agent-cli drafts -r <id> # Read draft');
|
|
496
|
+
console.log(' m365-agent-cli drafts --create --to "..." --subject "..." --body "..."');
|
|
497
|
+
console.log(' m365-agent-cli drafts --edit <id> --body "new text"');
|
|
498
|
+
console.log(' m365-agent-cli drafts --send <id> # Send draft');
|
|
499
|
+
console.log(' m365-agent-cli drafts --delete <id> # Delete draft');
|
|
500
|
+
console.log();
|
|
501
|
+
}
|
|
502
|
+
);
|