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,544 @@
|
|
|
1
|
+
import { 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 { parseDay, parseTimeToDate, toLocalUnzonedISOString, toUTCISOString } from '../lib/dates.js';
|
|
7
|
+
import {
|
|
8
|
+
areRoomsFree,
|
|
9
|
+
createEvent,
|
|
10
|
+
type EmailAttachment,
|
|
11
|
+
getRooms,
|
|
12
|
+
type Recurrence,
|
|
13
|
+
type RecurrencePattern,
|
|
14
|
+
type RecurrenceRange,
|
|
15
|
+
type ReferenceAttachmentInput,
|
|
16
|
+
SENSITIVITY_MAP,
|
|
17
|
+
searchRooms
|
|
18
|
+
} from '../lib/ews-client.js';
|
|
19
|
+
import { lookupMimeType } from '../lib/mime-type.js';
|
|
20
|
+
import { checkReadOnly } from '../lib/utils.js';
|
|
21
|
+
|
|
22
|
+
function formatTime(dateStr: string): string {
|
|
23
|
+
const date = new Date(dateStr);
|
|
24
|
+
return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatDate(dateStr: string): string {
|
|
28
|
+
const date = new Date(dateStr);
|
|
29
|
+
return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const createEventCommand = new Command('create-event')
|
|
33
|
+
.description('Create a new calendar event')
|
|
34
|
+
.argument('<title>', 'Event title/subject')
|
|
35
|
+
.argument('[start]', 'Start time (e.g., 13:00, 1pm) - not needed for all-day events')
|
|
36
|
+
.argument('[end]', 'End time (e.g., 14:00, 2pm) - not needed for all-day events')
|
|
37
|
+
.option('--day <day>', 'Day for the event (today, tomorrow, monday-sunday, YYYY-MM-DD)', 'today')
|
|
38
|
+
.option('--timezone <timezone>', 'Timezone for the event (e.g., "Pacific Standard Time")')
|
|
39
|
+
.option('--description <text>', 'Event description/body')
|
|
40
|
+
.option('--attendees <emails>', 'Comma-separated list of attendee emails')
|
|
41
|
+
.option('--room <room>', 'Meeting room (use --list-rooms to see available)')
|
|
42
|
+
.option('--teams', 'Create as Teams meeting')
|
|
43
|
+
.option('--category <name>', 'Category label (repeatable)', (v, acc) => [...acc, v], [] as string[])
|
|
44
|
+
.option('--all-day', 'Create as an all-day event (no time slots)')
|
|
45
|
+
.option('--sensitivity <level>', 'Sensitivity: normal, personal, private, confidential')
|
|
46
|
+
.option('--list-rooms', 'List available meeting rooms')
|
|
47
|
+
.option('--find-room', 'Find an available room for the time slot')
|
|
48
|
+
.option('--repeat <type>', 'Recurrence: daily, weekly, monthly, yearly')
|
|
49
|
+
.option('--every <n>', 'Repeat every N days/weeks/months (default: 1)', '1')
|
|
50
|
+
.option('--days <days>', 'Days of week for weekly recurrence (mon,tue,wed,thu,fri,sat,sun)')
|
|
51
|
+
.option('--until <date>', 'End date for recurrence (YYYY-MM-DD)')
|
|
52
|
+
.option('--count <n>', 'Number of occurrences (alternative to --until)')
|
|
53
|
+
.option('--json', 'Output as JSON')
|
|
54
|
+
.option('--token <token>', 'Use a specific token')
|
|
55
|
+
.option('--identity <name>', 'Use a specific authentication identity (default: default)')
|
|
56
|
+
.option('--mailbox <email>', 'Create event in shared mailbox calendar')
|
|
57
|
+
.option('--attach <files>', 'Attach file(s), comma-separated paths (relative to cwd)')
|
|
58
|
+
.option(
|
|
59
|
+
'--attach-link <spec>',
|
|
60
|
+
'Attach link: "Title|https://url" or bare https URL (repeatable)',
|
|
61
|
+
(v: string, prev: string[]) => [...prev, v],
|
|
62
|
+
[] as string[]
|
|
63
|
+
)
|
|
64
|
+
.action(
|
|
65
|
+
async (
|
|
66
|
+
title: string,
|
|
67
|
+
startTime: string | undefined,
|
|
68
|
+
endTime: string | undefined,
|
|
69
|
+
options: {
|
|
70
|
+
day: string;
|
|
71
|
+
timezone?: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
attendees?: string;
|
|
74
|
+
room?: string;
|
|
75
|
+
teams?: boolean;
|
|
76
|
+
allDay?: boolean;
|
|
77
|
+
sensitivity?: string;
|
|
78
|
+
listRooms?: boolean;
|
|
79
|
+
findRoom?: boolean;
|
|
80
|
+
repeat?: string;
|
|
81
|
+
every?: string;
|
|
82
|
+
days?: string;
|
|
83
|
+
until?: string;
|
|
84
|
+
count?: string;
|
|
85
|
+
json?: boolean;
|
|
86
|
+
token?: string;
|
|
87
|
+
identity?: string;
|
|
88
|
+
mailbox?: string;
|
|
89
|
+
category?: string[];
|
|
90
|
+
attach?: string;
|
|
91
|
+
attachLink?: string[];
|
|
92
|
+
},
|
|
93
|
+
cmd: any
|
|
94
|
+
) => {
|
|
95
|
+
checkReadOnly(cmd);
|
|
96
|
+
const authResult = await resolveAuth({
|
|
97
|
+
token: options.token,
|
|
98
|
+
identity: options.identity
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!authResult.success) {
|
|
102
|
+
if (options.json) {
|
|
103
|
+
console.log(JSON.stringify({ error: authResult.error }, null, 2));
|
|
104
|
+
} else {
|
|
105
|
+
console.error(`Error: ${authResult.error}`);
|
|
106
|
+
console.error('\nCheck your .env file for EWS_CLIENT_ID and EWS_REFRESH_TOKEN.');
|
|
107
|
+
}
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle --list-rooms
|
|
112
|
+
if (options.listRooms) {
|
|
113
|
+
console.log('\nFetching available meeting rooms...\n');
|
|
114
|
+
|
|
115
|
+
// Search with multiple queries to find more rooms
|
|
116
|
+
const allRooms = new Map<string, { Name: string; Address: string }>();
|
|
117
|
+
const queries = ['room', 'meeting', 'vergader', 'nv-', 'conference'];
|
|
118
|
+
|
|
119
|
+
for (const q of queries) {
|
|
120
|
+
const result = await searchRooms(authResult.token!, q);
|
|
121
|
+
if (result.ok && result.data) {
|
|
122
|
+
for (const room of result.data) {
|
|
123
|
+
if (!allRooms.has(room.Address)) {
|
|
124
|
+
allRooms.set(room.Address, room);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (allRooms.size > 0) {
|
|
131
|
+
console.log('Available rooms:');
|
|
132
|
+
const sortedRooms = [...allRooms.values()].sort((a, b) => a.Name.localeCompare(b.Name));
|
|
133
|
+
for (const room of sortedRooms) {
|
|
134
|
+
console.log(` - ${room.Name} (${room.Address})`);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Fallback to REST API
|
|
140
|
+
const roomsResult = await getRooms(authResult.token!);
|
|
141
|
+
if (roomsResult.ok && roomsResult.data && roomsResult.data.length > 0) {
|
|
142
|
+
console.log('Available rooms:');
|
|
143
|
+
for (const room of roomsResult.data) {
|
|
144
|
+
console.log(` - ${room.Name} (${room.Address})`);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
console.log("No meeting rooms found or you don't have access to room lists.");
|
|
148
|
+
console.log('You can still specify a room by email address with --room <email>');
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Validate start/end times for non-all-day events
|
|
154
|
+
if (!options.allDay && (!startTime || !endTime)) {
|
|
155
|
+
if (options.json) {
|
|
156
|
+
console.log(JSON.stringify({ error: 'Start and end times are required for non-all-day events' }, null, 2));
|
|
157
|
+
} else {
|
|
158
|
+
console.error('Error: Start and end times are required for non-all-day events');
|
|
159
|
+
}
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Parse date and times
|
|
164
|
+
let baseDate: Date;
|
|
165
|
+
try {
|
|
166
|
+
baseDate = parseDay(options.day, { throwOnInvalid: true });
|
|
167
|
+
} catch (err) {
|
|
168
|
+
const message = err instanceof Error ? err.message : 'Invalid day value';
|
|
169
|
+
if (options.json) {
|
|
170
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
171
|
+
} else {
|
|
172
|
+
console.error(`Error: ${message}`);
|
|
173
|
+
}
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let start: Date;
|
|
178
|
+
let end: Date;
|
|
179
|
+
|
|
180
|
+
if (options.allDay) {
|
|
181
|
+
// For all-day events, use midnight boundaries regardless of provided times
|
|
182
|
+
start = new Date(baseDate);
|
|
183
|
+
start.setHours(0, 0, 0, 0);
|
|
184
|
+
end = new Date(baseDate);
|
|
185
|
+
end.setHours(23, 59, 59, 999);
|
|
186
|
+
} else {
|
|
187
|
+
// For regular events, parse the provided times
|
|
188
|
+
try {
|
|
189
|
+
start = parseTimeToDate(startTime!, baseDate, { throwOnInvalid: true });
|
|
190
|
+
} catch (err) {
|
|
191
|
+
const message = err instanceof Error ? err.message : 'Invalid start time';
|
|
192
|
+
if (options.json) {
|
|
193
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
194
|
+
} else {
|
|
195
|
+
console.error(`Error: ${message}`);
|
|
196
|
+
}
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
end = parseTimeToDate(endTime!, baseDate, { throwOnInvalid: true });
|
|
202
|
+
} catch (err) {
|
|
203
|
+
const message = err instanceof Error ? err.message : 'Invalid end time';
|
|
204
|
+
if (options.json) {
|
|
205
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
206
|
+
} else {
|
|
207
|
+
console.error(`Error: ${message}`);
|
|
208
|
+
}
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Parse attendees
|
|
214
|
+
const attendees: Array<{ email: string; name?: string; type?: 'Required' | 'Optional' | 'Resource' }> =
|
|
215
|
+
options.attendees ? options.attendees.split(',').map((e) => ({ email: e.trim() })) : [];
|
|
216
|
+
|
|
217
|
+
// Handle --find-room: find an available room
|
|
218
|
+
let roomEmail: string | undefined;
|
|
219
|
+
let roomName: string | undefined;
|
|
220
|
+
|
|
221
|
+
if (options.findRoom) {
|
|
222
|
+
console.log('Searching for available rooms...');
|
|
223
|
+
|
|
224
|
+
const roomsResult = await getRooms(authResult.token!);
|
|
225
|
+
|
|
226
|
+
if (!roomsResult.ok || !roomsResult.data || roomsResult.data.length === 0) {
|
|
227
|
+
console.error('Could not fetch room list.');
|
|
228
|
+
} else {
|
|
229
|
+
// Batch check all rooms in a single EWS request
|
|
230
|
+
const roomEmails = roomsResult.data.map((r) => r.Address);
|
|
231
|
+
const freeMap = await areRoomsFree(authResult.token!, roomEmails, start.toISOString(), end.toISOString());
|
|
232
|
+
|
|
233
|
+
for (const room of roomsResult.data) {
|
|
234
|
+
if (freeMap.get(room.Address)) {
|
|
235
|
+
roomEmail = room.Address;
|
|
236
|
+
roomName = room.Name;
|
|
237
|
+
console.log(`Found available room: ${room.Name}`);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!roomEmail) {
|
|
243
|
+
console.log('No available rooms found for this time slot.');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} else if (options.room) {
|
|
247
|
+
// User specified a room - could be name or email
|
|
248
|
+
roomName = options.room;
|
|
249
|
+
// If it looks like an email, use it directly
|
|
250
|
+
if (options.room.includes('@')) {
|
|
251
|
+
roomEmail = options.room;
|
|
252
|
+
} else {
|
|
253
|
+
// Try to find the room by name - search for it
|
|
254
|
+
let roomsResult = await searchRooms(authResult.token!, options.room);
|
|
255
|
+
if (!roomsResult.ok || !roomsResult.data || roomsResult.data.length === 0) {
|
|
256
|
+
roomsResult = await getRooms(authResult.token!);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (roomsResult.ok && roomsResult.data) {
|
|
260
|
+
const found = roomsResult.data.find((r) =>
|
|
261
|
+
options.room ? r.Name.toLowerCase().includes(options.room.toLowerCase()) : false
|
|
262
|
+
);
|
|
263
|
+
if (found) {
|
|
264
|
+
roomEmail = found.Address;
|
|
265
|
+
roomName = found.Name;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Add room as attendee if found
|
|
272
|
+
if (roomEmail) {
|
|
273
|
+
attendees.push({ email: roomEmail, name: roomName, type: 'Resource' });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Build recurrence if specified
|
|
277
|
+
let recurrence: Recurrence | undefined;
|
|
278
|
+
if (options.repeat) {
|
|
279
|
+
const dayMap: Record<string, string> = {
|
|
280
|
+
mon: 'Monday',
|
|
281
|
+
monday: 'Monday',
|
|
282
|
+
tue: 'Tuesday',
|
|
283
|
+
tuesday: 'Tuesday',
|
|
284
|
+
wed: 'Wednesday',
|
|
285
|
+
wednesday: 'Wednesday',
|
|
286
|
+
thu: 'Thursday',
|
|
287
|
+
thursday: 'Thursday',
|
|
288
|
+
fri: 'Friday',
|
|
289
|
+
friday: 'Friday',
|
|
290
|
+
sat: 'Saturday',
|
|
291
|
+
saturday: 'Saturday',
|
|
292
|
+
sun: 'Sunday',
|
|
293
|
+
sunday: 'Sunday'
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const patternTypeMap: Record<string, RecurrencePattern['Type']> = {
|
|
297
|
+
daily: 'Daily',
|
|
298
|
+
weekly: 'Weekly',
|
|
299
|
+
monthly: 'AbsoluteMonthly',
|
|
300
|
+
yearly: 'AbsoluteYearly'
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const patternType = patternTypeMap[options.repeat.toLowerCase()];
|
|
304
|
+
if (!patternType) {
|
|
305
|
+
console.error(`Invalid repeat type: ${options.repeat}`);
|
|
306
|
+
console.error('Valid options: daily, weekly, monthly, yearly');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const pattern: RecurrencePattern = {
|
|
311
|
+
Type: patternType,
|
|
312
|
+
Interval: parseInt(options.every || '1', 10) || 1
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// For weekly recurrence, add days of week
|
|
316
|
+
if (patternType === 'Weekly') {
|
|
317
|
+
if (options.days) {
|
|
318
|
+
const days = options.days
|
|
319
|
+
.split(',')
|
|
320
|
+
.map((d) => dayMap[d.trim().toLowerCase()])
|
|
321
|
+
.filter(Boolean);
|
|
322
|
+
if (days.length > 0) {
|
|
323
|
+
pattern.DaysOfWeek = days;
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
// Default to the day of the event
|
|
327
|
+
const dayOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][
|
|
328
|
+
start.getDay()
|
|
329
|
+
];
|
|
330
|
+
pattern.DaysOfWeek = [dayOfWeek];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// For monthly, use day of month
|
|
335
|
+
if (patternType === 'AbsoluteMonthly') {
|
|
336
|
+
pattern.DayOfMonth = start.getDate();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// For yearly, use month and day
|
|
340
|
+
if (patternType === 'AbsoluteYearly') {
|
|
341
|
+
pattern.Month = start.getMonth() + 1;
|
|
342
|
+
pattern.DayOfMonth = start.getDate();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Build range — use local date to avoid UTC shift for late-evening events
|
|
346
|
+
const year = start.getFullYear();
|
|
347
|
+
const month = String(start.getMonth() + 1).padStart(2, '0');
|
|
348
|
+
const day = String(start.getDate()).padStart(2, '0');
|
|
349
|
+
const localDate = `${year}-${month}-${day}`;
|
|
350
|
+
const range: RecurrenceRange = {
|
|
351
|
+
Type: 'NoEnd',
|
|
352
|
+
StartDate: localDate
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
if (options.until) {
|
|
356
|
+
range.Type = 'EndDate';
|
|
357
|
+
range.EndDate = options.until;
|
|
358
|
+
} else if (options.count) {
|
|
359
|
+
range.Type = 'Numbered';
|
|
360
|
+
range.NumberOfOccurrences = parseInt(options.count, 10);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
recurrence = { Pattern: pattern, Range: range };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const sensitivity = options.sensitivity ? SENSITIVITY_MAP[options.sensitivity.toLowerCase()] : undefined;
|
|
367
|
+
|
|
368
|
+
if (options.sensitivity && !sensitivity) {
|
|
369
|
+
console.error(`Invalid sensitivity: ${options.sensitivity}`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const workingDirectory = process.cwd();
|
|
374
|
+
let fileAttachments: EmailAttachment[] | undefined;
|
|
375
|
+
if (options.attach?.trim()) {
|
|
376
|
+
fileAttachments = [];
|
|
377
|
+
const filePaths = options.attach
|
|
378
|
+
.split(',')
|
|
379
|
+
.map((f) => f.trim())
|
|
380
|
+
.filter(Boolean);
|
|
381
|
+
for (const filePath of filePaths) {
|
|
382
|
+
try {
|
|
383
|
+
const validated = await validateAttachmentPath(filePath, workingDirectory);
|
|
384
|
+
const content = await readFile(validated.absolutePath);
|
|
385
|
+
const contentType = lookupMimeType(validated.fileName);
|
|
386
|
+
fileAttachments.push({
|
|
387
|
+
name: validated.fileName,
|
|
388
|
+
contentType,
|
|
389
|
+
contentBytes: content.toString('base64')
|
|
390
|
+
});
|
|
391
|
+
if (!options.json) {
|
|
392
|
+
console.log(` Attaching file: ${validated.fileName} (${Math.round(validated.size / 1024)} KB)`);
|
|
393
|
+
}
|
|
394
|
+
} catch (err) {
|
|
395
|
+
console.error(`Failed to read attachment: ${filePath}`);
|
|
396
|
+
if (err instanceof AttachmentPathError) {
|
|
397
|
+
console.error(err.message);
|
|
398
|
+
} else {
|
|
399
|
+
console.error(err instanceof Error ? err.message : 'Unknown error');
|
|
400
|
+
}
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let referenceAttachments: ReferenceAttachmentInput[] | undefined;
|
|
407
|
+
const linkSpecs = options.attachLink ?? [];
|
|
408
|
+
if (linkSpecs.length > 0) {
|
|
409
|
+
referenceAttachments = [];
|
|
410
|
+
for (const spec of linkSpecs) {
|
|
411
|
+
try {
|
|
412
|
+
const { name, url } = parseAttachLinkSpec(spec);
|
|
413
|
+
referenceAttachments.push({ name, url, contentType: 'text/html' });
|
|
414
|
+
if (!options.json) {
|
|
415
|
+
console.log(` Attaching link: ${name}`);
|
|
416
|
+
}
|
|
417
|
+
} catch (err) {
|
|
418
|
+
const msg =
|
|
419
|
+
err instanceof AttachmentLinkSpecError ? err.message : err instanceof Error ? err.message : String(err);
|
|
420
|
+
console.error(`Invalid --attach-link: ${msg}`);
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Create the event
|
|
427
|
+
const result = await createEvent({
|
|
428
|
+
token: authResult.token!,
|
|
429
|
+
subject: title,
|
|
430
|
+
start: options.timezone ? toLocalUnzonedISOString(start) : toUTCISOString(start),
|
|
431
|
+
end: options.timezone ? toLocalUnzonedISOString(end) : toUTCISOString(end),
|
|
432
|
+
body: options.description,
|
|
433
|
+
location: roomName,
|
|
434
|
+
attendees: attendees.length > 0 ? attendees : undefined,
|
|
435
|
+
isOnlineMeeting: options.teams,
|
|
436
|
+
isAllDay: options.allDay,
|
|
437
|
+
sensitivity,
|
|
438
|
+
recurrence,
|
|
439
|
+
mailbox: options.mailbox,
|
|
440
|
+
timezone: options.timezone,
|
|
441
|
+
categories: options.category && options.category.length > 0 ? options.category : undefined,
|
|
442
|
+
fileAttachments,
|
|
443
|
+
referenceAttachments
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
if (!result.ok || !result.data) {
|
|
447
|
+
if (options.json) {
|
|
448
|
+
console.log(JSON.stringify({ error: result.error?.message || 'Failed to create event' }, null, 2));
|
|
449
|
+
} else {
|
|
450
|
+
console.error(`Error: ${result.error?.message || 'Failed to create event'}`);
|
|
451
|
+
}
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (options.json) {
|
|
456
|
+
console.log(
|
|
457
|
+
JSON.stringify(
|
|
458
|
+
{
|
|
459
|
+
success: true,
|
|
460
|
+
event: {
|
|
461
|
+
id: result.data.Id,
|
|
462
|
+
changeKey: result.data.ChangeKey,
|
|
463
|
+
subject: result.data.Subject,
|
|
464
|
+
start: result.data.Start.DateTime,
|
|
465
|
+
end: result.data.End.DateTime,
|
|
466
|
+
webLink: result.data.WebLink,
|
|
467
|
+
onlineMeetingUrl: result.data.OnlineMeetingUrl,
|
|
468
|
+
fileAttachments: fileAttachments?.length ?? 0,
|
|
469
|
+
referenceAttachments: referenceAttachments?.length ?? 0,
|
|
470
|
+
recurring: !!recurrence,
|
|
471
|
+
recurrence: recurrence
|
|
472
|
+
? {
|
|
473
|
+
type: recurrence.Pattern.Type,
|
|
474
|
+
interval: recurrence.Pattern.Interval,
|
|
475
|
+
daysOfWeek: recurrence.Pattern.DaysOfWeek,
|
|
476
|
+
endType: recurrence.Range.Type,
|
|
477
|
+
endDate: recurrence.Range.EndDate,
|
|
478
|
+
occurrences: recurrence.Range.NumberOfOccurrences
|
|
479
|
+
}
|
|
480
|
+
: undefined
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
null,
|
|
484
|
+
2
|
|
485
|
+
)
|
|
486
|
+
);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
console.log('\n\u2713 Event created successfully!\n');
|
|
491
|
+
console.log(` Title: ${result.data.Subject}`);
|
|
492
|
+
console.log(
|
|
493
|
+
` When: ${formatDate(result.data.Start.DateTime)} ${formatTime(result.data.Start.DateTime)} - ${formatTime(result.data.End.DateTime)}`
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
if (roomName) {
|
|
497
|
+
console.log(` Room: ${roomName}`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (attendees.length > 0) {
|
|
501
|
+
const nonRoomAttendees = attendees.filter((a) => a.type !== 'Resource');
|
|
502
|
+
if (nonRoomAttendees.length > 0) {
|
|
503
|
+
console.log(` Attendees: ${nonRoomAttendees.map((a) => a.email).join(', ')}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (result.data.OnlineMeetingUrl) {
|
|
508
|
+
console.log(` Teams: ${result.data.OnlineMeetingUrl}`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (result.data.WebLink) {
|
|
512
|
+
console.log(` Link: ${result.data.WebLink}`);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (recurrence) {
|
|
516
|
+
let recurrenceDesc = `Every ${recurrence.Pattern.Interval > 1 ? `${recurrence.Pattern.Interval} ` : ''}`;
|
|
517
|
+
switch (recurrence.Pattern.Type) {
|
|
518
|
+
case 'Daily':
|
|
519
|
+
recurrenceDesc += recurrence.Pattern.Interval > 1 ? 'days' : 'day';
|
|
520
|
+
break;
|
|
521
|
+
case 'Weekly':
|
|
522
|
+
recurrenceDesc += recurrence.Pattern.Interval > 1 ? 'weeks' : 'week';
|
|
523
|
+
if (recurrence.Pattern.DaysOfWeek) {
|
|
524
|
+
recurrenceDesc += ` on ${recurrence.Pattern.DaysOfWeek.join(', ')}`;
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
case 'AbsoluteMonthly':
|
|
528
|
+
recurrenceDesc += recurrence.Pattern.Interval > 1 ? 'months' : 'month';
|
|
529
|
+
break;
|
|
530
|
+
case 'AbsoluteYearly':
|
|
531
|
+
recurrenceDesc += recurrence.Pattern.Interval > 1 ? 'years' : 'year';
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
if (recurrence.Range.Type === 'EndDate' && recurrence.Range.EndDate) {
|
|
535
|
+
recurrenceDesc += ` until ${recurrence.Range.EndDate}`;
|
|
536
|
+
} else if (recurrence.Range.Type === 'Numbered' && recurrence.Range.NumberOfOccurrences) {
|
|
537
|
+
recurrenceDesc += ` (${recurrence.Range.NumberOfOccurrences} occurrences)`;
|
|
538
|
+
}
|
|
539
|
+
console.log(` Repeat: ${recurrenceDesc}`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
console.log();
|
|
543
|
+
}
|
|
544
|
+
);
|