runline 0.36.0 → 0.38.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/dist/credentials/aws-plugin.d.ts +3 -0
- package/dist/credentials/aws-plugin.js +7 -2
- package/dist/credentials/aws.d.ts +11 -0
- package/dist/credentials/aws.js +110 -11
- package/dist/credentials/broker.js +8 -7
- package/dist/credentials/http.d.ts +17 -3
- package/dist/credentials/http.js +31 -9
- package/dist/credentials/transport.js +5 -1
- package/dist/credentials/types.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugins/_shared/attachments.js +97 -0
- package/dist/plugins/_shared/microsoftAuth.js +33 -4
- package/dist/plugins/_shared/schemas.js +8 -0
- package/dist/plugins/agentMail/src/drafts.js +2 -1
- package/dist/plugins/agentMail/src/inboxes.js +2 -1
- package/dist/plugins/agentMail/src/messages.js +2 -1
- package/dist/plugins/agentMail/src/schemas.js +4 -41
- package/dist/plugins/agentMail/src/shared.js +28 -50
- package/dist/plugins/agentMail/src/threads.js +2 -1
- package/dist/plugins/gmail/src/index.js +10 -22
- package/dist/plugins/microsoftCalendar/src/index.js +513 -17
- package/dist/plugins/microsoftFiles/src/index.js +3 -3
- package/dist/plugins/microsoftMail/src/index.js +305 -54
- package/dist/plugins/todoist/src/credentials.js +4 -11
- package/dist/plugins/todoist/src/index.js +390 -281
- package/dist/plugins/zohoMail/src/credentials.js +92 -0
- package/dist/plugins/zohoMail/src/index.js +241 -0
- package/package.json +1 -1
|
@@ -1,9 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Outlook calendar plugin for runline (Microsoft Graph).
|
|
3
|
+
*
|
|
4
|
+
* Auth: the "microsoft" grant (delegated → /me) or app-only
|
|
5
|
+
* (tenantId/clientId/clientSecret + userUpn). See _shared/microsoftAuth.ts.
|
|
6
|
+
* Graph delegated scopes: Calendars.ReadWrite, and Calendars.ReadWrite.Shared
|
|
7
|
+
* for calendars other people shared with the user or made them a delegate
|
|
8
|
+
* of. Those calendars appear in calendar.listCalendars, owner and canEdit
|
|
9
|
+
* included, and every action that takes a calendarId works in them.
|
|
10
|
+
*/
|
|
11
|
+
import { pathSegment, } from "runline";
|
|
12
|
+
import * as t from "typebox";
|
|
1
13
|
import { graphRequest, microsoftCredential, microsoftOAuth, userBase, } from "../../_shared/microsoftAuth.js";
|
|
14
|
+
import { atLeastOne } from "../../_shared/schemas.js";
|
|
2
15
|
const NAME = "microsoftCalendar";
|
|
3
|
-
const SCOPES = [
|
|
16
|
+
const SCOPES = [
|
|
17
|
+
"https://graph.microsoft.com/Calendars.ReadWrite",
|
|
18
|
+
"https://graph.microsoft.com/Calendars.ReadWrite.Shared",
|
|
19
|
+
];
|
|
20
|
+
const STRICT = { additionalProperties: false };
|
|
21
|
+
const SELECT = "id,subject,start,end,location,organizer,attendees,isAllDay,webLink";
|
|
22
|
+
const Id = (description) => t.String({ minLength: 1, ...(description ? { description } : {}) });
|
|
23
|
+
const Addresses = (description) => t.Array(t.String({ minLength: 1 }), { description });
|
|
24
|
+
const Time = (description) => t.String({ minLength: 1, description });
|
|
25
|
+
const TimeZone = t.Optional(t.String({
|
|
26
|
+
minLength: 1,
|
|
27
|
+
description: 'Zone for times without an offset: a Windows name ("Pacific Standard Time") or an IANA name Graph supports ("Asia/Jerusalem"); default UTC',
|
|
28
|
+
}));
|
|
29
|
+
const Top = t.Optional(t.Integer({ minimum: 1, description: "Default 50" }));
|
|
30
|
+
/**
|
|
31
|
+
* The fields Graph requires for each value of an object's `type`, as its
|
|
32
|
+
* reference tables list them: one branch per type.
|
|
33
|
+
*/
|
|
34
|
+
function requiredFor(byType) {
|
|
35
|
+
return {
|
|
36
|
+
anyOf: Object.entries(byType).map(([type, required]) => ({
|
|
37
|
+
properties: { type: { const: type } },
|
|
38
|
+
required,
|
|
39
|
+
})),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const CalendarId = t.Optional(Id("From calendar.listCalendars, including calendars shared with you or delegated to you; the default calendar when omitted"));
|
|
43
|
+
const DayOfWeek = t.Union([
|
|
44
|
+
t.Literal("sunday"),
|
|
45
|
+
t.Literal("monday"),
|
|
46
|
+
t.Literal("tuesday"),
|
|
47
|
+
t.Literal("wednesday"),
|
|
48
|
+
t.Literal("thursday"),
|
|
49
|
+
t.Literal("friday"),
|
|
50
|
+
t.Literal("saturday"),
|
|
51
|
+
]);
|
|
52
|
+
const Recurrence = t.Object({
|
|
53
|
+
pattern: t.Object({
|
|
54
|
+
type: t.Union([
|
|
55
|
+
t.Literal("daily"),
|
|
56
|
+
t.Literal("weekly"),
|
|
57
|
+
t.Literal("absoluteMonthly"),
|
|
58
|
+
t.Literal("relativeMonthly"),
|
|
59
|
+
t.Literal("absoluteYearly"),
|
|
60
|
+
t.Literal("relativeYearly"),
|
|
61
|
+
]),
|
|
62
|
+
interval: t.Integer({ minimum: 1 }),
|
|
63
|
+
daysOfWeek: t.Optional(t.Array(DayOfWeek, { minItems: 1 })),
|
|
64
|
+
dayOfMonth: t.Optional(t.Integer({ minimum: 1, maximum: 31 })),
|
|
65
|
+
month: t.Optional(t.Integer({ minimum: 1, maximum: 12 })),
|
|
66
|
+
index: t.Optional(t.Union([
|
|
67
|
+
t.Literal("first"),
|
|
68
|
+
t.Literal("second"),
|
|
69
|
+
t.Literal("third"),
|
|
70
|
+
t.Literal("fourth"),
|
|
71
|
+
t.Literal("last"),
|
|
72
|
+
])),
|
|
73
|
+
firstDayOfWeek: t.Optional(DayOfWeek),
|
|
74
|
+
}, {
|
|
75
|
+
...STRICT,
|
|
76
|
+
...requiredFor({
|
|
77
|
+
daily: [],
|
|
78
|
+
weekly: ["daysOfWeek", "firstDayOfWeek"],
|
|
79
|
+
absoluteMonthly: ["dayOfMonth"],
|
|
80
|
+
relativeMonthly: ["daysOfWeek"],
|
|
81
|
+
absoluteYearly: ["dayOfMonth", "month"],
|
|
82
|
+
relativeYearly: ["daysOfWeek", "month"],
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
85
|
+
range: t.Object({
|
|
86
|
+
type: t.Union([
|
|
87
|
+
t.Literal("endDate"),
|
|
88
|
+
t.Literal("noEnd"),
|
|
89
|
+
t.Literal("numbered"),
|
|
90
|
+
]),
|
|
91
|
+
startDate: t.String({
|
|
92
|
+
description: "YYYY-MM-DD, the date of the event's start",
|
|
93
|
+
}),
|
|
94
|
+
endDate: t.Optional(t.String({ description: "YYYY-MM-DD" })),
|
|
95
|
+
numberOfOccurrences: t.Optional(t.Integer({ minimum: 1 })),
|
|
96
|
+
recurrenceTimeZone: t.Optional(t.String({ minLength: 1 })),
|
|
97
|
+
}, {
|
|
98
|
+
...STRICT,
|
|
99
|
+
...requiredFor({
|
|
100
|
+
endDate: ["endDate"],
|
|
101
|
+
noEnd: [],
|
|
102
|
+
numbered: ["numberOfOccurrences"],
|
|
103
|
+
}),
|
|
104
|
+
}),
|
|
105
|
+
}, { ...STRICT, description: "Graph patternedRecurrence, sent as given" });
|
|
106
|
+
const START = "ISO datetime. With Z or an offset it is an instant; without, it is read in timeZone. YYYY-MM-DD when allDay.";
|
|
107
|
+
const END = "Same form as start, given with it. For allDay, the day after the last day.";
|
|
108
|
+
/** The event properties event.create sets and event.update changes. */
|
|
109
|
+
const eventChanges = {
|
|
110
|
+
subject: t.Optional(t.String()),
|
|
111
|
+
start: t.Optional(Time(START)),
|
|
112
|
+
end: t.Optional(Time(END)),
|
|
113
|
+
allDay: t.Optional(t.Boolean({ description: "Given with start and end" })),
|
|
114
|
+
body: t.Optional(t.String()),
|
|
115
|
+
location: t.Optional(t.String()),
|
|
116
|
+
attendees: t.Optional(Addresses("Required attendees. With optionalAttendees, replaces the whole attendee list")),
|
|
117
|
+
optionalAttendees: t.Optional(Addresses("Optional attendees. With attendees, replaces the whole attendee list")),
|
|
118
|
+
isOnlineMeeting: t.Optional(t.Boolean({ description: "Attach a Teams meeting" })),
|
|
119
|
+
showAs: t.Optional(t.Union([
|
|
120
|
+
t.Literal("free"),
|
|
121
|
+
t.Literal("tentative"),
|
|
122
|
+
t.Literal("busy"),
|
|
123
|
+
t.Literal("oof"),
|
|
124
|
+
t.Literal("workingElsewhere"),
|
|
125
|
+
])),
|
|
126
|
+
importance: t.Optional(t.Union([t.Literal("low"), t.Literal("normal"), t.Literal("high")])),
|
|
127
|
+
sensitivity: t.Optional(t.Union([
|
|
128
|
+
t.Literal("normal"),
|
|
129
|
+
t.Literal("personal"),
|
|
130
|
+
t.Literal("private"),
|
|
131
|
+
t.Literal("confidential"),
|
|
132
|
+
])),
|
|
133
|
+
reminderMinutesBeforeStart: t.Optional(t.Integer({ minimum: 0 })),
|
|
134
|
+
categories: t.Optional(t.Array(t.String({ minLength: 1 }))),
|
|
135
|
+
recurrence: t.Optional(Recurrence),
|
|
136
|
+
};
|
|
137
|
+
/** The changes, and how their times and body are read. */
|
|
138
|
+
const eventFields = {
|
|
139
|
+
...eventChanges,
|
|
140
|
+
timeZone: TimeZone,
|
|
141
|
+
html: t.Optional(t.Boolean({ description: "body is HTML (default plain text)" })),
|
|
142
|
+
};
|
|
143
|
+
/** One event: its id, and the calendar it was listed from. */
|
|
144
|
+
const eventRef = {
|
|
145
|
+
id: Id(),
|
|
146
|
+
calendarId: t.Optional(Id("The calendar the event was listed from, when not the default calendar, such as one shared with you")),
|
|
147
|
+
};
|
|
148
|
+
const calendarList = t.Object({
|
|
149
|
+
start: Time("ISO start datetime, e.g. 2026-05-01T00:00:00Z"),
|
|
150
|
+
end: Time("ISO end datetime"),
|
|
151
|
+
top: Top,
|
|
152
|
+
calendarId: CalendarId,
|
|
153
|
+
}, STRICT);
|
|
154
|
+
const getSchedule = t.Object({
|
|
155
|
+
schedules: t.Array(t.String({ minLength: 1 }), {
|
|
156
|
+
minItems: 1,
|
|
157
|
+
description: "Addresses of users, rooms or distribution lists",
|
|
158
|
+
}),
|
|
159
|
+
start: Time("ISO start datetime"),
|
|
160
|
+
end: Time("ISO end datetime"),
|
|
161
|
+
timeZone: TimeZone,
|
|
162
|
+
intervalMinutes: t.Optional(t.Integer({
|
|
163
|
+
minimum: 5,
|
|
164
|
+
maximum: 1440,
|
|
165
|
+
description: "Slot length of availabilityView (default 30)",
|
|
166
|
+
})),
|
|
167
|
+
}, STRICT);
|
|
168
|
+
const findMeetingTimes = t.Object({
|
|
169
|
+
attendees: t.Array(t.String({ minLength: 1 }), {
|
|
170
|
+
minItems: 1,
|
|
171
|
+
description: "People who must attend; Graph treats every person as required",
|
|
172
|
+
}),
|
|
173
|
+
start: Time("Earliest start to consider (ISO)"),
|
|
174
|
+
end: Time("Latest end to consider (ISO)"),
|
|
175
|
+
timeZone: TimeZone,
|
|
176
|
+
durationMinutes: t.Optional(t.Integer({ minimum: 1, description: "Default 30" })),
|
|
177
|
+
maxCandidates: t.Optional(t.Integer({ minimum: 1, description: "Default 5" })),
|
|
178
|
+
activityDomain: t.Optional(t.Union([t.Literal("work"), t.Literal("personal"), t.Literal("unrestricted")], { description: "work: within working hours (default)" })),
|
|
179
|
+
isOrganizerOptional: t.Optional(t.Boolean()),
|
|
180
|
+
minimumAttendeePercentage: t.Optional(t.Number({
|
|
181
|
+
minimum: 0,
|
|
182
|
+
maximum: 100,
|
|
183
|
+
description: "Lowest confidence (0-100) a suggestion must have",
|
|
184
|
+
})),
|
|
185
|
+
}, STRICT);
|
|
186
|
+
const eventList = t.Object({
|
|
187
|
+
subject: t.Optional(Id("Subject contains this text")),
|
|
188
|
+
organizer: t.Optional(Id("Organizer email address")),
|
|
189
|
+
category: t.Optional(Id()),
|
|
190
|
+
modifiedSince: t.Optional(Time("Changed at or after this ISO datetime")),
|
|
191
|
+
filter: t.Optional(Id("Raw OData $filter, ANDed with the fields above")),
|
|
192
|
+
orderBy: t.Optional(Id('OData $orderby, e.g. "start/dateTime desc"')),
|
|
193
|
+
top: Top,
|
|
194
|
+
calendarId: CalendarId,
|
|
195
|
+
}, STRICT);
|
|
196
|
+
const eventGet = t.Object(eventRef, STRICT);
|
|
197
|
+
const eventInstances = t.Object({
|
|
198
|
+
...eventRef,
|
|
199
|
+
id: Id("Series master id"),
|
|
200
|
+
start: Time("ISO start"),
|
|
201
|
+
end: Time("ISO end"),
|
|
202
|
+
top: Top,
|
|
203
|
+
}, STRICT);
|
|
204
|
+
const eventCreate = t.Object({
|
|
205
|
+
...eventFields,
|
|
206
|
+
subject: t.String(),
|
|
207
|
+
start: Time(START),
|
|
208
|
+
end: Time(END),
|
|
209
|
+
calendarId: CalendarId,
|
|
210
|
+
}, STRICT);
|
|
211
|
+
const eventUpdate = t.Object({ ...eventRef, ...eventFields }, { ...STRICT, ...atLeastOne(eventChanges) });
|
|
212
|
+
const eventCancel = t.Object({ ...eventRef, comment: t.Optional(t.String()) }, STRICT);
|
|
213
|
+
const eventRespond = t.Object({
|
|
214
|
+
...eventRef,
|
|
215
|
+
response: t.Union([
|
|
216
|
+
t.Literal("accept"),
|
|
217
|
+
t.Literal("tentativelyAccept"),
|
|
218
|
+
t.Literal("decline"),
|
|
219
|
+
]),
|
|
220
|
+
comment: t.Optional(t.String()),
|
|
221
|
+
sendResponse: t.Optional(t.Boolean({ description: "Reply to the organizer (default true)" })),
|
|
222
|
+
proposedStart: t.Optional(Time("decline or tentativelyAccept only, for meetings that allow new time proposals: propose this start instead, same form as event.create")),
|
|
223
|
+
proposedEnd: t.Optional(Time("End of the proposed time; given with proposedStart")),
|
|
224
|
+
timeZone: TimeZone,
|
|
225
|
+
}, STRICT);
|
|
226
|
+
const eventForward = t.Object({
|
|
227
|
+
...eventRef,
|
|
228
|
+
to: t.Array(t.String({ minLength: 1 }), {
|
|
229
|
+
minItems: 1,
|
|
230
|
+
description: "Recipient addresses",
|
|
231
|
+
}),
|
|
232
|
+
comment: t.Optional(t.String()),
|
|
233
|
+
}, STRICT);
|
|
234
|
+
const recipients = (addresses) => (addresses ?? []).map((address) => ({ emailAddress: { address } }));
|
|
235
|
+
/** An OData string literal: single quotes doubled. */
|
|
236
|
+
const literal = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
237
|
+
/** A valid ISO instant, or a refusal naming the field. */
|
|
238
|
+
function instant(value, field) {
|
|
239
|
+
const parsed = new Date(value);
|
|
240
|
+
if (Number.isNaN(parsed.getTime()))
|
|
241
|
+
throw new Error(`${NAME}: ${field} is not an ISO datetime`);
|
|
242
|
+
return parsed;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Graph's dateTimeTimeZone for a time: one with `Z` or an offset is an
|
|
246
|
+
* instant and goes in UTC; one without is a wall-clock time read in
|
|
247
|
+
* `timeZone` (default UTC); an all-day date is midnight of that date.
|
|
248
|
+
*/
|
|
249
|
+
function dateTime(value, field, timeZone, allDay = false) {
|
|
250
|
+
if (allDay) {
|
|
251
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
252
|
+
throw new Error(`${NAME}: ${field} of an all-day event is YYYY-MM-DD`);
|
|
253
|
+
return { dateTime: `${value}T00:00:00`, timeZone: timeZone ?? "UTC" };
|
|
254
|
+
}
|
|
255
|
+
if (/(Z|[+-]\d{2}:?\d{2})$/i.test(value))
|
|
256
|
+
return {
|
|
257
|
+
dateTime: instant(value, field).toISOString().slice(0, -1),
|
|
258
|
+
timeZone: "UTC",
|
|
259
|
+
};
|
|
260
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(value))
|
|
261
|
+
throw new Error(`${NAME}: ${field} is not an ISO datetime`);
|
|
262
|
+
return { dateTime: value, timeZone: timeZone ?? "UTC" };
|
|
263
|
+
}
|
|
264
|
+
/** A Graph event from the fields given; an update patches only those. */
|
|
265
|
+
function toEvent(p) {
|
|
266
|
+
const e = {};
|
|
267
|
+
if (p.start !== undefined || p.end !== undefined || p.allDay !== undefined) {
|
|
268
|
+
if (p.start === undefined || p.end === undefined)
|
|
269
|
+
throw new Error(`${NAME}: start and end are given together`);
|
|
270
|
+
const allDay = p.allDay === true;
|
|
271
|
+
e.start = dateTime(p.start, "start", p.timeZone, allDay);
|
|
272
|
+
e.end = dateTime(p.end, "end", p.timeZone, allDay);
|
|
273
|
+
e.isAllDay = allDay;
|
|
274
|
+
}
|
|
275
|
+
if (p.subject !== undefined)
|
|
276
|
+
e.subject = p.subject;
|
|
277
|
+
if (p.body !== undefined)
|
|
278
|
+
e.body = { contentType: p.html ? "HTML" : "Text", content: p.body };
|
|
279
|
+
if (p.location !== undefined)
|
|
280
|
+
e.location = { displayName: p.location };
|
|
281
|
+
if (p.attendees !== undefined || p.optionalAttendees !== undefined)
|
|
282
|
+
e.attendees = [
|
|
283
|
+
...recipients(p.attendees).map((a) => ({ ...a, type: "required" })),
|
|
284
|
+
...recipients(p.optionalAttendees).map((a) => ({
|
|
285
|
+
...a,
|
|
286
|
+
type: "optional",
|
|
287
|
+
})),
|
|
288
|
+
];
|
|
289
|
+
if (p.isOnlineMeeting !== undefined) {
|
|
290
|
+
e.isOnlineMeeting = p.isOnlineMeeting;
|
|
291
|
+
if (p.isOnlineMeeting)
|
|
292
|
+
e.onlineMeetingProvider = "teamsForBusiness";
|
|
293
|
+
}
|
|
294
|
+
if (p.showAs !== undefined)
|
|
295
|
+
e.showAs = p.showAs;
|
|
296
|
+
if (p.importance !== undefined)
|
|
297
|
+
e.importance = p.importance;
|
|
298
|
+
if (p.sensitivity !== undefined)
|
|
299
|
+
e.sensitivity = p.sensitivity;
|
|
300
|
+
if (p.categories !== undefined)
|
|
301
|
+
e.categories = p.categories;
|
|
302
|
+
if (p.recurrence !== undefined)
|
|
303
|
+
e.recurrence = p.recurrence;
|
|
304
|
+
if (p.reminderMinutesBeforeStart !== undefined) {
|
|
305
|
+
e.isReminderOn = true;
|
|
306
|
+
e.reminderMinutesBeforeStart = p.reminderMinutesBeforeStart;
|
|
307
|
+
}
|
|
308
|
+
return e;
|
|
309
|
+
}
|
|
310
|
+
/** The mailbox, or one calendar in it: the default calendar when no id. */
|
|
311
|
+
function calendarPath(ctx, calendarId) {
|
|
312
|
+
return calendarId
|
|
313
|
+
? `${userBase(ctx)}/calendars/${pathSegment(calendarId)}`
|
|
314
|
+
: userBase(ctx);
|
|
315
|
+
}
|
|
316
|
+
/** One event, under the calendar it was listed from. */
|
|
317
|
+
function eventPath(ctx, ref) {
|
|
318
|
+
return `${calendarPath(ctx, ref.calendarId)}/events/${pathSegment(ref.id)}`;
|
|
319
|
+
}
|
|
320
|
+
function graph(ctx, method, path, body) {
|
|
321
|
+
return graphRequest(ctx, NAME, SCOPES, method, path, body);
|
|
322
|
+
}
|
|
4
323
|
export default function microsoftCalendar(rl) {
|
|
5
324
|
rl.setName(NAME);
|
|
6
|
-
rl.setVersion("1.
|
|
325
|
+
rl.setVersion("1.1.0");
|
|
7
326
|
rl.setCredential(microsoftCredential(NAME, SCOPES));
|
|
8
327
|
rl.setConnectionSchema({
|
|
9
328
|
authMethod: {
|
|
@@ -42,39 +361,216 @@ export default function microsoftCalendar(rl) {
|
|
|
42
361
|
description: "App-only only: target user UPN",
|
|
43
362
|
},
|
|
44
363
|
});
|
|
45
|
-
rl.setOAuth(microsoftOAuth(SCOPES, "Calendars.
|
|
364
|
+
rl.setOAuth(microsoftOAuth(SCOPES, "Calendars.ReadWrite and Calendars.ReadWrite.Shared"));
|
|
46
365
|
rl.registerAction("calendar.list", {
|
|
47
366
|
access: "read",
|
|
48
|
-
description: "List
|
|
49
|
-
inputSchema:
|
|
50
|
-
start: {
|
|
51
|
-
type: "string",
|
|
52
|
-
required: true,
|
|
53
|
-
description: "ISO start datetime, e.g. 2026-05-01T00:00:00Z",
|
|
54
|
-
},
|
|
55
|
-
end: { type: "string", required: true, description: "ISO end datetime" },
|
|
56
|
-
top: { type: "number", required: false, default: 50 },
|
|
57
|
-
},
|
|
367
|
+
description: "List events in a date range, recurring events expanded. Returns [{id,subject,start,end,location,organizer,attendees,isAllDay,webLink}].",
|
|
368
|
+
inputSchema: calendarList,
|
|
58
369
|
async execute(input, ctx) {
|
|
59
370
|
const p = input;
|
|
60
371
|
const qs = new URLSearchParams({
|
|
61
372
|
startDateTime: p.start,
|
|
62
373
|
endDateTime: p.end,
|
|
63
374
|
$top: String(p.top ?? 50),
|
|
64
|
-
$select:
|
|
375
|
+
$select: SELECT,
|
|
65
376
|
$orderby: "start/dateTime",
|
|
66
377
|
});
|
|
67
|
-
const r = await
|
|
378
|
+
const r = await graph(ctx, "GET", `${calendarPath(ctx, p.calendarId)}/calendarView?${qs}`);
|
|
379
|
+
return r.value;
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
rl.registerAction("calendar.listCalendars", {
|
|
383
|
+
access: "read",
|
|
384
|
+
description: "List the calendars you can use, including ones shared with you or delegated to you (owner names whose they are; canEdit says whether you may write). Returns [{id,name,color,isDefaultCalendar,canEdit,owner}].",
|
|
385
|
+
inputSchema: t.Object({}, STRICT),
|
|
386
|
+
async execute(_input, ctx) {
|
|
387
|
+
const qs = new URLSearchParams({
|
|
388
|
+
$select: "id,name,color,isDefaultCalendar,canEdit,owner",
|
|
389
|
+
});
|
|
390
|
+
const r = await graph(ctx, "GET", `${userBase(ctx)}/calendars?${qs}`);
|
|
391
|
+
return r.value;
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
rl.registerAction("calendar.getSchedule", {
|
|
395
|
+
access: "read",
|
|
396
|
+
description: "Free/busy for people, rooms or lists in a time range. Work or school accounts only. Returns [{scheduleId,availabilityView,scheduleItems,workingHours}].",
|
|
397
|
+
inputSchema: getSchedule,
|
|
398
|
+
async execute(input, ctx) {
|
|
399
|
+
const p = input;
|
|
400
|
+
const r = await graph(ctx, "POST", `${userBase(ctx)}/calendar/getSchedule`, {
|
|
401
|
+
schedules: p.schedules,
|
|
402
|
+
startTime: dateTime(p.start, "start", p.timeZone),
|
|
403
|
+
endTime: dateTime(p.end, "end", p.timeZone),
|
|
404
|
+
availabilityViewInterval: p.intervalMinutes ?? 30,
|
|
405
|
+
});
|
|
406
|
+
return r.value;
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
rl.registerAction("calendar.findMeetingTimes", {
|
|
410
|
+
access: "read",
|
|
411
|
+
description: "Suggest meeting times that fit the attendees' free/busy and working hours. Delegated connections of work or school accounts only. Returns {suggestions:[{meetingTimeSlot,confidence,attendeeAvailability,suggestionReason}], emptySuggestionsReason}.",
|
|
412
|
+
inputSchema: findMeetingTimes,
|
|
413
|
+
async execute(input, ctx) {
|
|
414
|
+
const p = input;
|
|
415
|
+
const r = await graph(ctx, "POST", `${userBase(ctx)}/findMeetingTimes`, {
|
|
416
|
+
attendees: recipients(p.attendees).map((a) => ({
|
|
417
|
+
...a,
|
|
418
|
+
type: "required",
|
|
419
|
+
})),
|
|
420
|
+
timeConstraint: {
|
|
421
|
+
activityDomain: p.activityDomain ?? "work",
|
|
422
|
+
timeSlots: [
|
|
423
|
+
{
|
|
424
|
+
start: dateTime(p.start, "start", p.timeZone),
|
|
425
|
+
end: dateTime(p.end, "end", p.timeZone),
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
},
|
|
429
|
+
meetingDuration: `PT${p.durationMinutes ?? 30}M`,
|
|
430
|
+
maxCandidates: p.maxCandidates ?? 5,
|
|
431
|
+
returnSuggestionReasons: true,
|
|
432
|
+
...(p.isOrganizerOptional === undefined
|
|
433
|
+
? {}
|
|
434
|
+
: { isOrganizerOptional: p.isOrganizerOptional }),
|
|
435
|
+
...(p.minimumAttendeePercentage === undefined
|
|
436
|
+
? {}
|
|
437
|
+
: { minimumAttendeePercentage: p.minimumAttendeePercentage }),
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
suggestions: r.meetingTimeSuggestions ?? [],
|
|
441
|
+
emptySuggestionsReason: r.emptySuggestionsReason || undefined,
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
rl.registerAction("event.list", {
|
|
446
|
+
access: "read",
|
|
447
|
+
description: "Find events by subject, organizer, category or change time. Recurring events come back as their series, not expanded (use calendar.list for a date range). Returns [{id,subject,start,end,location,organizer,attendees,isAllDay,webLink}].",
|
|
448
|
+
inputSchema: eventList,
|
|
449
|
+
async execute(input, ctx) {
|
|
450
|
+
const p = input;
|
|
451
|
+
const filters = [];
|
|
452
|
+
if (p.subject)
|
|
453
|
+
filters.push(`contains(subject,${literal(p.subject)})`);
|
|
454
|
+
if (p.organizer)
|
|
455
|
+
filters.push(`organizer/emailAddress/address eq ${literal(p.organizer)}`);
|
|
456
|
+
if (p.category)
|
|
457
|
+
filters.push(`categories/any(c:c eq ${literal(p.category)})`);
|
|
458
|
+
if (p.modifiedSince)
|
|
459
|
+
filters.push(`lastModifiedDateTime ge ${instant(p.modifiedSince, "modifiedSince").toISOString()}`);
|
|
460
|
+
if (p.filter)
|
|
461
|
+
filters.push(`(${p.filter})`);
|
|
462
|
+
const qs = new URLSearchParams({
|
|
463
|
+
$top: String(p.top ?? 50),
|
|
464
|
+
$select: SELECT,
|
|
465
|
+
});
|
|
466
|
+
if (filters.length)
|
|
467
|
+
qs.set("$filter", filters.join(" and "));
|
|
468
|
+
if (p.orderBy)
|
|
469
|
+
qs.set("$orderby", p.orderBy);
|
|
470
|
+
const r = await graph(ctx, "GET", `${calendarPath(ctx, p.calendarId)}/events?${qs}`);
|
|
68
471
|
return r.value;
|
|
69
472
|
},
|
|
70
473
|
});
|
|
71
474
|
rl.registerAction("event.get", {
|
|
72
475
|
access: "read",
|
|
73
476
|
description: "Get one calendar event by id (full details incl. body).",
|
|
74
|
-
inputSchema:
|
|
477
|
+
inputSchema: eventGet,
|
|
478
|
+
async execute(input, ctx) {
|
|
479
|
+
return graph(ctx, "GET", eventPath(ctx, input));
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
rl.registerAction("event.listInstances", {
|
|
483
|
+
access: "read",
|
|
484
|
+
description: "List occurrences of a recurring event (by its series id) in a date range.",
|
|
485
|
+
inputSchema: eventInstances,
|
|
486
|
+
async execute(input, ctx) {
|
|
487
|
+
const p = input;
|
|
488
|
+
const qs = new URLSearchParams({
|
|
489
|
+
startDateTime: p.start,
|
|
490
|
+
endDateTime: p.end,
|
|
491
|
+
$top: String(p.top ?? 50),
|
|
492
|
+
$select: SELECT,
|
|
493
|
+
});
|
|
494
|
+
const r = await graph(ctx, "GET", `${eventPath(ctx, p)}/instances?${qs}`);
|
|
495
|
+
return r.value;
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
rl.registerAction("event.create", {
|
|
499
|
+
access: "write",
|
|
500
|
+
description: "Create a calendar event; attendees are sent invitations. Returns {id, webLink}. Get user approval before inviting others.",
|
|
501
|
+
inputSchema: eventCreate,
|
|
502
|
+
async execute(input, ctx) {
|
|
503
|
+
const p = input;
|
|
504
|
+
const r = await graph(ctx, "POST", `${calendarPath(ctx, p.calendarId)}/events`, toEvent(p));
|
|
505
|
+
return { id: r.id, webLink: r.webLink };
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
rl.registerAction("event.update", {
|
|
509
|
+
access: "write",
|
|
510
|
+
description: "Change an event; only the fields given change. Attendees are notified. Returns {id, webLink}.",
|
|
511
|
+
inputSchema: eventUpdate,
|
|
75
512
|
async execute(input, ctx) {
|
|
76
513
|
const p = input;
|
|
77
|
-
|
|
514
|
+
const r = await graph(ctx, "PATCH", eventPath(ctx, p), toEvent(p));
|
|
515
|
+
return { id: r.id, webLink: r.webLink };
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
rl.registerAction("event.delete", {
|
|
519
|
+
access: "write",
|
|
520
|
+
description: "Delete an event. Deleting a meeting from the organizer's calendar sends attendees a cancellation; event.cancel also carries a comment. Returns {success}.",
|
|
521
|
+
inputSchema: eventGet,
|
|
522
|
+
async execute(input, ctx) {
|
|
523
|
+
return graph(ctx, "DELETE", eventPath(ctx, input));
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
rl.registerAction("event.cancel", {
|
|
527
|
+
access: "write",
|
|
528
|
+
description: "Organizer only: cancel a meeting and send attendees a cancellation with an optional comment. Returns {success}.",
|
|
529
|
+
inputSchema: eventCancel,
|
|
530
|
+
async execute(input, ctx) {
|
|
531
|
+
const p = input;
|
|
532
|
+
return graph(ctx, "POST", `${eventPath(ctx, p)}/cancel`, p.comment === undefined ? {} : { comment: p.comment });
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
rl.registerAction("event.respond", {
|
|
536
|
+
access: "write",
|
|
537
|
+
description: "Accept, tentatively accept or decline a meeting invitation, optionally proposing a new time. Returns {success}.",
|
|
538
|
+
inputSchema: eventRespond,
|
|
539
|
+
async execute(input, ctx) {
|
|
540
|
+
const p = input;
|
|
541
|
+
const proposing = p.proposedStart !== undefined || p.proposedEnd !== undefined;
|
|
542
|
+
if (proposing) {
|
|
543
|
+
if (p.proposedStart === undefined || p.proposedEnd === undefined)
|
|
544
|
+
throw new Error(`${NAME}: proposedStart and proposedEnd are given together`);
|
|
545
|
+
if (p.response === "accept")
|
|
546
|
+
throw new Error(`${NAME}: a new time is proposed only with decline or tentativelyAccept`);
|
|
547
|
+
if (p.sendResponse === false)
|
|
548
|
+
throw new Error(`${NAME}: proposing a new time sends a response to the organizer`);
|
|
549
|
+
}
|
|
550
|
+
return graph(ctx, "POST", `${eventPath(ctx, p)}/${p.response}`, {
|
|
551
|
+
sendResponse: p.sendResponse ?? true,
|
|
552
|
+
...(p.comment === undefined ? {} : { comment: p.comment }),
|
|
553
|
+
...(p.proposedStart !== undefined && p.proposedEnd !== undefined
|
|
554
|
+
? {
|
|
555
|
+
proposedNewTime: {
|
|
556
|
+
start: dateTime(p.proposedStart, "proposedStart", p.timeZone),
|
|
557
|
+
end: dateTime(p.proposedEnd, "proposedEnd", p.timeZone),
|
|
558
|
+
},
|
|
559
|
+
}
|
|
560
|
+
: {}),
|
|
561
|
+
});
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
rl.registerAction("event.forward", {
|
|
565
|
+
access: "write",
|
|
566
|
+
description: "Forward a meeting invitation to more people, with an optional comment. Returns {success}. Get user approval before forwarding.",
|
|
567
|
+
inputSchema: eventForward,
|
|
568
|
+
async execute(input, ctx) {
|
|
569
|
+
const p = input;
|
|
570
|
+
return graph(ctx, "POST", `${eventPath(ctx, p)}/forward`, {
|
|
571
|
+
toRecipients: recipients(p.to),
|
|
572
|
+
...(p.comment === undefined ? {} : { comment: p.comment }),
|
|
573
|
+
});
|
|
78
574
|
},
|
|
79
575
|
});
|
|
80
576
|
}
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* _shared/microsoftAuth.ts. Graph delegated scopes: Files.ReadWrite.All,
|
|
8
8
|
* Sites.ReadWrite.All (use the .Read.* variants if you only need reads).
|
|
9
9
|
*/
|
|
10
|
-
import { jsonAnswer, pathSegment, pathSegments,
|
|
10
|
+
import { jsonAnswer, pathSegment, pathSegments, } from "runline";
|
|
11
11
|
import { standardBase64 } from "../../_shared/base64.js";
|
|
12
|
-
import { graphRequest, graphResponse, microsoftCredential, microsoftDownload, microsoftDriveBase, microsoftOAuth, } from "../../_shared/microsoftAuth.js";
|
|
12
|
+
import { graphFailed, graphRequest, graphResponse, microsoftCredential, microsoftDownload, microsoftDriveBase, microsoftOAuth, } from "../../_shared/microsoftAuth.js";
|
|
13
13
|
const NAME = "microsoftFiles";
|
|
14
14
|
const SCOPES = [
|
|
15
15
|
"https://graph.microsoft.com/Files.ReadWrite.All",
|
|
@@ -161,7 +161,7 @@ export default function microsoftFiles(rl) {
|
|
|
161
161
|
const bytes = Buffer.from(content, "base64");
|
|
162
162
|
const res = await binaryFetch(ctx, "PUT", `${driveBase(ctx)}/root:/${pathSegments(path)}:/content`, bytes);
|
|
163
163
|
if (!res.ok)
|
|
164
|
-
throw
|
|
164
|
+
throw await graphFailed(NAME, res);
|
|
165
165
|
return jsonAnswer(res);
|
|
166
166
|
},
|
|
167
167
|
});
|