runline 0.11.6 → 0.13.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 (256) hide show
  1. package/dist/commands/actions.js +3 -1
  2. package/dist/core/engine.js +3 -0
  3. package/dist/index.d.ts +1 -1
  4. package/dist/plugin/api.d.ts +2 -1
  5. package/dist/plugin/types.d.ts +2 -0
  6. package/dist/plugins/_shared/googleSchemas.js +23 -0
  7. package/dist/plugins/actionNetwork/src/index.js +23 -0
  8. package/dist/plugins/activeCampaign/src/index.js +48 -0
  9. package/dist/plugins/adalo/src/index.js +5 -0
  10. package/dist/plugins/affinity/src/index.js +16 -0
  11. package/dist/plugins/agileCrm/src/index.js +15 -0
  12. package/dist/plugins/airtable/src/index.js +10 -0
  13. package/dist/plugins/airtop/src/index.js +24 -0
  14. package/dist/plugins/apiTemplateIo/src/index.js +4 -0
  15. package/dist/plugins/asana/src/index.js +22 -0
  16. package/dist/plugins/autopilot/src/index.js +11 -0
  17. package/dist/plugins/bambooHr/src/index.js +11 -0
  18. package/dist/plugins/bannerbear/src/index.js +4 -0
  19. package/dist/plugins/baserow/src/index.js +8 -0
  20. package/dist/plugins/beeminder/src/index.js +18 -0
  21. package/dist/plugins/bitly/src/index.js +3 -0
  22. package/dist/plugins/bitwarden/src/index.js +19 -0
  23. package/dist/plugins/box/src/index.js +11 -0
  24. package/dist/plugins/brandfetch/src/index.js +5 -0
  25. package/dist/plugins/brevo/src/index.js +15 -0
  26. package/dist/plugins/bubble/src/index.js +5 -0
  27. package/dist/plugins/chargebee/src/index.js +5 -0
  28. package/dist/plugins/circleci/src/index.js +3 -0
  29. package/dist/plugins/ciscoWebex/src/index.js +10 -0
  30. package/dist/plugins/clearbit/src/index.js +3 -0
  31. package/dist/plugins/clickup/src/index.js +61 -0
  32. package/dist/plugins/clockify/src/index.js +25 -0
  33. package/dist/plugins/cloudflare/src/index.js +4 -0
  34. package/dist/plugins/cockpit/src/index.js +5 -0
  35. package/dist/plugins/coda/src/index.js +18 -0
  36. package/dist/plugins/coingecko/src/index.js +10 -0
  37. package/dist/plugins/contentful/src/index.js +7 -0
  38. package/dist/plugins/convertkit/src/index.js +16 -0
  39. package/dist/plugins/copper/src/index.js +7 -0
  40. package/dist/plugins/cortex/src/index.js +4 -0
  41. package/dist/plugins/currents/src/index.js +22 -0
  42. package/dist/plugins/customerIo/src/index.js +9 -0
  43. package/dist/plugins/databricks/src/index.js +34 -0
  44. package/dist/plugins/deepl/src/index.js +2 -0
  45. package/dist/plugins/demio/src/index.js +4 -0
  46. package/dist/plugins/dhl/src/index.js +1 -0
  47. package/dist/plugins/discord/src/index.js +13 -0
  48. package/dist/plugins/discourse/src/index.js +16 -0
  49. package/dist/plugins/disqus/src/index.js +4 -0
  50. package/dist/plugins/docker/src/index.js +5 -0
  51. package/dist/plugins/drift/src/index.js +5 -0
  52. package/dist/plugins/dropbox/src/index.js +9 -0
  53. package/dist/plugins/dropcontact/src/index.js +2 -0
  54. package/dist/plugins/egoi/src/index.js +4 -0
  55. package/dist/plugins/elasticsearch/src/index.js +9 -0
  56. package/dist/plugins/emelia/src/index.js +9 -0
  57. package/dist/plugins/erpnext/src/index.js +5 -0
  58. package/dist/plugins/facebookGraph/src/index.js +1 -0
  59. package/dist/plugins/freshdesk/src/index.js +10 -0
  60. package/dist/plugins/freshservice/src/index.js +7 -0
  61. package/dist/plugins/freshworksCrm/src/index.js +7 -0
  62. package/dist/plugins/getresponse/src/index.js +5 -0
  63. package/dist/plugins/ghost/src/index.js +5 -0
  64. package/dist/plugins/github/src/index.js +37 -0
  65. package/dist/plugins/gitlab/src/index.js +17 -0
  66. package/dist/plugins/gmail/src/index.js +200 -184
  67. package/dist/plugins/gong/src/index.js +4 -0
  68. package/dist/plugins/googleAppsScript/src/index.js +98 -43
  69. package/dist/plugins/googleCalendar/src/index.js +329 -250
  70. package/dist/plugins/googleContacts/src/index.js +144 -145
  71. package/dist/plugins/googleDocs/src/documents.js +31 -38
  72. package/dist/plugins/googleDocs/src/formatting.js +51 -47
  73. package/dist/plugins/googleDocs/src/images.js +23 -26
  74. package/dist/plugins/googleDocs/src/shared.js +55 -0
  75. package/dist/plugins/googleDocs/src/structure.js +111 -123
  76. package/dist/plugins/googleDocs/src/tables.js +103 -154
  77. package/dist/plugins/googleDocs/src/tabs.js +27 -23
  78. package/dist/plugins/googleDocs/src/text.js +106 -136
  79. package/dist/plugins/googleDrive/src/index.js +461 -408
  80. package/dist/plugins/googleImage/src/index.js +14 -16
  81. package/dist/plugins/googleSheets/src/index.js +527 -339
  82. package/dist/plugins/googleSlides/src/index.js +92 -72
  83. package/dist/plugins/googleTasks/src/index.js +94 -92
  84. package/dist/plugins/gotify/src/index.js +3 -0
  85. package/dist/plugins/gotowebinar/src/index.js +20 -0
  86. package/dist/plugins/grafana/src/index.js +17 -0
  87. package/dist/plugins/graphql/src/index.js +2 -0
  88. package/dist/plugins/grist/src/index.js +4 -0
  89. package/dist/plugins/hackernews/src/index.js +6 -0
  90. package/dist/plugins/halopsa/src/index.js +5 -0
  91. package/dist/plugins/harvest/src/index.js +14 -0
  92. package/dist/plugins/helpscout/src/index.js +13 -0
  93. package/dist/plugins/highlevel/src/index.js +17 -0
  94. package/dist/plugins/homeAssistant/src/index.js +13 -0
  95. package/dist/plugins/hubspot/src/index.js +14 -0
  96. package/dist/plugins/humanticAi/src/index.js +3 -0
  97. package/dist/plugins/hunter/src/index.js +3 -0
  98. package/dist/plugins/intercom/src/index.js +10 -0
  99. package/dist/plugins/iterable/src/index.js +6 -0
  100. package/dist/plugins/jenkins/src/index.js +11 -0
  101. package/dist/plugins/jira/src/index.js +16 -0
  102. package/dist/plugins/keap/src/index.js +28 -0
  103. package/dist/plugins/kobotoolbox/src/index.js +17 -0
  104. package/dist/plugins/lemlist/src/index.js +15 -0
  105. package/dist/plugins/linear/src/attachments.js +6 -0
  106. package/dist/plugins/linear/src/comments.js +5 -0
  107. package/dist/plugins/linear/src/cycles.js +2 -0
  108. package/dist/plugins/linear/src/initiatives.js +5 -0
  109. package/dist/plugins/linear/src/issues.js +14 -0
  110. package/dist/plugins/linear/src/labels.js +5 -0
  111. package/dist/plugins/linear/src/organization.js +1 -0
  112. package/dist/plugins/linear/src/projects.js +11 -0
  113. package/dist/plugins/linear/src/shared.js +2 -0
  114. package/dist/plugins/linear/src/states.js +2 -0
  115. package/dist/plugins/linear/src/teams.js +3 -0
  116. package/dist/plugins/linear/src/users.js +2 -0
  117. package/dist/plugins/linear/src/views.js +4 -0
  118. package/dist/plugins/linear/src/webhooks.js +4 -0
  119. package/dist/plugins/lingvanex/src/index.js +1 -0
  120. package/dist/plugins/linkedin/src/index.js +1 -0
  121. package/dist/plugins/lonescale/src/index.js +4 -0
  122. package/dist/plugins/magento/src/index.js +15 -0
  123. package/dist/plugins/mailcheck/src/index.js +1 -0
  124. package/dist/plugins/mailchimp/src/index.js +14 -0
  125. package/dist/plugins/mailerlite/src/index.js +4 -0
  126. package/dist/plugins/mailgun/src/index.js +1 -0
  127. package/dist/plugins/mailjet/src/index.js +3 -0
  128. package/dist/plugins/mandrill/src/index.js +2 -0
  129. package/dist/plugins/marketstack/src/index.js +3 -0
  130. package/dist/plugins/matrix/src/index.js +10 -0
  131. package/dist/plugins/mattermost/src/index.js +19 -0
  132. package/dist/plugins/mautic/src/index.js +20 -0
  133. package/dist/plugins/medium/src/index.js +3 -0
  134. package/dist/plugins/messagebird/src/index.js +2 -0
  135. package/dist/plugins/metabase/src/index.js +10 -0
  136. package/dist/plugins/microsoftCalendar/src/index.js +2 -0
  137. package/dist/plugins/microsoftFiles/src/index.js +5 -0
  138. package/dist/plugins/microsoftMail/src/index.js +4 -0
  139. package/dist/plugins/misp/src/index.js +44 -0
  140. package/dist/plugins/mocean/src/index.js +2 -0
  141. package/dist/plugins/monday/src/index.js +18 -0
  142. package/dist/plugins/monicaCrm/src/index.js +5 -0
  143. package/dist/plugins/msg91/src/index.js +1 -0
  144. package/dist/plugins/nasa/src/index.js +7 -0
  145. package/dist/plugins/netlify/src/index.js +7 -0
  146. package/dist/plugins/netscalerAdc/src/index.js +3 -0
  147. package/dist/plugins/nextcloud/src/index.js +13 -0
  148. package/dist/plugins/nocodb/src/index.js +5 -0
  149. package/dist/plugins/node/src/index.js +38 -0
  150. package/dist/plugins/notion/src/index.js +14 -0
  151. package/dist/plugins/npm/src/index.js +5 -0
  152. package/dist/plugins/odoo/src/index.js +6 -0
  153. package/dist/plugins/okta/src/index.js +5 -0
  154. package/dist/plugins/oneSimpleApi/src/index.js +10 -0
  155. package/dist/plugins/onfleet/src/index.js +24 -0
  156. package/dist/plugins/openThesaurus/src/index.js +1 -0
  157. package/dist/plugins/openai/src/index.js +1 -0
  158. package/dist/plugins/openweathermap/src/index.js +2 -0
  159. package/dist/plugins/oura/src/index.js +2 -0
  160. package/dist/plugins/paddle/src/index.js +9 -0
  161. package/dist/plugins/pagerduty/src/index.js +9 -0
  162. package/dist/plugins/parallel/src/index.js +1 -0
  163. package/dist/plugins/paypal/src/index.js +4 -0
  164. package/dist/plugins/peekalink/src/index.js +2 -0
  165. package/dist/plugins/phantombuster/src/index.js +5 -0
  166. package/dist/plugins/philipsHue/src/index.js +4 -0
  167. package/dist/plugins/pipedrive/src/index.js +14 -0
  168. package/dist/plugins/plivo/src/index.js +3 -0
  169. package/dist/plugins/postbin/src/index.js +6 -0
  170. package/dist/plugins/posthog/src/index.js +5 -0
  171. package/dist/plugins/profitwell/src/index.js +2 -0
  172. package/dist/plugins/pushbullet/src/index.js +4 -0
  173. package/dist/plugins/pushcut/src/index.js +1 -0
  174. package/dist/plugins/pushover/src/index.js +1 -0
  175. package/dist/plugins/quickbase/src/index.js +8 -0
  176. package/dist/plugins/quickbooks/src/index.js +5 -0
  177. package/dist/plugins/quickchart/src/index.js +1 -0
  178. package/dist/plugins/raindrop/src/index.js +13 -0
  179. package/dist/plugins/recraft/src/index.js +1 -0
  180. package/dist/plugins/reddit/src/index.js +10 -0
  181. package/dist/plugins/replicate/src/index.js +1 -0
  182. package/dist/plugins/rocketchat/src/index.js +1 -0
  183. package/dist/plugins/rundeck/src/index.js +2 -0
  184. package/dist/plugins/salesforce/src/metadata.js +4 -0
  185. package/dist/plugins/salesforce/src/query.js +5 -0
  186. package/dist/plugins/salesforce/src/sobjects.js +12 -0
  187. package/dist/plugins/salesmate/src/index.js +5 -0
  188. package/dist/plugins/securityScorecard/src/index.js +14 -0
  189. package/dist/plugins/segment/src/index.js +4 -0
  190. package/dist/plugins/sendgrid/src/index.js +10 -0
  191. package/dist/plugins/sendy/src/index.js +6 -0
  192. package/dist/plugins/sentry/src/index.js +24 -0
  193. package/dist/plugins/servicenow/src/index.js +10 -0
  194. package/dist/plugins/shiftLabs/src/index.js +6 -0
  195. package/dist/plugins/shiftLabs/src/issues.js +4 -0
  196. package/dist/plugins/shiftLabs/src/pages.js +10 -0
  197. package/dist/plugins/shiftLabs/src/shared.js +2 -0
  198. package/dist/plugins/shiftLabs/src/transcription.js +203 -0
  199. package/dist/plugins/shopify/src/index.js +10 -0
  200. package/dist/plugins/signl4/src/index.js +2 -0
  201. package/dist/plugins/slack/src/index.js +38 -0
  202. package/dist/plugins/sms77/src/index.js +2 -0
  203. package/dist/plugins/splunk/src/index.js +16 -0
  204. package/dist/plugins/spotify/src/index.js +30 -0
  205. package/dist/plugins/stackby/src/index.js +4 -0
  206. package/dist/plugins/steel/src/browser.js +7 -0
  207. package/dist/plugins/steel/src/captchas.js +3 -0
  208. package/dist/plugins/steel/src/credentials.js +4 -0
  209. package/dist/plugins/steel/src/extensions.js +5 -0
  210. package/dist/plugins/steel/src/files.js +10 -0
  211. package/dist/plugins/steel/src/profiles.js +4 -0
  212. package/dist/plugins/steel/src/sessions.js +11 -0
  213. package/dist/plugins/storyblok/src/index.js +7 -0
  214. package/dist/plugins/strapi/src/index.js +5 -0
  215. package/dist/plugins/strava/src/index.js +6 -0
  216. package/dist/plugins/stripe/src/index.js +20 -0
  217. package/dist/plugins/supabase/src/index.js +5 -0
  218. package/dist/plugins/syncromsp/src/index.js +20 -0
  219. package/dist/plugins/tapfiliate/src/index.js +11 -0
  220. package/dist/plugins/telegram/src/index.js +21 -0
  221. package/dist/plugins/thehive/src/index.js +23 -0
  222. package/dist/plugins/thehiveProject/src/index.js +31 -0
  223. package/dist/plugins/todoist/src/index.js +31 -0
  224. package/dist/plugins/together/src/index.js +1 -0
  225. package/dist/plugins/travisci/src/index.js +5 -0
  226. package/dist/plugins/trello/src/index.js +37 -0
  227. package/dist/plugins/twake/src/index.js +1 -0
  228. package/dist/plugins/twilio/src/index.js +2 -0
  229. package/dist/plugins/twist/src/index.js +22 -0
  230. package/dist/plugins/twitter/src/index.js +8 -0
  231. package/dist/plugins/unleashedSoftware/src/index.js +3 -0
  232. package/dist/plugins/uplead/src/index.js +2 -0
  233. package/dist/plugins/uproc/src/index.js +1 -0
  234. package/dist/plugins/uptimerobot/src/index.js +22 -0
  235. package/dist/plugins/urlscanio/src/index.js +3 -0
  236. package/dist/plugins/vercel/src/account.js +1 -0
  237. package/dist/plugins/vercel/src/deployments.js +5 -0
  238. package/dist/plugins/vercel/src/env.js +4 -0
  239. package/dist/plugins/vercel/src/projects.js +2 -0
  240. package/dist/plugins/vercel/src/shared.js +1 -0
  241. package/dist/plugins/vero/src/index.js +6 -0
  242. package/dist/plugins/vonage/src/index.js +1 -0
  243. package/dist/plugins/wekan/src/index.js +24 -0
  244. package/dist/plugins/woocommerce/src/index.js +5 -0
  245. package/dist/plugins/wordpress/src/index.js +10 -0
  246. package/dist/plugins/xai/src/index.js +1 -0
  247. package/dist/plugins/xero/src/index.js +8 -0
  248. package/dist/plugins/yourls/src/index.js +3 -0
  249. package/dist/plugins/zammad/src/index.js +12 -0
  250. package/dist/plugins/zendesk/src/index.js +18 -0
  251. package/dist/plugins/zoho/src/index.js +6 -0
  252. package/dist/plugins/zoom/src/index.js +5 -0
  253. package/dist/plugins/zulip/src/index.js +15 -0
  254. package/dist/sdk.d.ts +2 -1
  255. package/dist/sdk.js +1 -0
  256. package/package.json +1 -1
@@ -28,7 +28,9 @@
28
28
  * call `event.listInstances`.
29
29
  */
30
30
  import rrulePkg from "rrule";
31
+ import * as t from "typebox";
31
32
  import { googleAccessToken } from "../../_shared/googleAuth.js";
33
+ import { Id, NonEmptyString, NonEmptyStringArray, PositiveInteger, StringOrStringArray, stringEnum, } from "../../_shared/googleSchemas.js";
32
34
  // `rrule` ships as CJS; named imports fail under Node ESM.
33
35
  const { RRule } = rrulePkg;
34
36
  // ─── Auth ────────────────────────────────────────────────────────
@@ -234,9 +236,9 @@ function applyEventFields(body, p) {
234
236
  body.guestsCanModify = p.guestsCanModify;
235
237
  if (typeof p.guestsCanSeeOtherGuests === "boolean")
236
238
  body.guestsCanSeeOtherGuests = p.guestsCanSeeOtherGuests;
237
- const attendees = splitAttendees(p.attendees);
238
- if (attendees)
239
- body.attendees = attendees;
239
+ if (p.attendees !== undefined) {
240
+ body.attendees = splitAttendees(p.attendees) ?? [];
241
+ }
240
242
  if (p.reminders !== undefined || p.useDefaultReminders !== undefined) {
241
243
  const useDefault = p.useDefaultReminders !== false && !Array.isArray(p.reminders);
242
244
  body.reminders = { useDefault };
@@ -255,6 +257,286 @@ function applyEventFields(body, p) {
255
257
  }
256
258
  // ─── Plugin ──────────────────────────────────────────────────────
257
259
  const SCOPES = ["https://www.googleapis.com/auth/calendar"];
260
+ const strictObject = (properties, options = {}) => t.Object(properties, { ...options, additionalProperties: false });
261
+ const optionalString = (description) => t.Optional(t.String(description ? { description } : {}));
262
+ const optionalBoolean = t.Optional(t.Boolean());
263
+ const optionalPositiveInteger = t.Optional(PositiveInteger);
264
+ const calendarId = NonEmptyString;
265
+ const accessRole = stringEnum([
266
+ "freeBusyReader",
267
+ "reader",
268
+ "writer",
269
+ "owner",
270
+ ]);
271
+ const sendUpdates = stringEnum(["all", "externalOnly", "none"]);
272
+ const reminder = strictObject({
273
+ method: stringEnum(["email", "popup"]),
274
+ minutes: t.Integer({ minimum: 0 }),
275
+ });
276
+ const reminders = t.Array(reminder);
277
+ const attendees = StringOrStringArray;
278
+ const recurrenceFields = {
279
+ rrule: t.Optional(NonEmptyString),
280
+ repeatFrequency: t.Optional(stringEnum(["daily", "weekly", "monthly", "yearly"])),
281
+ repeatHowManyTimes: t.Optional(PositiveInteger),
282
+ repeatUntil: t.Optional(NonEmptyString),
283
+ };
284
+ const recurrenceConstraints = [
285
+ { not: { required: ["repeatHowManyTimes", "repeatUntil"] } },
286
+ { not: { required: ["rrule", "repeatFrequency"] } },
287
+ { not: { required: ["rrule", "repeatHowManyTimes"] } },
288
+ { not: { required: ["rrule", "repeatUntil"] } },
289
+ {
290
+ anyOf: [
291
+ {
292
+ not: {
293
+ anyOf: [
294
+ { required: ["repeatHowManyTimes"] },
295
+ { required: ["repeatUntil"] },
296
+ ],
297
+ },
298
+ },
299
+ { required: ["repeatFrequency"] },
300
+ ],
301
+ },
302
+ ];
303
+ const eventMutableFields = {
304
+ summary: optionalString(),
305
+ description: optionalString(),
306
+ location: optionalString(),
307
+ colorId: optionalString(),
308
+ transparency: t.Optional(stringEnum(["opaque", "transparent"])),
309
+ visibility: t.Optional(stringEnum(["default", "public", "private", "confidential"])),
310
+ guestsCanInviteOthers: optionalBoolean,
311
+ guestsCanModify: optionalBoolean,
312
+ guestsCanSeeOtherGuests: optionalBoolean,
313
+ attendees: t.Optional(attendees),
314
+ reminders: t.Optional(reminders),
315
+ useDefaultReminders: optionalBoolean,
316
+ };
317
+ const eventListFields = {
318
+ q: optionalString(),
319
+ timeMin: optionalString("ISO datetime"),
320
+ timeMax: optionalString("ISO datetime"),
321
+ updatedMin: optionalString("ISO datetime"),
322
+ timeZone: optionalString(),
323
+ iCalUID: optionalString(),
324
+ orderBy: t.Optional(stringEnum(["startTime", "updated"])),
325
+ singleEvents: optionalBoolean,
326
+ showDeleted: optionalBoolean,
327
+ showHiddenInvitations: optionalBoolean,
328
+ maxAttendees: optionalPositiveInteger,
329
+ maxResults: optionalPositiveInteger,
330
+ pageToken: optionalString(),
331
+ returnAll: optionalBoolean,
332
+ fields: optionalString(),
333
+ nextOccurrence: optionalBoolean,
334
+ };
335
+ const calendarSchemas = {
336
+ list: strictObject({
337
+ returnAll: optionalBoolean,
338
+ maxResults: optionalPositiveInteger,
339
+ pageToken: optionalString(),
340
+ showHidden: optionalBoolean,
341
+ showDeleted: optionalBoolean,
342
+ minAccessRole: t.Optional(accessRole),
343
+ }),
344
+ get: strictObject({ calendarId }),
345
+ availability: strictObject({
346
+ calendarId: t.Optional(calendarId),
347
+ calendarIds: t.Optional(NonEmptyStringArray),
348
+ timeMin: NonEmptyString,
349
+ timeMax: NonEmptyString,
350
+ timeZone: optionalString(),
351
+ outputFormat: t.Optional(stringEnum(["availability", "bookedSlots", "raw"])),
352
+ }, {
353
+ oneOf: [
354
+ {
355
+ required: ["calendarId"],
356
+ not: { required: ["calendarIds"] },
357
+ },
358
+ {
359
+ required: ["calendarIds"],
360
+ not: { required: ["calendarId"] },
361
+ },
362
+ ],
363
+ }),
364
+ listColors: strictObject({}),
365
+ };
366
+ const eventSchemas = {
367
+ create: strictObject({
368
+ calendarId,
369
+ ...eventMutableFields,
370
+ start: NonEmptyString,
371
+ end: NonEmptyString,
372
+ allDay: optionalBoolean,
373
+ timeZone: optionalString(),
374
+ id: optionalString("Custom event ID"),
375
+ ...recurrenceFields,
376
+ conferenceSolution: t.Optional(stringEnum([
377
+ "eventHangout",
378
+ "eventNamedHangout",
379
+ "hangoutsMeet",
380
+ ])),
381
+ sendUpdates: t.Optional(sendUpdates),
382
+ maxAttendees: optionalPositiveInteger,
383
+ }, { allOf: recurrenceConstraints }),
384
+ get: strictObject({
385
+ calendarId,
386
+ eventId: Id,
387
+ timeZone: optionalString(),
388
+ maxAttendees: optionalPositiveInteger,
389
+ nextOccurrence: optionalBoolean,
390
+ }),
391
+ list: strictObject({ calendarId, ...eventListFields }),
392
+ listInstances: strictObject({
393
+ calendarId,
394
+ eventId: Id,
395
+ timeMin: optionalString(),
396
+ timeMax: optionalString(),
397
+ timeZone: optionalString(),
398
+ showDeleted: optionalBoolean,
399
+ maxResults: optionalPositiveInteger,
400
+ pageToken: optionalString(),
401
+ returnAll: optionalBoolean,
402
+ }),
403
+ update: strictObject({
404
+ calendarId,
405
+ eventId: Id,
406
+ modifyTarget: t.Optional(stringEnum(["instance", "series"])),
407
+ ...eventMutableFields,
408
+ start: optionalString(),
409
+ end: optionalString(),
410
+ allDay: optionalBoolean,
411
+ timeZone: optionalString(),
412
+ ...recurrenceFields,
413
+ sendUpdates: t.Optional(sendUpdates),
414
+ maxAttendees: optionalPositiveInteger,
415
+ sendNotifications: optionalBoolean,
416
+ }, {
417
+ allOf: [
418
+ ...recurrenceConstraints,
419
+ {
420
+ anyOf: [
421
+ {
422
+ not: {
423
+ anyOf: [{ required: ["start"] }, { required: ["end"] }],
424
+ },
425
+ },
426
+ { required: ["start", "end"] },
427
+ ],
428
+ },
429
+ {
430
+ not: {
431
+ required: ["allDay"],
432
+ not: { required: ["start"] },
433
+ },
434
+ },
435
+ {
436
+ not: {
437
+ required: ["timeZone"],
438
+ not: { required: ["start"] },
439
+ },
440
+ },
441
+ ],
442
+ anyOf: [
443
+ ...Object.keys(eventMutableFields).map((key) => ({ required: [key] })),
444
+ { required: ["start"] },
445
+ { required: ["rrule"] },
446
+ { required: ["repeatFrequency"] },
447
+ ],
448
+ }),
449
+ delete: strictObject({
450
+ calendarId,
451
+ eventId: Id,
452
+ sendUpdates: t.Optional(sendUpdates),
453
+ }),
454
+ move: strictObject({
455
+ calendarId,
456
+ eventId: Id,
457
+ destinationCalendarId: calendarId,
458
+ sendUpdates: t.Optional(sendUpdates),
459
+ }),
460
+ };
461
+ const freeBusySchema = strictObject({
462
+ calendarIds: NonEmptyStringArray,
463
+ timeMin: NonEmptyString,
464
+ timeMax: NonEmptyString,
465
+ timeZone: optionalString(),
466
+ groupExpansionMax: optionalPositiveInteger,
467
+ calendarExpansionMax: optionalPositiveInteger,
468
+ });
469
+ const calendarListSchemas = {
470
+ list: strictObject({
471
+ minAccessRole: t.Optional(accessRole),
472
+ showDeleted: optionalBoolean,
473
+ showHidden: optionalBoolean,
474
+ returnAll: optionalBoolean,
475
+ }),
476
+ insert: strictObject({
477
+ calendarId,
478
+ colorRgbFormat: optionalBoolean,
479
+ defaultReminders: t.Optional(reminders),
480
+ summaryOverride: optionalString(),
481
+ }),
482
+ patch: strictObject({
483
+ calendarId,
484
+ colorId: optionalString(),
485
+ backgroundColor: optionalString(),
486
+ foregroundColor: optionalString(),
487
+ summaryOverride: optionalString(),
488
+ selected: optionalBoolean,
489
+ hidden: optionalBoolean,
490
+ defaultReminders: t.Optional(reminders),
491
+ }, {
492
+ anyOf: [
493
+ { required: ["colorId"] },
494
+ { required: ["backgroundColor"] },
495
+ { required: ["foregroundColor"] },
496
+ { required: ["summaryOverride"] },
497
+ { required: ["selected"] },
498
+ { required: ["hidden"] },
499
+ { required: ["defaultReminders"] },
500
+ ],
501
+ }),
502
+ delete: strictObject({ calendarId }),
503
+ };
504
+ const aclRole = stringEnum([
505
+ "none",
506
+ "freeBusyReader",
507
+ "reader",
508
+ "writer",
509
+ "owner",
510
+ ]);
511
+ const aclSchemas = {
512
+ list: strictObject({ calendarId, returnAll: optionalBoolean }),
513
+ insert: strictObject({
514
+ calendarId,
515
+ role: aclRole,
516
+ scopeType: stringEnum(["default", "user", "group", "domain"]),
517
+ scopeValue: optionalString(),
518
+ sendNotifications: optionalBoolean,
519
+ }, {
520
+ anyOf: [
521
+ {
522
+ properties: { scopeType: { const: "default" } },
523
+ not: { required: ["scopeValue"] },
524
+ },
525
+ {
526
+ properties: {
527
+ scopeType: { enum: ["user", "group", "domain"] },
528
+ },
529
+ required: ["scopeValue"],
530
+ },
531
+ ],
532
+ }),
533
+ update: strictObject({ calendarId, ruleId: Id, role: aclRole }),
534
+ delete: strictObject({ calendarId, ruleId: Id }),
535
+ };
536
+ const settingsSchemas = {
537
+ list: strictObject({ returnAll: optionalBoolean }),
538
+ get: strictObject({ setting: NonEmptyString }),
539
+ };
258
540
  export default function googleCalendar(rl) {
259
541
  rl.setName("googleCalendar");
260
542
  rl.setVersion("0.1.0");
@@ -345,19 +627,9 @@ export default function googleCalendar(rl) {
345
627
  });
346
628
  // ── Calendar ──────────────────────────────────────────
347
629
  rl.registerAction("calendar.list", {
630
+ access: "read",
348
631
  description: "List calendars the authenticated user has access to",
349
- inputSchema: {
350
- returnAll: { type: "boolean", required: false },
351
- maxResults: { type: "number", required: false },
352
- pageToken: { type: "string", required: false },
353
- showHidden: { type: "boolean", required: false },
354
- showDeleted: { type: "boolean", required: false },
355
- minAccessRole: {
356
- type: "string",
357
- required: false,
358
- description: "freeBusyReader | reader | writer | owner",
359
- },
360
- },
632
+ inputSchema: calendarSchemas.list,
361
633
  async execute(input, ctx) {
362
634
  const p = (input ?? {});
363
635
  const qs = {};
@@ -377,49 +649,18 @@ export default function googleCalendar(rl) {
377
649
  },
378
650
  });
379
651
  rl.registerAction("calendar.get", {
652
+ access: "read",
380
653
  description: "Get a calendar's metadata (including conference solutions)",
381
- inputSchema: {
382
- calendarId: {
383
- type: "string",
384
- required: true,
385
- description: 'Calendar ID (email) or "primary"',
386
- },
387
- },
654
+ inputSchema: calendarSchemas.get,
388
655
  async execute(input, ctx) {
389
656
  const { calendarId } = input;
390
657
  return calRequest(ctx, "GET", `/users/me/calendarList/${encodeCalendarId(calendarId)}`);
391
658
  },
392
659
  });
393
660
  rl.registerAction("calendar.availability", {
661
+ access: "read",
394
662
  description: "Check free/busy information for one or more calendars over a time range",
395
- inputSchema: {
396
- calendarId: {
397
- type: "string",
398
- required: false,
399
- description: 'Single calendar ID (email) or "primary"',
400
- },
401
- calendarIds: {
402
- type: "array",
403
- required: false,
404
- description: "Multiple calendar IDs (takes precedence over calendarId)",
405
- },
406
- timeMin: {
407
- type: "string",
408
- required: true,
409
- description: "ISO datetime — start of the interval",
410
- },
411
- timeMax: {
412
- type: "string",
413
- required: true,
414
- description: "ISO datetime — end of the interval",
415
- },
416
- timeZone: { type: "string", required: false },
417
- outputFormat: {
418
- type: "string",
419
- required: false,
420
- description: "availability | bookedSlots | raw (default: raw)",
421
- },
422
- },
663
+ inputSchema: calendarSchemas.availability,
423
664
  async execute(input, ctx) {
424
665
  const p = (input ?? {});
425
666
  const ids = Array.isArray(p.calendarIds)
@@ -466,69 +707,18 @@ export default function googleCalendar(rl) {
466
707
  },
467
708
  });
468
709
  rl.registerAction("calendar.listColors", {
710
+ access: "read",
469
711
  description: "List event and calendar color palettes available in Google Calendar",
712
+ inputSchema: calendarSchemas.listColors,
470
713
  async execute(_input, ctx) {
471
714
  return calRequest(ctx, "GET", "/colors");
472
715
  },
473
716
  });
474
717
  // ── Event ─────────────────────────────────────────────
475
718
  rl.registerAction("event.create", {
719
+ access: "write",
476
720
  description: "Create a calendar event",
477
- inputSchema: {
478
- calendarId: { type: "string", required: true },
479
- summary: { type: "string", required: false },
480
- description: { type: "string", required: false },
481
- location: { type: "string", required: false },
482
- start: {
483
- type: "string",
484
- required: true,
485
- description: "ISO datetime (or YYYY-MM-DD when allDay)",
486
- },
487
- end: { type: "string", required: true },
488
- allDay: { type: "boolean", required: false },
489
- timeZone: { type: "string", required: false },
490
- attendees: {
491
- type: "array",
492
- required: false,
493
- description: "Array of email addresses, or a comma-separated string",
494
- },
495
- colorId: { type: "string", required: false },
496
- id: { type: "string", required: false, description: "Custom event ID" },
497
- transparency: {
498
- type: "string",
499
- required: false,
500
- description: 'Show me as: "opaque" (busy) | "transparent" (free)',
501
- },
502
- visibility: { type: "string", required: false },
503
- guestsCanInviteOthers: { type: "boolean", required: false },
504
- guestsCanModify: { type: "boolean", required: false },
505
- guestsCanSeeOtherGuests: { type: "boolean", required: false },
506
- reminders: {
507
- type: "array",
508
- required: false,
509
- description: '[{method: "email"|"popup", minutes: number}]',
510
- },
511
- useDefaultReminders: { type: "boolean", required: false },
512
- rrule: { type: "string", required: false, description: "e.g. FREQ=WEEKLY;COUNT=5" },
513
- repeatFrequency: {
514
- type: "string",
515
- required: false,
516
- description: "daily | weekly | monthly | yearly",
517
- },
518
- repeatHowManyTimes: { type: "number", required: false },
519
- repeatUntil: { type: "string", required: false, description: "ISO datetime" },
520
- conferenceSolution: {
521
- type: "string",
522
- required: false,
523
- description: "eventHangout | eventNamedHangout | hangoutsMeet",
524
- },
525
- sendUpdates: {
526
- type: "string",
527
- required: false,
528
- description: "all | externalOnly | none",
529
- },
530
- maxAttendees: { type: "number", required: false },
531
- },
721
+ inputSchema: eventSchemas.create,
532
722
  async execute(input, ctx) {
533
723
  const p = (input ?? {});
534
724
  const body = {};
@@ -550,18 +740,9 @@ export default function googleCalendar(rl) {
550
740
  },
551
741
  });
552
742
  rl.registerAction("event.get", {
743
+ access: "read",
553
744
  description: "Get a single event",
554
- inputSchema: {
555
- calendarId: { type: "string", required: true },
556
- eventId: { type: "string", required: true },
557
- timeZone: { type: "string", required: false },
558
- maxAttendees: { type: "number", required: false },
559
- nextOccurrence: {
560
- type: "boolean",
561
- required: false,
562
- description: "Attach nextOccurrence for recurring events (default: true)",
563
- },
564
- },
745
+ inputSchema: eventSchemas.get,
565
746
  async execute(input, ctx) {
566
747
  const p = (input ?? {});
567
748
  const qs = {};
@@ -576,34 +757,9 @@ export default function googleCalendar(rl) {
576
757
  },
577
758
  });
578
759
  rl.registerAction("event.list", {
760
+ access: "read",
579
761
  description: "List events in a calendar. Set `singleEvents=true` to expand recurring events into instances.",
580
- inputSchema: {
581
- calendarId: { type: "string", required: true },
582
- q: { type: "string", required: false, description: "Free-text query" },
583
- timeMin: { type: "string", required: false, description: "ISO datetime" },
584
- timeMax: { type: "string", required: false },
585
- updatedMin: { type: "string", required: false },
586
- timeZone: { type: "string", required: false },
587
- iCalUID: { type: "string", required: false },
588
- orderBy: {
589
- type: "string",
590
- required: false,
591
- description: "startTime (requires singleEvents=true) | updated",
592
- },
593
- singleEvents: { type: "boolean", required: false },
594
- showDeleted: { type: "boolean", required: false },
595
- showHiddenInvitations: { type: "boolean", required: false },
596
- maxAttendees: { type: "number", required: false },
597
- maxResults: { type: "number", required: false },
598
- pageToken: { type: "string", required: false },
599
- returnAll: { type: "boolean", required: false },
600
- fields: { type: "string", required: false },
601
- nextOccurrence: {
602
- type: "boolean",
603
- required: false,
604
- description: "Attach nextOccurrence to recurring events (default: true; ignored when singleEvents=true)",
605
- },
606
- },
762
+ inputSchema: eventSchemas.list,
607
763
  async execute(input, ctx) {
608
764
  const p = (input ?? {});
609
765
  const qs = {};
@@ -645,18 +801,9 @@ export default function googleCalendar(rl) {
645
801
  },
646
802
  });
647
803
  rl.registerAction("event.listInstances", {
804
+ access: "read",
648
805
  description: "List instances of a recurring event",
649
- inputSchema: {
650
- calendarId: { type: "string", required: true },
651
- eventId: { type: "string", required: true },
652
- timeMin: { type: "string", required: false },
653
- timeMax: { type: "string", required: false },
654
- timeZone: { type: "string", required: false },
655
- showDeleted: { type: "boolean", required: false },
656
- maxResults: { type: "number", required: false },
657
- pageToken: { type: "string", required: false },
658
- returnAll: { type: "boolean", required: false },
659
- },
806
+ inputSchema: eventSchemas.listInstances,
660
807
  async execute(input, ctx) {
661
808
  const p = (input ?? {});
662
809
  const qs = {};
@@ -679,39 +826,9 @@ export default function googleCalendar(rl) {
679
826
  },
680
827
  });
681
828
  rl.registerAction("event.update", {
829
+ access: "write",
682
830
  description: "Patch an event (only supplied fields are changed). Set modifyTarget='series' to edit the entire recurrence instead of a single instance.",
683
- inputSchema: {
684
- calendarId: { type: "string", required: true },
685
- eventId: { type: "string", required: true },
686
- modifyTarget: {
687
- type: "string",
688
- required: false,
689
- description: "instance (default) | series",
690
- },
691
- summary: { type: "string", required: false },
692
- description: { type: "string", required: false },
693
- location: { type: "string", required: false },
694
- start: { type: "string", required: false },
695
- end: { type: "string", required: false },
696
- allDay: { type: "boolean", required: false },
697
- timeZone: { type: "string", required: false },
698
- attendees: { type: "array", required: false },
699
- colorId: { type: "string", required: false },
700
- transparency: { type: "string", required: false },
701
- visibility: { type: "string", required: false },
702
- guestsCanInviteOthers: { type: "boolean", required: false },
703
- guestsCanModify: { type: "boolean", required: false },
704
- guestsCanSeeOtherGuests: { type: "boolean", required: false },
705
- reminders: { type: "array", required: false },
706
- useDefaultReminders: { type: "boolean", required: false },
707
- rrule: { type: "string", required: false },
708
- repeatFrequency: { type: "string", required: false },
709
- repeatHowManyTimes: { type: "number", required: false },
710
- repeatUntil: { type: "string", required: false },
711
- sendUpdates: { type: "string", required: false },
712
- maxAttendees: { type: "number", required: false },
713
- sendNotifications: { type: "boolean", required: false },
714
- },
831
+ inputSchema: eventSchemas.update,
715
832
  async execute(input, ctx) {
716
833
  const p = (input ?? {});
717
834
  let eventId = p.eventId;
@@ -748,12 +865,9 @@ export default function googleCalendar(rl) {
748
865
  },
749
866
  });
750
867
  rl.registerAction("event.delete", {
868
+ access: "write",
751
869
  description: "Delete an event",
752
- inputSchema: {
753
- calendarId: { type: "string", required: true },
754
- eventId: { type: "string", required: true },
755
- sendUpdates: { type: "string", required: false },
756
- },
870
+ inputSchema: eventSchemas.delete,
757
871
  async execute(input, ctx) {
758
872
  const p = (input ?? {});
759
873
  const qs = {};
@@ -763,13 +877,9 @@ export default function googleCalendar(rl) {
763
877
  },
764
878
  });
765
879
  rl.registerAction("event.move", {
880
+ access: "write",
766
881
  description: "Move an event from one calendar to another",
767
- inputSchema: {
768
- calendarId: { type: "string", required: true, description: "Source calendar" },
769
- eventId: { type: "string", required: true },
770
- destinationCalendarId: { type: "string", required: true },
771
- sendUpdates: { type: "string", required: false },
772
- },
882
+ inputSchema: eventSchemas.move,
773
883
  async execute(input, ctx) {
774
884
  const p = (input ?? {});
775
885
  const qs = {
@@ -782,15 +892,9 @@ export default function googleCalendar(rl) {
782
892
  });
783
893
  // ─── FreeBusy ────────────────────────────────────────────────────
784
894
  rl.registerAction("freeBusy.query", {
895
+ access: "read",
785
896
  description: "Query free/busy windows across one or more calendars in a time range. Returns an array of busy intervals per calendar id, plus any errors.",
786
- inputSchema: {
787
- calendarIds: { type: "array", required: true, description: "Calendar ids to query (use 'primary' for the user's own)." },
788
- timeMin: { type: "string", required: true, description: "RFC 3339 lower bound." },
789
- timeMax: { type: "string", required: true, description: "RFC 3339 upper bound." },
790
- timeZone: { type: "string", required: false },
791
- groupExpansionMax: { type: "number", required: false },
792
- calendarExpansionMax: { type: "number", required: false },
793
- },
897
+ inputSchema: freeBusySchema,
794
898
  async execute(input, ctx) {
795
899
  const p = (input ?? {});
796
900
  const body = {
@@ -806,13 +910,9 @@ export default function googleCalendar(rl) {
806
910
  });
807
911
  // ─── Calendar list management ───────────────────────────────────
808
912
  rl.registerAction("calendarList.list", {
913
+ access: "read",
809
914
  description: "List calendars on the user's calendar list (the user's left sidebar).",
810
- inputSchema: {
811
- minAccessRole: { type: "string", required: false, description: "freeBusyReader | reader | writer | owner" },
812
- showDeleted: { type: "boolean", required: false },
813
- showHidden: { type: "boolean", required: false },
814
- returnAll: { type: "boolean", required: false, default: true },
815
- },
915
+ inputSchema: calendarListSchemas.list,
816
916
  async execute(input, ctx) {
817
917
  const p = (input ?? {});
818
918
  const qs = {
@@ -828,13 +928,9 @@ export default function googleCalendar(rl) {
828
928
  },
829
929
  });
830
930
  rl.registerAction("calendarList.insert", {
931
+ access: "write",
831
932
  description: "Add a calendar (by id) to the user's calendar list.",
832
- inputSchema: {
833
- calendarId: { type: "string", required: true },
834
- colorRgbFormat: { type: "boolean", required: false },
835
- defaultReminders: { type: "array", required: false },
836
- summaryOverride: { type: "string", required: false },
837
- },
933
+ inputSchema: calendarListSchemas.insert,
838
934
  async execute(input, ctx) {
839
935
  const p = (input ?? {});
840
936
  const body = { id: p.calendarId };
@@ -846,17 +942,9 @@ export default function googleCalendar(rl) {
846
942
  },
847
943
  });
848
944
  rl.registerAction("calendarList.patch", {
945
+ access: "write",
849
946
  description: "Patch a calendar entry on the user's calendar list (colors, summary override, reminders, selected).",
850
- inputSchema: {
851
- calendarId: { type: "string", required: true },
852
- colorId: { type: "string", required: false },
853
- backgroundColor: { type: "string", required: false },
854
- foregroundColor: { type: "string", required: false },
855
- summaryOverride: { type: "string", required: false },
856
- selected: { type: "boolean", required: false },
857
- hidden: { type: "boolean", required: false },
858
- defaultReminders: { type: "array", required: false },
859
- },
947
+ inputSchema: calendarListSchemas.patch,
860
948
  async execute(input, ctx) {
861
949
  const p = (input ?? {});
862
950
  const body = {};
@@ -868,8 +956,9 @@ export default function googleCalendar(rl) {
868
956
  },
869
957
  });
870
958
  rl.registerAction("calendarList.delete", {
959
+ access: "write",
871
960
  description: "Remove a calendar from the user's calendar list (does not delete the underlying calendar).",
872
- inputSchema: { calendarId: { type: "string", required: true } },
961
+ inputSchema: calendarListSchemas.delete,
873
962
  async execute(input, ctx) {
874
963
  const p = (input ?? {});
875
964
  await calRequest(ctx, "DELETE", `/users/me/calendarList/${encodeCalendarId(p.calendarId)}`);
@@ -878,11 +967,9 @@ export default function googleCalendar(rl) {
878
967
  });
879
968
  // ─── ACL (calendar sharing) ──────────────────────────────────────
880
969
  rl.registerAction("acl.list", {
970
+ access: "read",
881
971
  description: "List ACL rules on a calendar.",
882
- inputSchema: {
883
- calendarId: { type: "string", required: true },
884
- returnAll: { type: "boolean", required: false, default: true },
885
- },
972
+ inputSchema: aclSchemas.list,
886
973
  async execute(input, ctx) {
887
974
  const p = (input ?? {});
888
975
  const path = `/calendars/${encodeCalendarId(p.calendarId)}/acl`;
@@ -893,14 +980,9 @@ export default function googleCalendar(rl) {
893
980
  },
894
981
  });
895
982
  rl.registerAction("acl.insert", {
983
+ access: "write",
896
984
  description: "Add an ACL rule to a calendar. Roles: 'none' | 'freeBusyReader' | 'reader' | 'writer' | 'owner'.",
897
- inputSchema: {
898
- calendarId: { type: "string", required: true },
899
- role: { type: "string", required: true },
900
- scopeType: { type: "string", required: true, description: "'default' | 'user' | 'group' | 'domain'" },
901
- scopeValue: { type: "string", required: false, description: "Email / domain depending on scopeType." },
902
- sendNotifications: { type: "boolean", required: false },
903
- },
985
+ inputSchema: aclSchemas.insert,
904
986
  async execute(input, ctx) {
905
987
  const p = (input ?? {});
906
988
  const body = { role: p.role, scope: { type: p.scopeType, value: p.scopeValue } };
@@ -908,23 +990,18 @@ export default function googleCalendar(rl) {
908
990
  },
909
991
  });
910
992
  rl.registerAction("acl.update", {
993
+ access: "write",
911
994
  description: "Patch an ACL rule's role.",
912
- inputSchema: {
913
- calendarId: { type: "string", required: true },
914
- ruleId: { type: "string", required: true },
915
- role: { type: "string", required: true },
916
- },
995
+ inputSchema: aclSchemas.update,
917
996
  async execute(input, ctx) {
918
997
  const p = (input ?? {});
919
998
  return calRequest(ctx, "PATCH", `/calendars/${encodeCalendarId(p.calendarId)}/acl/${encodeURIComponent(p.ruleId)}`, { role: p.role });
920
999
  },
921
1000
  });
922
1001
  rl.registerAction("acl.delete", {
1002
+ access: "write",
923
1003
  description: "Remove an ACL rule.",
924
- inputSchema: {
925
- calendarId: { type: "string", required: true },
926
- ruleId: { type: "string", required: true },
927
- },
1004
+ inputSchema: aclSchemas.delete,
928
1005
  async execute(input, ctx) {
929
1006
  const p = (input ?? {});
930
1007
  await calRequest(ctx, "DELETE", `/calendars/${encodeCalendarId(p.calendarId)}/acl/${encodeURIComponent(p.ruleId)}`);
@@ -933,15 +1010,17 @@ export default function googleCalendar(rl) {
933
1010
  });
934
1011
  // ─── Settings ────────────────────────────────────────────────────
935
1012
  rl.registerAction("settings.list", {
1013
+ access: "read",
936
1014
  description: "List the user's calendar settings (timezone, week start, working location, etc.).",
937
- inputSchema: { returnAll: { type: "boolean", required: false, default: true } },
1015
+ inputSchema: settingsSchemas.list,
938
1016
  async execute(_input, ctx) {
939
1017
  return paginateAll(ctx, "/users/me/settings", "items", { maxResults: 100 });
940
1018
  },
941
1019
  });
942
1020
  rl.registerAction("settings.get", {
1021
+ access: "read",
943
1022
  description: "Get a single setting by key (e.g. 'timezone', 'weekStart', 'locale').",
944
- inputSchema: { setting: { type: "string", required: true } },
1023
+ inputSchema: settingsSchemas.get,
945
1024
  async execute(input, ctx) {
946
1025
  const p = (input ?? {});
947
1026
  return calRequest(ctx, "GET", `/users/me/settings/${encodeURIComponent(p.setting)}`);