feat(tools): added calcom

This commit is contained in:
waleed
2026-01-28 18:56:57 -08:00
parent 06d7ce7667
commit 6632a5e26b
44 changed files with 4884 additions and 59 deletions

View File

@@ -5113,3 +5113,42 @@ export function PulseIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function CalComIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='101'
height='22'
viewBox='0 0 101 22'
fill='currentColor'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M10.0582 20.817C4.32115 20.817 0 16.2763 0 10.6704C0 5.04589 4.1005 0.467773 10.0582 0.467773C13.2209 0.467773 15.409 1.43945 17.1191 3.66311L14.3609 5.96151C13.2025 4.72822 11.805 4.11158 10.0582 4.11158C6.17833 4.11158 4.04533 7.08268 4.04533 10.6704C4.04533 14.2582 6.38059 17.1732 10.0582 17.1732C11.7866 17.1732 13.2577 16.5566 14.4161 15.3233L17.1375 17.7151C15.501 19.8453 13.2577 20.817 10.0582 20.817Z'
fill='#292929'
/>
<path
d='M29.0161 5.88601H32.7304V20.4612H29.0161V18.331C28.2438 19.8446 26.9566 20.8536 24.4927 20.8536C20.5577 20.8536 17.4133 17.4341 17.4133 13.2297C17.4133 9.02528 20.5577 5.60571 24.4927 5.60571C26.9383 5.60571 28.2438 6.61477 29.0161 8.12835V5.88601ZM29.1264 13.2297C29.1264 10.95 27.5634 9.06266 25.0995 9.06266C22.7274 9.06266 21.1828 10.9686 21.1828 13.2297C21.1828 15.4346 22.7274 17.3967 25.0995 17.3967C27.5451 17.3967 29.1264 15.4907 29.1264 13.2297Z'
fill='#292929'
/>
<path d='M35.3599 0H39.0742V20.4427H35.3599V0Z' fill='#292929' />
<path
d='M40.7291 18.5182C40.7291 17.3223 41.6853 16.3132 42.9908 16.3132C44.2964 16.3132 45.2158 17.3223 45.2158 18.5182C45.2158 19.7515 44.278 20.7605 42.9908 20.7605C41.7037 20.7605 40.7291 19.7515 40.7291 18.5182Z'
fill='#292929'
/>
<path
d='M59.4296 18.1068C58.0505 19.7885 55.9543 20.8536 53.4719 20.8536C49.0404 20.8536 45.7858 17.4341 45.7858 13.2297C45.7858 9.02528 49.0404 5.60571 53.4719 5.60571C55.8623 5.60571 57.9402 6.61477 59.3193 8.20309L56.4508 10.6136C55.7336 9.71667 54.7958 9.04397 53.4719 9.04397C51.0999 9.04397 49.5553 10.95 49.5553 13.211C49.5553 15.472 51.0999 17.378 53.4719 17.378C54.9062 17.378 55.8991 16.6306 56.6346 15.6215L59.4296 18.1068Z'
fill='#292929'
/>
<path
d='M59.7422 13.2297C59.7422 9.02528 62.9968 5.60571 67.4283 5.60571C71.8598 5.60571 75.1144 9.02528 75.1144 13.2297C75.1144 17.4341 71.8598 20.8536 67.4283 20.8536C62.9968 20.8349 59.7422 17.4341 59.7422 13.2297ZM71.3449 13.2297C71.3449 10.95 69.8003 9.06266 67.4283 9.06266C65.0563 9.04397 63.5117 10.95 63.5117 13.2297C63.5117 15.4907 65.0563 17.3967 67.4283 17.3967C69.8003 17.3967 71.3449 15.4907 71.3449 13.2297Z'
fill='#292929'
/>
<path
d='M100.232 11.5482V20.4428H96.518V12.4638C96.518 9.94119 95.3412 8.85739 93.576 8.85739C91.921 8.85739 90.7442 9.67958 90.7442 12.4638V20.4428H87.0299V12.4638C87.0299 9.94119 85.8346 8.85739 84.0878 8.85739C82.4329 8.85739 80.9802 9.67958 80.9802 12.4638V20.4428H77.2659V5.8676H80.9802V7.88571C81.7525 6.31607 83.15 5.53125 85.3014 5.53125C87.3425 5.53125 89.0525 6.5403 89.9903 8.24074C90.9281 6.50293 92.3072 5.53125 94.8079 5.53125C97.8603 5.54994 100.232 7.86702 100.232 11.5482Z'
fill='#292929'
/>
</svg>
)
}

View File

@@ -13,6 +13,7 @@ import {
AsanaIcon,
BrainIcon,
BrowserUseIcon,
CalComIcon,
CalendlyIcon,
CirclebackIcon,
ClayIcon,
@@ -141,6 +142,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
arxiv: ArxivIcon,
asana: AsanaIcon,
browser_use: BrowserUseIcon,
calcom: CalComIcon,
calendly: CalendlyIcon,
circleback: CirclebackIcon,
clay: ClayIcon,

View File

@@ -0,0 +1,515 @@
---
title: CalCom
description: Manage Cal.com bookings, event types, schedules, and availability
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="calcom"
color="#FFFFFE"
/>
{/* MANUAL-CONTENT-START:intro */}
[Cal.com](https://cal.com/) is a flexible and open-source scheduling platform that makes it easy to manage appointments, bookings, event types, and team availabilities.
With Cal.com, you can:
- **Automate scheduling**: Allow users to view your available time slots and book meetings automatically, without back-and-forth emails.
- **Manage events**: Create and customize event types, durations, and rules for one-on-one or group meetings.
- **Integrate calendars**: Seamlessly connect with Google, Outlook, Apple, or other calendar providers to avoid double bookings.
- **Handle attendees and guests**: Collect attendee information, manage guests, and send invitations or reminders.
- **Control availability**: Define custom working hours, buffer times, and cancellation/rebooking rules.
- **Power workflows**: Trigger custom actions via webhooks when a booking is created, cancelled, or rescheduled.
In Sim, the Cal.com integration enables your agents to book meetings, check availabilities, manage event types, and automate scheduling tasks programmatically. This helps agents coordinate meetings, send bookings on behalf of users, check schedules, or respond to booking events—all without manual intervention. By connecting Sim with Cal.com, you unlock highly automated and intelligent scheduling workflows that can integrate seamlessly with your broader automation needs.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Cal.com into your workflow. Create and manage bookings, event types, schedules, and check availability slots. Supports creating, listing, rescheduling, and canceling bookings, as well as managing event types and schedules. Can also trigger workflows based on Cal.com webhook events (booking created, cancelled, rescheduled). Connect your Cal.com account via OAuth.
## Tools
### `calcom_create_booking`
Create a new booking on Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `eventTypeId` | number | Yes | The ID of the event type to book |
| `start` | string | Yes | Start time in UTC ISO 8601 format \(e.g., 2024-01-15T09:00:00Z\) |
| `attendee` | object | Yes | Attendee information object with name, email, timeZone, and optional phoneNumber |
| `guests` | array | No | Array of guest email addresses |
| `items` | string | No | Guest email address |
| `lengthInMinutes` | number | No | Duration of the booking in minutes \(overrides event type default\) |
| `metadata` | object | No | Custom metadata to attach to the booking |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Created booking details |
### `calcom_get_booking`
Get details of a specific booking by its UID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `bookingUid` | string | Yes | Unique identifier \(UID\) of the booking |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Booking details |
### `calcom_list_bookings`
List all bookings with optional status filter
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `status` | string | No | Filter bookings by status: upcoming, recurring, past, cancelled, or unconfirmed |
| `take` | number | No | Number of bookings to return \(pagination limit\) |
| `skip` | number | No | Number of bookings to skip \(pagination offset\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | array | Array of bookings |
### `calcom_cancel_booking`
Cancel an existing booking
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `bookingUid` | string | Yes | Unique identifier \(UID\) of the booking to cancel |
| `cancellationReason` | string | No | Reason for cancelling the booking |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Cancelled booking details |
| ↳ `status` | string | Booking status \(should be cancelled\) |
### `calcom_reschedule_booking`
Reschedule an existing booking to a new time
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `bookingUid` | string | Yes | Unique identifier \(UID\) of the booking to reschedule |
| `start` | string | Yes | New start time in UTC ISO 8601 format \(e.g., 2024-01-15T09:00:00Z\) |
| `reschedulingReason` | string | No | Reason for rescheduling the booking |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Rescheduled booking details |
| ↳ `uid` | string | Unique identifier for the new booking |
| ↳ `start` | string | New start time in ISO 8601 format |
| ↳ `end` | string | New end time in ISO 8601 format |
### `calcom_confirm_booking`
Confirm a pending booking that requires confirmation
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `bookingUid` | string | Yes | Unique identifier \(UID\) of the booking to confirm |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Confirmed booking details |
| ↳ `status` | string | Booking status \(should be accepted/confirmed\) |
### `calcom_decline_booking`
Decline a pending booking request
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `bookingUid` | string | Yes | Unique identifier \(UID\) of the booking to decline |
| `reason` | string | No | Reason for declining the booking |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Declined booking details |
| ↳ `status` | string | Booking status \(should be cancelled/rejected\) |
### `calcom_create_event_type`
Create a new event type in Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `title` | string | Yes | Title of the event type |
| `slug` | string | Yes | Unique slug for the event type URL |
| `lengthInMinutes` | number | Yes | Duration of the event in minutes |
| `description` | string | No | Description of the event type |
| `slotInterval` | number | No | Interval between available booking slots in minutes |
| `minimumBookingNotice` | number | No | Minimum notice required before booking in minutes |
| `beforeEventBuffer` | number | No | Buffer time before the event in minutes |
| `afterEventBuffer` | number | No | Buffer time after the event in minutes |
| `scheduleId` | number | No | ID of the schedule to use for availability |
| `disableGuests` | boolean | No | Whether to disable guests from being added to bookings |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Created event type details |
| ↳ `id` | number | Event type ID |
| ↳ `title` | string | Event type title |
| ↳ `slug` | string | Event type slug |
| ↳ `description` | string | Event type description |
| ↳ `lengthInMinutes` | number | Duration in minutes |
| ↳ `slotInterval` | number | Slot interval in minutes |
| ↳ `minimumBookingNotice` | number | Minimum booking notice in minutes |
| ↳ `beforeEventBuffer` | number | Buffer before event in minutes |
| ↳ `afterEventBuffer` | number | Buffer after event in minutes |
| ↳ `scheduleId` | number | Schedule ID |
| ↳ `disableGuests` | boolean | Whether guests are disabled |
| ↳ `createdAt` | string | ISO timestamp of creation |
| ↳ `updatedAt` | string | ISO timestamp of last update |
### `calcom_get_event_type`
Get detailed information about a specific event type
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `eventTypeId` | number | Yes | Event type ID to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Event type details |
| ↳ `id` | number | Event type ID |
| ↳ `title` | string | Event type title |
| ↳ `slug` | string | Event type slug |
| ↳ `description` | string | Event type description |
| ↳ `lengthInMinutes` | number | Duration in minutes |
| ↳ `slotInterval` | number | Slot interval in minutes |
| ↳ `minimumBookingNotice` | number | Minimum booking notice in minutes |
| ↳ `beforeEventBuffer` | number | Buffer before event in minutes |
| ↳ `afterEventBuffer` | number | Buffer after event in minutes |
| ↳ `scheduleId` | number | Schedule ID |
| ↳ `disableGuests` | boolean | Whether guests are disabled |
| ↳ `createdAt` | string | ISO timestamp of creation |
| ↳ `updatedAt` | string | ISO timestamp of last update |
### `calcom_list_event_types`
Retrieve a list of all event types
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `sortCreatedAt` | string | No | Sort by creation date: "asc" or "desc" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | array | Array of event types |
| ↳ `id` | number | Event type ID |
| ↳ `title` | string | Event type title |
| ↳ `slug` | string | Event type slug |
| ↳ `description` | string | Event type description |
| ↳ `lengthInMinutes` | number | Duration in minutes |
| ↳ `slotInterval` | number | Slot interval in minutes |
| ↳ `minimumBookingNotice` | number | Minimum booking notice in minutes |
| ↳ `beforeEventBuffer` | number | Buffer before event in minutes |
| ↳ `afterEventBuffer` | number | Buffer after event in minutes |
| ↳ `scheduleId` | number | Schedule ID |
| ↳ `disableGuests` | boolean | Whether guests are disabled |
| ↳ `createdAt` | string | ISO timestamp of creation |
| ↳ `updatedAt` | string | ISO timestamp of last update |
### `calcom_update_event_type`
Update an existing event type in Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `eventTypeId` | number | Yes | Event type ID to update |
| `title` | string | No | Title of the event type |
| `slug` | string | No | Unique slug for the event type URL |
| `lengthInMinutes` | number | No | Duration of the event in minutes |
| `description` | string | No | Description of the event type |
| `slotInterval` | number | No | Interval between available booking slots in minutes |
| `minimumBookingNotice` | number | No | Minimum notice required before booking in minutes |
| `beforeEventBuffer` | number | No | Buffer time before the event in minutes |
| `afterEventBuffer` | number | No | Buffer time after the event in minutes |
| `scheduleId` | number | No | ID of the schedule to use for availability |
| `disableGuests` | boolean | No | Whether to disable guests from being added to bookings |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Updated event type details |
| ↳ `id` | number | Event type ID |
| ↳ `title` | string | Event type title |
| ↳ `slug` | string | Event type slug |
| ↳ `description` | string | Event type description |
| ↳ `lengthInMinutes` | number | Duration in minutes |
| ↳ `slotInterval` | number | Slot interval in minutes |
| ↳ `minimumBookingNotice` | number | Minimum booking notice in minutes |
| ↳ `beforeEventBuffer` | number | Buffer before event in minutes |
| ↳ `afterEventBuffer` | number | Buffer after event in minutes |
| ↳ `scheduleId` | number | Schedule ID |
| ↳ `disableGuests` | boolean | Whether guests are disabled |
| ↳ `createdAt` | string | ISO timestamp of creation |
| ↳ `updatedAt` | string | ISO timestamp of last update |
### `calcom_delete_event_type`
Delete an event type from Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `eventTypeId` | number | Yes | Event type ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the event type was successfully deleted |
| `message` | string | Status message |
### `calcom_create_schedule`
Create a new availability schedule in Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Name of the schedule |
| `timeZone` | string | Yes | Timezone for the schedule \(e.g., America/New_York\) |
| `isDefault` | boolean | Yes | Whether this schedule should be the default |
| `availability` | array | No | Availability intervals for the schedule |
| `items` | object | No | Availability interval |
| `properties` | array | No | Days of the week \(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday\) |
| `days` | array | No | Days of the week \(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday\) |
| `startTime` | string | No | Start time in HH:MM format |
| `endTime` | string | No | End time in HH:MM format |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Created schedule data |
| ↳ `id` | number | Unique identifier for the schedule |
| ↳ `name` | string | Name of the schedule |
| ↳ `timeZone` | string | Timezone of the schedule |
| ↳ `isDefault` | boolean | Whether this is the default schedule |
| ↳ `availability` | array | Availability intervals |
| ↳ `days` | array | Days of the week |
| ↳ `startTime` | string | Start time in HH:MM format |
| ↳ `endTime` | string | End time in HH:MM format |
### `calcom_get_schedule`
Get a specific schedule by ID from Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `scheduleId` | string | Yes | ID of the schedule to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Schedule data |
| ↳ `id` | number | Unique identifier for the schedule |
| ↳ `name` | string | Name of the schedule |
| ↳ `timeZone` | string | Timezone of the schedule |
| ↳ `isDefault` | boolean | Whether this is the default schedule |
| ↳ `availability` | array | Availability intervals |
| ↳ `days` | array | Days of the week |
| ↳ `startTime` | string | Start time in HH:MM format |
| ↳ `endTime` | string | End time in HH:MM format |
### `calcom_list_schedules`
List all availability schedules from Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | array | Array of schedule objects |
| ↳ `id` | number | Unique identifier for the schedule |
| ↳ `name` | string | Name of the schedule |
| ↳ `timeZone` | string | Timezone of the schedule |
| ↳ `isDefault` | boolean | Whether this is the default schedule |
| ↳ `availability` | array | Availability intervals |
| ↳ `days` | array | Days of the week |
| ↳ `startTime` | string | Start time in HH:MM format |
| ↳ `endTime` | string | End time in HH:MM format |
### `calcom_update_schedule`
Update an existing schedule in Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `scheduleId` | string | Yes | ID of the schedule to update |
| `name` | string | No | New name for the schedule |
| `timeZone` | string | No | New timezone for the schedule \(e.g., America/New_York\) |
| `isDefault` | boolean | No | Whether this schedule should be the default |
| `availability` | array | No | New availability intervals for the schedule |
| `items` | object | No | Availability interval |
| `properties` | array | No | Days of the week \(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday\) |
| `days` | array | No | Days of the week \(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday\) |
| `startTime` | string | No | Start time in HH:MM format |
| `endTime` | string | No | End time in HH:MM format |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Updated schedule data |
| ↳ `id` | number | Unique identifier for the schedule |
| ↳ `name` | string | Name of the schedule |
| ↳ `timeZone` | string | Timezone of the schedule |
| ↳ `isDefault` | boolean | Whether this is the default schedule |
| ↳ `availability` | array | Availability intervals |
| ↳ `days` | array | Days of the week |
| ↳ `startTime` | string | Start time in HH:MM format |
| ↳ `endTime` | string | End time in HH:MM format |
### `calcom_delete_schedule`
Delete a schedule from Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `scheduleId` | string | Yes | ID of the schedule to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Deleted schedule data |
| ↳ `id` | number | Unique identifier of the deleted schedule |
| ↳ `name` | string | Name of the deleted schedule |
| ↳ `timeZone` | string | Timezone of the deleted schedule |
| ↳ `isDefault` | boolean | Whether this was the default schedule |
### `calcom_get_default_schedule`
Get the default availability schedule from Cal.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Default schedule data |
| ↳ `id` | number | Unique identifier for the schedule |
| ↳ `name` | string | Name of the schedule |
| ↳ `timeZone` | string | Timezone of the schedule |
| ↳ `isDefault` | boolean | Whether this is the default schedule \(always true\) |
| ↳ `availability` | array | Availability intervals |
| ↳ `days` | array | Days of the week |
| ↳ `startTime` | string | Start time in HH:MM format |
| ↳ `endTime` | string | End time in HH:MM format |
### `calcom_get_slots`
Get available booking slots for a Cal.com event type within a time range
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `start` | string | Yes | Start of time range in UTC ISO 8601 format \(e.g., 2024-01-15T00:00:00Z\) |
| `end` | string | Yes | End of time range in UTC ISO 8601 format \(e.g., 2024-01-22T00:00:00Z\) |
| `eventTypeId` | number | No | Event type ID for direct lookup |
| `eventTypeSlug` | string | No | Event type slug \(requires username to be set\) |
| `username` | string | No | Username for personal event types \(required when using eventTypeSlug\) |
| `timeZone` | string | No | Timezone for returned slots \(defaults to UTC\) |
| `duration` | number | No | Slot length in minutes |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Response status |
| `data` | object | Slots data container |
| ↳ `slots` | object | Available time slots grouped by date \(YYYY-MM-DD keys\) |

View File

@@ -9,6 +9,7 @@
"arxiv",
"asana",
"browser_use",
"calcom",
"calendly",
"circleback",
"clay",

View File

@@ -1,5 +1,5 @@
import { memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { Check, Copy, Wand2 } from 'lucide-react'
import { Wand2 } from 'lucide-react'
import { useReactFlow } from 'reactflow'
import { Input } from '@/components/emcn'
import { Button } from '@/components/ui/button'
@@ -40,8 +40,6 @@ interface ShortInputProps {
disabled?: boolean
/** Whether the input is read-only */
readOnly?: boolean
/** Whether to show a copy button */
showCopyButton?: boolean
/** Whether to use webhook URL as value */
useWebhookUrl?: boolean
/** Ref to expose wand control handlers to parent */
@@ -59,7 +57,6 @@ interface ShortInputProps {
* - Handles drag-and-drop for connections and variable references
* - Provides environment variable and tag autocomplete
* - Password masking with reveal on focus
* - Copy to clipboard functionality
* - Integrates with ReactFlow for zoom control
*/
export const ShortInput = memo(function ShortInput({
@@ -74,14 +71,12 @@ export const ShortInput = memo(function ShortInput({
previewValue,
disabled = false,
readOnly = false,
showCopyButton = false,
useWebhookUrl = false,
wandControlRef,
hideInternalWand = false,
}: ShortInputProps) {
const [localContent, setLocalContent] = useState<string>('')
const [isFocused, setIsFocused] = useState(false)
const [copied, setCopied] = useState(false)
const persistSubBlockValueRef = useRef<(value: string) => void>(() => {})
const justPastedRef = useRef(false)
@@ -278,18 +273,6 @@ export const ShortInput = memo(function ShortInput({
[reactFlowInstance]
)
/**
* Handles copying the value to the clipboard.
*/
const handleCopy = useCallback(() => {
const textToCopy = useWebhookUrl ? webhookManagement?.webhookUrl : value?.toString()
if (textToCopy) {
navigator.clipboard.writeText(textToCopy)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}, [useWebhookUrl, webhookManagement?.webhookUrl, value])
const handleBlur = useCallback(() => {
setIsFocused(false)
}, [])
@@ -366,10 +349,7 @@ export const ShortInput = memo(function ShortInput({
<>
<Input
ref={ref as React.RefObject<HTMLInputElement>}
className={cn(
'allow-scroll w-full overflow-auto text-transparent caret-foreground [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-muted-foreground/50 [&::-webkit-scrollbar]:hidden',
showCopyButton && 'pr-14'
)}
className='allow-scroll w-full overflow-auto text-transparent caret-foreground [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-muted-foreground/50 [&::-webkit-scrollbar]:hidden'
readOnly={readOnly}
placeholder={placeholder ?? ''}
type='text'
@@ -392,8 +372,7 @@ export const ShortInput = memo(function ShortInput({
<div
ref={overlayRef}
className={cn(
'pointer-events-none absolute inset-0 flex items-center overflow-x-auto bg-transparent px-[8px] py-[6px] font-medium font-sans text-foreground text-sm [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
showCopyButton ? 'pr-14' : 'pr-3',
'pointer-events-none absolute inset-0 flex items-center overflow-x-auto bg-transparent px-[8px] py-[6px] pr-3 font-medium font-sans text-foreground text-sm [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
(isPreview || disabled) && 'opacity-50'
)}
>
@@ -404,27 +383,6 @@ export const ShortInput = memo(function ShortInput({
}}
</SubBlockInputController>
{/* Copy Button */}
{showCopyButton && value && (
<div className='pointer-events-none absolute top-0 right-0 bottom-0 z-10 flex w-14 items-center justify-end pr-2 opacity-0 transition-opacity group-hover:opacity-100'>
<Button
type='button'
variant='ghost'
size='icon'
onClick={handleCopy}
disabled={!value}
className='pointer-events-auto h-6 w-6 p-0'
aria-label='Copy value'
>
{copied ? (
<Check className='h-3.5 w-3.5 text-green-500' />
) : (
<Copy className='h-3.5 w-3.5 text-muted-foreground' />
)}
</Button>
</div>
)}
{/* Wand Button - only show if not hidden by parent */}
{isWandEnabled && !isPreview && !wandHook.isStreaming && !hideInternalWand && (
<div className='-translate-y-1/2 absolute top-1/2 right-3 z-10 flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100'>

View File

@@ -1,6 +1,6 @@
import { type JSX, type MouseEvent, memo, useRef, useState } from 'react'
import { type JSX, type MouseEvent, memo, useCallback, useRef, useState } from 'react'
import { isEqual } from 'lodash'
import { AlertTriangle, ArrowLeftRight, ArrowUp } from 'lucide-react'
import { AlertTriangle, ArrowLeftRight, ArrowUp, Check, Clipboard } from 'lucide-react'
import { Button, Input, Label, Tooltip } from '@/components/emcn/components'
import { cn } from '@/lib/core/utils/cn'
import type { FieldDiffStatus } from '@/lib/workflows/diff/types'
@@ -44,6 +44,7 @@ import {
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import type { SubBlockConfig } from '@/blocks/types'
import { useWebhookManagement } from '@/hooks/use-webhook-management'
/**
* Interface for wand control handlers exposed by sub-block inputs
@@ -195,7 +196,12 @@ const renderLabel = (
disabled?: boolean
onToggle?: () => void
},
canonicalToggleIsDisabled?: boolean
canonicalToggleIsDisabled?: boolean,
copyState?: {
showCopyButton: boolean
copied: boolean
onCopy: () => void
}
): JSX.Element | null => {
if (config.type === 'switch') return null
if (!config.title) return null
@@ -203,6 +209,7 @@ const renderLabel = (
const required = isFieldRequired(config, subBlockValues)
const showWand = wandState?.isWandEnabled && !wandState.isPreview && !wandState.disabled
const showCanonicalToggle = !!canonicalToggle && !wandState?.isPreview
const showCopy = copyState?.showCopyButton && !wandState?.isPreview
const canonicalToggleDisabledResolved = canonicalToggleIsDisabled ?? canonicalToggle?.disabled
return (
@@ -227,6 +234,27 @@ const renderLabel = (
)}
</Label>
<div className='flex items-center gap-[6px]'>
{showCopy && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
type='button'
onClick={copyState.onCopy}
className='-my-1 flex h-5 w-5 items-center justify-center'
aria-label='Copy value'
>
{copyState.copied ? (
<Check className='h-3 w-3 text-green-500' />
) : (
<Clipboard className='h-3 w-3 text-muted-foreground' />
)}
</button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
<p>{copyState.copied ? 'Copied!' : 'Copy'}</p>
</Tooltip.Content>
</Tooltip.Root>
)}
{showWand && (
<>
{!wandState.isSearchActive ? (
@@ -385,9 +413,18 @@ function SubBlockComponent({
const [isValidJson, setIsValidJson] = useState(true)
const [isSearchActive, setIsSearchActive] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const [copied, setCopied] = useState(false)
const searchInputRef = useRef<HTMLInputElement>(null)
const wandControlRef = useRef<WandControlHandlers | null>(null)
// Use webhook management hook when config has useWebhookUrl enabled
const webhookManagement = useWebhookManagement({
blockId,
triggerId: undefined,
isPreview,
useWebhookUrl: config.useWebhookUrl,
})
const handleMouseDown = (e: MouseEvent<HTMLDivElement>): void => {
e.stopPropagation()
}
@@ -398,6 +435,18 @@ function SubBlockComponent({
const isWandEnabled = config.wandConfig?.enabled ?? false
/**
* Handles copying the webhook URL to clipboard.
*/
const handleCopy = useCallback(() => {
const textToCopy = webhookManagement?.webhookUrl
if (textToCopy) {
navigator.clipboard.writeText(textToCopy)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}, [webhookManagement?.webhookUrl])
/**
* Handles wand icon click to activate inline prompt mode.
* Focuses the input after a brief delay to ensure DOM is ready.
@@ -482,7 +531,6 @@ function SubBlockComponent({
placeholder={config.placeholder}
password={config.password}
readOnly={config.readOnly}
showCopyButton={config.showCopyButton}
useWebhookUrl={config.useWebhookUrl}
config={config}
isPreview={isPreview}
@@ -979,7 +1027,12 @@ function SubBlockComponent({
searchInputRef,
},
canonicalToggle,
Boolean(canonicalToggle?.disabled || disabled || isPreview)
Boolean(canonicalToggle?.disabled || disabled || isPreview),
{
showCopyButton: Boolean(config.showCopyButton && config.useWebhookUrl),
copied,
onCopy: handleCopy,
}
)}
{renderInput()}
</div>

View File

@@ -0,0 +1,661 @@
import { CalComIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { ToolResponse } from '@/tools/types'
import { getTrigger } from '@/triggers'
export const CalComBlock: BlockConfig<ToolResponse> = {
type: 'calcom',
name: 'CalCom',
description: 'Manage Cal.com bookings, event types, schedules, and availability',
authMode: AuthMode.OAuth,
triggerAllowed: true,
longDescription:
'Integrate Cal.com into your workflow. Create and manage bookings, event types, schedules, and check availability slots. Supports creating, listing, rescheduling, and canceling bookings, as well as managing event types and schedules. Can also trigger workflows based on Cal.com webhook events (booking created, cancelled, rescheduled). Connect your Cal.com account via OAuth.',
docsLink: 'https://docs.sim.ai/tools/calcom',
category: 'tools',
bgColor: '#FFFFFE',
icon: CalComIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Create Booking', id: 'calcom_create_booking' },
{ label: 'Get Booking', id: 'calcom_get_booking' },
{ label: 'List Bookings', id: 'calcom_list_bookings' },
{ label: 'Cancel Booking', id: 'calcom_cancel_booking' },
{ label: 'Reschedule Booking', id: 'calcom_reschedule_booking' },
{ label: 'Confirm Booking', id: 'calcom_confirm_booking' },
{ label: 'Decline Booking', id: 'calcom_decline_booking' },
{ label: 'Create Event Type', id: 'calcom_create_event_type' },
{ label: 'Get Event Type', id: 'calcom_get_event_type' },
{ label: 'List Event Types', id: 'calcom_list_event_types' },
{ label: 'Update Event Type', id: 'calcom_update_event_type' },
{ label: 'Delete Event Type', id: 'calcom_delete_event_type' },
{ label: 'Create Schedule', id: 'calcom_create_schedule' },
{ label: 'Get Schedule', id: 'calcom_get_schedule' },
{ label: 'List Schedules', id: 'calcom_list_schedules' },
{ label: 'Update Schedule', id: 'calcom_update_schedule' },
{ label: 'Delete Schedule', id: 'calcom_delete_schedule' },
{ label: 'Get Default Schedule', id: 'calcom_get_default_schedule' },
{ label: 'Get Available Slots', id: 'calcom_get_slots' },
],
value: () => 'calcom_list_bookings',
},
{
id: 'credential',
title: 'Cal.com Account',
type: 'oauth-input',
serviceId: 'calcom',
placeholder: 'Select Cal.com account',
required: true,
},
// === Create Booking fields ===
{
id: 'eventTypeId',
title: 'Event Type ID',
type: 'short-input',
placeholder: 'Enter event type ID (number)',
condition: {
field: 'operation',
value: ['calcom_create_booking', 'calcom_get_slots'],
},
required: { field: 'operation', value: 'calcom_create_booking' },
},
{
id: 'start',
title: 'Start Time',
type: 'short-input',
placeholder: 'ISO 8601 format (e.g., 2024-01-15T10:00:00Z)',
condition: {
field: 'operation',
value: ['calcom_create_booking', 'calcom_reschedule_booking', 'calcom_get_slots'],
},
required: {
field: 'operation',
value: ['calcom_create_booking', 'calcom_reschedule_booking', 'calcom_get_slots'],
},
wandConfig: {
enabled: true,
prompt: `Generate an ISO 8601 timestamp in UTC based on the user's description.
Format: YYYY-MM-DDTHH:MM:SSZ
Examples:
- "tomorrow at 2pm" -> Tomorrow's date at 14:00:00Z
- "next Monday 9am" -> Next Monday at 09:00:00Z
- "in 3 hours" -> Current time + 3 hours
Return ONLY the timestamp string - no explanations or quotes.`,
placeholder: 'Describe the start time (e.g., "tomorrow at 2pm")...',
generationType: 'timestamp',
},
},
{
id: 'end',
title: 'End Time',
type: 'short-input',
placeholder: 'ISO 8601 format (e.g., 2024-01-15T11:00:00Z)',
condition: { field: 'operation', value: 'calcom_get_slots' },
required: { field: 'operation', value: 'calcom_get_slots' },
wandConfig: {
enabled: true,
prompt: `Generate an ISO 8601 timestamp in UTC based on the user's description.
Format: YYYY-MM-DDTHH:MM:SSZ
Examples:
- "end of tomorrow" -> Tomorrow at 23:59:59Z
- "next Friday" -> Next Friday at 23:59:59Z
- "in 1 week" -> Current date + 7 days
Return ONLY the timestamp string - no explanations or quotes.`,
placeholder: 'Describe the end time (e.g., "end of next week")...',
generationType: 'timestamp',
},
},
{
id: 'attendeeName',
title: 'Attendee Name',
type: 'short-input',
placeholder: 'Enter attendee name',
condition: { field: 'operation', value: 'calcom_create_booking' },
required: { field: 'operation', value: 'calcom_create_booking' },
},
{
id: 'attendeeEmail',
title: 'Attendee Email',
type: 'short-input',
placeholder: 'Enter attendee email',
condition: { field: 'operation', value: 'calcom_create_booking' },
},
{
id: 'attendeeTimeZone',
title: 'Attendee Time Zone',
type: 'short-input',
placeholder: 'e.g., America/New_York, Europe/London',
condition: { field: 'operation', value: 'calcom_create_booking' },
required: { field: 'operation', value: 'calcom_create_booking' },
},
{
id: 'attendeePhone',
title: 'Attendee Phone',
type: 'short-input',
placeholder: 'International format (e.g., +1234567890)',
condition: { field: 'operation', value: 'calcom_create_booking' },
},
{
id: 'guests',
title: 'Guests',
type: 'short-input',
placeholder: 'Comma-separated email addresses',
condition: { field: 'operation', value: 'calcom_create_booking' },
},
{
id: 'lengthInMinutes',
title: 'Duration (minutes)',
type: 'short-input',
placeholder: 'Override event duration (optional)',
condition: { field: 'operation', value: 'calcom_create_booking' },
},
{
id: 'metadata',
title: 'Metadata',
type: 'code',
language: 'json',
placeholder: '{"key": "value"}',
condition: { field: 'operation', value: 'calcom_create_booking' },
},
// === Get/Cancel/Reschedule/Confirm/Decline Booking fields ===
{
id: 'bookingUid',
title: 'Booking UID',
type: 'short-input',
placeholder: 'Enter booking UID',
condition: {
field: 'operation',
value: [
'calcom_get_booking',
'calcom_cancel_booking',
'calcom_reschedule_booking',
'calcom_confirm_booking',
'calcom_decline_booking',
],
},
required: {
field: 'operation',
value: [
'calcom_get_booking',
'calcom_cancel_booking',
'calcom_reschedule_booking',
'calcom_confirm_booking',
'calcom_decline_booking',
],
},
},
{
id: 'cancellationReason',
title: 'Cancellation Reason',
type: 'long-input',
placeholder: 'Reason for cancellation (optional)',
rows: 3,
condition: { field: 'operation', value: 'calcom_cancel_booking' },
},
{
id: 'reschedulingReason',
title: 'Rescheduling Reason',
type: 'long-input',
placeholder: 'Reason for rescheduling (optional)',
rows: 3,
condition: { field: 'operation', value: 'calcom_reschedule_booking' },
},
// === List Bookings filters ===
{
id: 'bookingStatus',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Upcoming', id: 'upcoming' },
{ label: 'Recurring', id: 'recurring' },
{ label: 'Past', id: 'past' },
{ label: 'Cancelled', id: 'cancelled' },
{ label: 'Unconfirmed', id: 'unconfirmed' },
],
condition: { field: 'operation', value: 'calcom_list_bookings' },
},
// === Event Type fields ===
{
id: 'eventTypeIdParam',
title: 'Event Type ID',
type: 'short-input',
placeholder: 'Enter event type ID',
condition: {
field: 'operation',
value: ['calcom_get_event_type', 'calcom_update_event_type', 'calcom_delete_event_type'],
},
required: {
field: 'operation',
value: ['calcom_get_event_type', 'calcom_update_event_type', 'calcom_delete_event_type'],
},
},
{
id: 'title',
title: 'Title',
type: 'short-input',
placeholder: 'Event type title',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
required: { field: 'operation', value: 'calcom_create_event_type' },
},
{
id: 'slug',
title: 'Slug',
type: 'short-input',
placeholder: 'URL-friendly identifier (e.g., 30-min-meeting)',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
required: { field: 'operation', value: 'calcom_create_event_type' },
},
{
id: 'eventLength',
title: 'Duration (minutes)',
type: 'short-input',
placeholder: 'Event duration in minutes (e.g., 30)',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
required: { field: 'operation', value: 'calcom_create_event_type' },
},
{
id: 'description',
title: 'Description',
type: 'long-input',
placeholder: 'Event type description',
rows: 3,
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
},
{
id: 'slotInterval',
title: 'Slot Interval (minutes)',
type: 'short-input',
placeholder: 'Minutes between available slots',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
},
{
id: 'minimumBookingNotice',
title: 'Minimum Notice (minutes)',
type: 'short-input',
placeholder: 'Minimum advance notice required',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
},
{
id: 'beforeEventBuffer',
title: 'Buffer Before (minutes)',
type: 'short-input',
placeholder: 'Buffer time before event',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
},
{
id: 'afterEventBuffer',
title: 'Buffer After (minutes)',
type: 'short-input',
placeholder: 'Buffer time after event',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
},
{
id: 'eventTypeScheduleId',
title: 'Schedule ID',
type: 'short-input',
placeholder: 'Assign to specific schedule (optional)',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
},
{
id: 'disableGuests',
title: 'Disable Guests',
type: 'switch',
description: 'Prevent attendees from adding guests',
condition: {
field: 'operation',
value: ['calcom_create_event_type', 'calcom_update_event_type'],
},
},
// === Schedule fields ===
{
id: 'scheduleId',
title: 'Schedule ID',
type: 'short-input',
placeholder: 'Enter schedule ID',
condition: {
field: 'operation',
value: ['calcom_get_schedule', 'calcom_update_schedule', 'calcom_delete_schedule'],
},
required: {
field: 'operation',
value: ['calcom_get_schedule', 'calcom_update_schedule', 'calcom_delete_schedule'],
},
},
{
id: 'scheduleName',
title: 'Name',
type: 'short-input',
placeholder: 'Schedule name (e.g., Working Hours)',
condition: {
field: 'operation',
value: ['calcom_create_schedule', 'calcom_update_schedule'],
},
required: { field: 'operation', value: 'calcom_create_schedule' },
},
{
id: 'timeZone',
title: 'Time Zone',
type: 'short-input',
placeholder: 'e.g., America/New_York',
condition: {
field: 'operation',
value: ['calcom_create_schedule', 'calcom_update_schedule', 'calcom_get_slots'],
},
required: { field: 'operation', value: 'calcom_create_schedule' },
},
{
id: 'isDefault',
title: 'Default Schedule',
type: 'switch',
description: 'Set as the default schedule',
condition: {
field: 'operation',
value: ['calcom_create_schedule', 'calcom_update_schedule'],
},
},
{
id: 'availability',
title: 'Availability',
type: 'code',
language: 'json',
placeholder: `[
{
"days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"startTime": "09:00",
"endTime": "17:00"
}
]`,
condition: {
field: 'operation',
value: ['calcom_create_schedule', 'calcom_update_schedule'],
},
wandConfig: {
enabled: true,
prompt: `Generate a Cal.com availability JSON array based on the user's description.
Each availability object has:
- days: Array of weekday names (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
- startTime: HH:MM format (24-hour)
- endTime: HH:MM format (24-hour)
Example for "9-5 weekdays":
[{"days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "startTime": "09:00", "endTime": "17:00"}]
Example for "mornings only, Monday and Wednesday":
[{"days": ["Monday", "Wednesday"], "startTime": "08:00", "endTime": "12:00"}]
Return ONLY valid JSON - no explanations.`,
placeholder: 'Describe your availability (e.g., "9-5 weekdays")...',
generationType: 'json-object',
},
},
// === Slots fields ===
{
id: 'eventTypeSlug',
title: 'Event Type Slug',
type: 'short-input',
placeholder: 'Event type slug (alternative to ID)',
condition: { field: 'operation', value: 'calcom_get_slots' },
},
{
id: 'username',
title: 'Username',
type: 'short-input',
placeholder: 'Cal.com username (required with slug)',
condition: { field: 'operation', value: 'calcom_get_slots' },
},
{
id: 'duration',
title: 'Duration (minutes)',
type: 'short-input',
placeholder: 'Slot duration (optional)',
condition: { field: 'operation', value: 'calcom_get_slots' },
},
// === List Event Types sorting ===
{
id: 'sortCreatedAt',
title: 'Sort by Created',
type: 'dropdown',
options: [
{ label: 'None', id: '' },
{ label: 'Ascending', id: 'asc' },
{ label: 'Descending', id: 'desc' },
],
condition: { field: 'operation', value: 'calcom_list_event_types' },
},
// Trigger SubBlocks
...getTrigger('calcom_booking_created').subBlocks,
...getTrigger('calcom_booking_cancelled').subBlocks,
...getTrigger('calcom_booking_rescheduled').subBlocks,
],
tools: {
access: [
'calcom_create_booking',
'calcom_get_booking',
'calcom_list_bookings',
'calcom_cancel_booking',
'calcom_reschedule_booking',
'calcom_confirm_booking',
'calcom_decline_booking',
'calcom_create_event_type',
'calcom_get_event_type',
'calcom_list_event_types',
'calcom_update_event_type',
'calcom_delete_event_type',
'calcom_create_schedule',
'calcom_get_schedule',
'calcom_list_schedules',
'calcom_update_schedule',
'calcom_delete_schedule',
'calcom_get_default_schedule',
'calcom_get_slots',
],
config: {
tool: (params) => params.operation || 'calcom_list_bookings',
params: (params) => {
const {
operation,
attendeeName,
attendeeEmail,
attendeeTimeZone,
attendeePhone,
guests,
metadata,
availability,
eventTypeIdParam,
bookingStatus,
eventLength,
scheduleName,
eventTypeScheduleId,
...rest
} = params
const result: Record<string, unknown> = { ...rest }
// Build attendee object for create booking
if (operation === 'calcom_create_booking') {
result.attendee = {
name: attendeeName,
...(attendeeEmail && { email: attendeeEmail }),
timeZone: attendeeTimeZone,
...(attendeePhone && { phoneNumber: attendeePhone }),
}
result.attendeeName = undefined
result.attendeeEmail = undefined
result.attendeeTimeZone = undefined
result.attendeePhone = undefined
// Parse guests as array
if (guests) {
result.guests = guests.split(',').map((g: string) => g.trim())
}
}
// Parse JSON fields
if (metadata) {
try {
result.metadata = typeof metadata === 'string' ? JSON.parse(metadata) : metadata
} catch {
throw new Error('Invalid JSON for metadata')
}
}
if (availability) {
try {
result.availability =
typeof availability === 'string' ? JSON.parse(availability) : availability
} catch {
throw new Error('Invalid JSON for availability')
}
}
// Map renamed fields
if (eventTypeIdParam) {
result.id = eventTypeIdParam
}
if (bookingStatus) {
result.status = bookingStatus
}
if (eventLength) {
result.lengthInMinutes = Number(eventLength)
}
if (scheduleName) {
result.name = scheduleName
}
if (eventTypeScheduleId) {
result.scheduleId = Number(eventTypeScheduleId)
}
return result
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Cal.com OAuth credential' },
// Booking inputs
eventTypeId: { type: 'number', description: 'Event type ID' },
start: { type: 'string', description: 'Start time (ISO 8601)' },
end: { type: 'string', description: 'End time (ISO 8601)' },
attendeeName: { type: 'string', description: 'Attendee name' },
attendeeEmail: { type: 'string', description: 'Attendee email' },
attendeeTimeZone: { type: 'string', description: 'Attendee time zone' },
attendeePhone: { type: 'string', description: 'Attendee phone number' },
guests: { type: 'string', description: 'Comma-separated guest emails' },
lengthInMinutes: { type: 'number', description: 'Duration override in minutes' },
metadata: { type: 'json', description: 'Custom metadata object' },
bookingUid: { type: 'string', description: 'Booking UID' },
cancellationReason: { type: 'string', description: 'Reason for cancellation' },
reschedulingReason: { type: 'string', description: 'Reason for rescheduling' },
bookingStatus: { type: 'string', description: 'Filter by booking status' },
// Event type inputs
eventTypeIdParam: { type: 'number', description: 'Event type ID for get/update/delete' },
title: { type: 'string', description: 'Event type title' },
slug: { type: 'string', description: 'URL-friendly slug' },
eventLength: { type: 'number', description: 'Event duration in minutes' },
description: { type: 'string', description: 'Event type description' },
slotInterval: { type: 'number', description: 'Minutes between available slots' },
minimumBookingNotice: { type: 'number', description: 'Minimum advance notice' },
beforeEventBuffer: { type: 'number', description: 'Buffer before event' },
afterEventBuffer: { type: 'number', description: 'Buffer after event' },
eventTypeScheduleId: { type: 'number', description: 'Schedule ID for event type' },
disableGuests: { type: 'boolean', description: 'Disable guest additions' },
sortCreatedAt: { type: 'string', description: 'Sort order for event types' },
// Schedule inputs
scheduleId: { type: 'number', description: 'Schedule ID' },
scheduleName: { type: 'string', description: 'Schedule name' },
timeZone: { type: 'string', description: 'Time zone' },
isDefault: { type: 'boolean', description: 'Set as default schedule' },
availability: { type: 'json', description: 'Availability configuration' },
// Slots inputs
eventTypeSlug: { type: 'string', description: 'Event type slug' },
username: { type: 'string', description: 'Cal.com username' },
duration: { type: 'number', description: 'Slot duration in minutes' },
},
outputs: {
success: { type: 'boolean', description: 'Whether operation succeeded' },
// Booking outputs
bookingUid: { type: 'string', description: 'Booking unique identifier' },
bookingId: { type: 'number', description: 'Booking ID' },
status: { type: 'string', description: 'Booking or event status' },
title: { type: 'string', description: 'Booking or event type title' },
startTime: { type: 'string', description: 'Booking start time (ISO 8601)' },
endTime: { type: 'string', description: 'Booking end time (ISO 8601)' },
attendees: { type: 'json', description: 'List of attendees' },
hosts: { type: 'json', description: 'List of hosts' },
location: { type: 'string', description: 'Meeting location' },
meetingUrl: { type: 'string', description: 'Video meeting URL' },
// List outputs
bookings: { type: 'json', description: 'List of bookings' },
eventTypes: { type: 'json', description: 'List of event types' },
schedules: { type: 'json', description: 'List of schedules' },
slots: { type: 'json', description: 'Available time slots' },
// Event type outputs
id: { type: 'number', description: 'Event type or schedule ID' },
slug: { type: 'string', description: 'Event type slug' },
lengthInMinutes: { type: 'number', description: 'Event duration' },
description: { type: 'string', description: 'Event type description' },
// Schedule outputs
name: { type: 'string', description: 'Schedule name' },
timeZone: { type: 'string', description: 'Schedule time zone' },
isDefault: { type: 'boolean', description: 'Whether schedule is default' },
availability: { type: 'json', description: 'Availability configuration' },
// Delete output
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
message: { type: 'string', description: 'Status or error message' },
// Trigger outputs
triggerEvent: { type: 'string', description: 'Webhook event type' },
createdAt: { type: 'string', description: 'Webhook event timestamp' },
payload: { type: 'json', description: 'Complete webhook payload data' },
},
triggers: {
enabled: true,
available: ['calcom_booking_created', 'calcom_booking_cancelled', 'calcom_booking_rescheduled'],
},
}

View File

@@ -9,6 +9,7 @@ import { ApolloBlock } from '@/blocks/blocks/apollo'
import { ArxivBlock } from '@/blocks/blocks/arxiv'
import { AsanaBlock } from '@/blocks/blocks/asana'
import { BrowserUseBlock } from '@/blocks/blocks/browser_use'
import { CalComBlock } from '@/blocks/blocks/calcom'
import { CalendlyBlock } from '@/blocks/blocks/calendly'
import { ChatTriggerBlock } from '@/blocks/blocks/chat_trigger'
import { CirclebackBlock } from '@/blocks/blocks/circleback'
@@ -165,6 +166,7 @@ export const registry: Record<string, BlockConfig> = {
arxiv: ArxivBlock,
asana: AsanaBlock,
browser_use: BrowserUseBlock,
calcom: CalComBlock,
calendly: CalendlyBlock,
chat_trigger: ChatTriggerBlock,
circleback: CirclebackBlock,

View File

@@ -5113,3 +5113,42 @@ export function PulseIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function CalComIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='101'
height='22'
viewBox='0 0 101 22'
fill='currentColor'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M10.0582 20.817C4.32115 20.817 0 16.2763 0 10.6704C0 5.04589 4.1005 0.467773 10.0582 0.467773C13.2209 0.467773 15.409 1.43945 17.1191 3.66311L14.3609 5.96151C13.2025 4.72822 11.805 4.11158 10.0582 4.11158C6.17833 4.11158 4.04533 7.08268 4.04533 10.6704C4.04533 14.2582 6.38059 17.1732 10.0582 17.1732C11.7866 17.1732 13.2577 16.5566 14.4161 15.3233L17.1375 17.7151C15.501 19.8453 13.2577 20.817 10.0582 20.817Z'
fill='#292929'
/>
<path
d='M29.0161 5.88601H32.7304V20.4612H29.0161V18.331C28.2438 19.8446 26.9566 20.8536 24.4927 20.8536C20.5577 20.8536 17.4133 17.4341 17.4133 13.2297C17.4133 9.02528 20.5577 5.60571 24.4927 5.60571C26.9383 5.60571 28.2438 6.61477 29.0161 8.12835V5.88601ZM29.1264 13.2297C29.1264 10.95 27.5634 9.06266 25.0995 9.06266C22.7274 9.06266 21.1828 10.9686 21.1828 13.2297C21.1828 15.4346 22.7274 17.3967 25.0995 17.3967C27.5451 17.3967 29.1264 15.4907 29.1264 13.2297Z'
fill='#292929'
/>
<path d='M35.3599 0H39.0742V20.4427H35.3599V0Z' fill='#292929' />
<path
d='M40.7291 18.5182C40.7291 17.3223 41.6853 16.3132 42.9908 16.3132C44.2964 16.3132 45.2158 17.3223 45.2158 18.5182C45.2158 19.7515 44.278 20.7605 42.9908 20.7605C41.7037 20.7605 40.7291 19.7515 40.7291 18.5182Z'
fill='#292929'
/>
<path
d='M59.4296 18.1068C58.0505 19.7885 55.9543 20.8536 53.4719 20.8536C49.0404 20.8536 45.7858 17.4341 45.7858 13.2297C45.7858 9.02528 49.0404 5.60571 53.4719 5.60571C55.8623 5.60571 57.9402 6.61477 59.3193 8.20309L56.4508 10.6136C55.7336 9.71667 54.7958 9.04397 53.4719 9.04397C51.0999 9.04397 49.5553 10.95 49.5553 13.211C49.5553 15.472 51.0999 17.378 53.4719 17.378C54.9062 17.378 55.8991 16.6306 56.6346 15.6215L59.4296 18.1068Z'
fill='#292929'
/>
<path
d='M59.7422 13.2297C59.7422 9.02528 62.9968 5.60571 67.4283 5.60571C71.8598 5.60571 75.1144 9.02528 75.1144 13.2297C75.1144 17.4341 71.8598 20.8536 67.4283 20.8536C62.9968 20.8349 59.7422 17.4341 59.7422 13.2297ZM71.3449 13.2297C71.3449 10.95 69.8003 9.06266 67.4283 9.06266C65.0563 9.04397 63.5117 10.95 63.5117 13.2297C63.5117 15.4907 65.0563 17.3967 67.4283 17.3967C69.8003 17.3967 71.3449 15.4907 71.3449 13.2297Z'
fill='#292929'
/>
<path
d='M100.232 11.5482V20.4428H96.518V12.4638C96.518 9.94119 95.3412 8.85739 93.576 8.85739C91.921 8.85739 90.7442 9.67958 90.7442 12.4638V20.4428H87.0299V12.4638C87.0299 9.94119 85.8346 8.85739 84.0878 8.85739C82.4329 8.85739 80.9802 9.67958 80.9802 12.4638V20.4428H77.2659V5.8676H80.9802V7.88571C81.7525 6.31607 83.15 5.53125 85.3014 5.53125C87.3425 5.53125 89.0525 6.5403 89.9903 8.24074C90.9281 6.50293 92.3072 5.53125 94.8079 5.53125C97.8603 5.54994 100.232 7.86702 100.232 11.5482Z'
fill='#292929'
/>
</svg>
)
}

View File

@@ -452,6 +452,7 @@ export const auth = betterAuth({
'linear',
'shopify',
'trello',
'calcom',
...SSO_TRUSTED_PROVIDERS,
],
},
@@ -2541,6 +2542,56 @@ export const auth = betterAuth({
}
},
},
// Cal.com provider
{
providerId: 'calcom',
clientId: env.CALCOM_CLIENT_ID as string,
clientSecret: env.CALCOM_CLIENT_SECRET as string,
authorizationUrl: 'https://app.cal.com/auth/oauth2/authorize',
tokenUrl: 'https://app.cal.com/api/auth/oauth/token',
scopes: [],
responseType: 'code',
pkce: true,
accessType: 'offline',
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/calcom`,
getUserInfo: async (tokens) => {
try {
logger.info('Fetching Cal.com user profile')
const response = await fetch('https://api.cal.com/v2/me', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
'cal-api-version': '2024-08-13',
},
})
if (!response.ok) {
logger.error('Failed to fetch Cal.com user info', {
status: response.status,
statusText: response.statusText,
})
throw new Error('Failed to fetch user info')
}
const data = await response.json()
const profile = data.data || data
return {
id: `${profile.id?.toString()}-${crypto.randomUUID()}`,
name: profile.name || 'Cal.com User',
email: profile.email || `${profile.id}@cal.com`,
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
}
} catch (error) {
logger.error('Error in Cal.com getUserInfo:', { error })
return null
}
},
},
],
}),
// Include SSO plugin when enabled

View File

@@ -243,6 +243,8 @@ export const env = createEnv({
WORDPRESS_CLIENT_SECRET: z.string().optional(), // WordPress.com OAuth client secret
SPOTIFY_CLIENT_ID: z.string().optional(), // Spotify OAuth client ID
SPOTIFY_CLIENT_SECRET: z.string().optional(), // Spotify OAuth client secret
CALCOM_CLIENT_ID: z.string().optional(), // Cal.com OAuth client ID
CALCOM_CLIENT_SECRET: z.string().optional(), // Cal.com OAuth client secret
// E2B Remote Code Execution
E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution

View File

@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
import {
AirtableIcon,
AsanaIcon,
CalComIcon,
ConfluenceIcon,
DropboxIcon,
GithubIcon,
@@ -613,6 +614,21 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
},
defaultService: 'asana',
},
calcom: {
name: 'Cal.com',
icon: CalComIcon,
services: {
calcom: {
name: 'Cal.com',
description: 'Manage Cal.com bookings, event types, and schedules.',
providerId: 'calcom',
icon: CalComIcon,
baseProviderIcon: CalComIcon,
scopes: [],
},
},
defaultService: 'calcom',
},
pipedrive: {
name: 'Pipedrive',
icon: PipedriveIcon,
@@ -861,6 +877,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
supportsRefreshTokenRotation: true,
}
}
case 'calcom': {
const { clientId, clientSecret } = getCredentials(
env.CALCOM_CLIENT_ID,
env.CALCOM_CLIENT_SECRET
)
return {
tokenEndpoint: 'https://app.cal.com/api/auth/oauth/refreshToken',
clientId,
clientSecret,
useBasicAuth: false,
supportsRefreshTokenRotation: true,
}
}
case 'airtable': {
const { clientId, clientSecret } = getCredentials(
env.AIRTABLE_CLIENT_ID,

View File

@@ -41,6 +41,7 @@ export type OAuthProvider =
| 'zoom'
| 'wordpress'
| 'spotify'
| 'calcom'
export type OAuthService =
| 'google'
@@ -81,6 +82,7 @@ export type OAuthService =
| 'zoom'
| 'wordpress'
| 'spotify'
| 'calcom'
export interface OAuthProviderConfig {
name: string

View File

@@ -13,6 +13,7 @@ import { convertSquareBracketsToTwiML } from '@/lib/webhooks/utils'
import {
handleSlackChallenge,
handleWhatsAppVerification,
validateCalcomSignature,
validateCirclebackSignature,
validateFirefliesSignature,
validateGitHubSignature,
@@ -654,6 +655,31 @@ export async function verifyProviderAuth(
}
}
if (foundWebhook.provider === 'calcom') {
const secret = providerConfig.webhookSecret as string | undefined
if (secret) {
const signature = request.headers.get('X-Cal-Signature-256')
if (!signature) {
logger.warn(`[${requestId}] Cal.com webhook missing signature header`)
return new NextResponse('Unauthorized - Missing Cal.com signature', { status: 401 })
}
const isValidSignature = validateCalcomSignature(secret, signature, rawBody)
if (!isValidSignature) {
logger.warn(`[${requestId}] Cal.com signature verification failed`, {
signatureLength: signature.length,
secretLength: secret.length,
})
return new NextResponse('Unauthorized - Invalid Cal.com signature', { status: 401 })
}
logger.debug(`[${requestId}] Cal.com signature verified successfully`)
}
}
if (foundWebhook.provider === 'jira') {
const secret = providerConfig.secret as string | undefined

View File

@@ -2537,3 +2537,49 @@ export function convertSquareBracketsToTwiML(twiml: string | undefined): string
// Replace [Tag] with <Tag> and [/Tag] with </Tag>
return twiml.replace(/\[(\/?[^\]]+)\]/g, '<$1>')
}
/**
* Validates a Cal.com webhook request signature using HMAC SHA-256
* @param secret - Cal.com webhook secret (plain text)
* @param signature - X-Cal-Signature-256 header value (hex-encoded HMAC SHA-256 signature)
* @param body - Raw request body string
* @returns Whether the signature is valid
*/
export function validateCalcomSignature(secret: string, signature: string, body: string): boolean {
try {
if (!secret || !signature || !body) {
logger.warn('Cal.com signature validation missing required fields', {
hasSecret: !!secret,
hasSignature: !!signature,
hasBody: !!body,
})
return false
}
const crypto = require('crypto')
const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
logger.debug('Cal.com signature comparison', {
computedSignature: `${computedHash.substring(0, 10)}...`,
providedSignature: `${signature.substring(0, 10)}...`,
computedLength: computedHash.length,
providedLength: signature.length,
match: computedHash === signature,
})
if (computedHash.length !== signature.length) {
return false
}
// Constant-time comparison to prevent timing attacks
let result = 0
for (let i = 0; i < computedHash.length; i++) {
result |= computedHash.charCodeAt(i) ^ signature.charCodeAt(i)
}
return result === 0
} catch (error) {
logger.error('Error validating Cal.com signature:', error)
return false
}
}

View File

@@ -0,0 +1,104 @@
import type { CalcomCancelBookingParams, CalcomCancelBookingResponse } from '@/tools/calcom/types'
import {
ATTENDEES_OUTPUT,
BOOKING_DATA_OUTPUT_PROPERTIES,
EVENT_TYPE_OUTPUT,
HOSTS_OUTPUT,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const cancelBookingTool: ToolConfig<CalcomCancelBookingParams, CalcomCancelBookingResponse> =
{
id: 'calcom_cancel_booking',
name: 'Cal.com Cancel Booking',
description: 'Cancel an existing booking',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
bookingUid: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Unique identifier (UID) of the booking to cancel',
},
cancellationReason: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Reason for cancelling the booking',
},
},
request: {
url: (params: CalcomCancelBookingParams) => {
return `https://api.cal.com/v2/bookings/${encodeURIComponent(params.bookingUid)}/cancel`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
}),
body: (params: CalcomCancelBookingParams) => {
const body: Record<string, unknown> = {}
if (params.cancellationReason) {
body.cancellationReason = params.cancellationReason
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Cancelled booking details',
properties: {
id: BOOKING_DATA_OUTPUT_PROPERTIES.id,
uid: BOOKING_DATA_OUTPUT_PROPERTIES.uid,
title: BOOKING_DATA_OUTPUT_PROPERTIES.title,
status: {
type: 'string',
description: 'Booking status (should be cancelled)',
},
cancellationReason: BOOKING_DATA_OUTPUT_PROPERTIES.cancellationReason,
cancelledByEmail: BOOKING_DATA_OUTPUT_PROPERTIES.cancelledByEmail,
start: BOOKING_DATA_OUTPUT_PROPERTIES.start,
end: BOOKING_DATA_OUTPUT_PROPERTIES.end,
duration: BOOKING_DATA_OUTPUT_PROPERTIES.duration,
eventTypeId: BOOKING_DATA_OUTPUT_PROPERTIES.eventTypeId,
eventType: EVENT_TYPE_OUTPUT,
location: BOOKING_DATA_OUTPUT_PROPERTIES.location,
attendees: ATTENDEES_OUTPUT,
hosts: HOSTS_OUTPUT,
metadata: BOOKING_DATA_OUTPUT_PROPERTIES.metadata,
createdAt: BOOKING_DATA_OUTPUT_PROPERTIES.createdAt,
},
},
},
}

View File

@@ -0,0 +1,95 @@
import type { CalcomConfirmBookingParams, CalcomConfirmBookingResponse } from '@/tools/calcom/types'
import {
ATTENDEES_OUTPUT,
BOOKING_DATA_OUTPUT_PROPERTIES,
EVENT_TYPE_OUTPUT,
HOSTS_OUTPUT,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const confirmBookingTool: ToolConfig<
CalcomConfirmBookingParams,
CalcomConfirmBookingResponse
> = {
id: 'calcom_confirm_booking',
name: 'Cal.com Confirm Booking',
description: 'Confirm a pending booking that requires confirmation',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
bookingUid: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Unique identifier (UID) of the booking to confirm',
},
},
request: {
url: (params: CalcomConfirmBookingParams) => {
return `https://api.cal.com/v2/bookings/${encodeURIComponent(params.bookingUid)}/confirm`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
}),
body: () => {
return {}
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Confirmed booking details',
properties: {
id: BOOKING_DATA_OUTPUT_PROPERTIES.id,
uid: BOOKING_DATA_OUTPUT_PROPERTIES.uid,
title: BOOKING_DATA_OUTPUT_PROPERTIES.title,
status: {
type: 'string',
description: 'Booking status (should be accepted/confirmed)',
},
start: BOOKING_DATA_OUTPUT_PROPERTIES.start,
end: BOOKING_DATA_OUTPUT_PROPERTIES.end,
duration: BOOKING_DATA_OUTPUT_PROPERTIES.duration,
eventTypeId: BOOKING_DATA_OUTPUT_PROPERTIES.eventTypeId,
eventType: EVENT_TYPE_OUTPUT,
meetingUrl: BOOKING_DATA_OUTPUT_PROPERTIES.meetingUrl,
location: BOOKING_DATA_OUTPUT_PROPERTIES.location,
attendees: ATTENDEES_OUTPUT,
hosts: HOSTS_OUTPUT,
guests: BOOKING_DATA_OUTPUT_PROPERTIES.guests,
metadata: BOOKING_DATA_OUTPUT_PROPERTIES.metadata,
icsUid: BOOKING_DATA_OUTPUT_PROPERTIES.icsUid,
createdAt: BOOKING_DATA_OUTPUT_PROPERTIES.createdAt,
},
},
},
}

View File

@@ -0,0 +1,143 @@
import type { CalcomCreateBookingParams, CalcomCreateBookingResponse } from '@/tools/calcom/types'
import {
ATTENDEES_OUTPUT,
BOOKING_DATA_OUTPUT_PROPERTIES,
EVENT_TYPE_OUTPUT,
HOSTS_OUTPUT,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const createBookingTool: ToolConfig<CalcomCreateBookingParams, CalcomCreateBookingResponse> =
{
id: 'calcom_create_booking',
name: 'Cal.com Create Booking',
description: 'Create a new booking on Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
eventTypeId: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'The ID of the event type to book',
},
start: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Start time in UTC ISO 8601 format (e.g., 2024-01-15T09:00:00Z)',
},
attendee: {
type: 'object',
required: true,
visibility: 'user-only',
description:
'Attendee information object with name, email, timeZone, and optional phoneNumber',
},
guests: {
type: 'array',
required: false,
visibility: 'user-only',
description: 'Array of guest email addresses',
items: {
type: 'string',
description: 'Guest email address',
},
},
lengthInMinutes: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Duration of the booking in minutes (overrides event type default)',
},
metadata: {
type: 'object',
required: false,
visibility: 'user-only',
description: 'Custom metadata to attach to the booking',
},
},
request: {
url: 'https://api.cal.com/v2/bookings',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
}),
body: (params: CalcomCreateBookingParams) => {
const body: Record<string, unknown> = {
eventTypeId: params.eventTypeId,
start: params.start,
attendee: params.attendee,
}
if (params.guests && params.guests.length > 0) {
body.guests = params.guests
}
if (params.lengthInMinutes !== undefined) {
body.lengthInMinutes = params.lengthInMinutes
}
if (params.metadata) {
body.metadata = params.metadata
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Created booking details',
properties: {
id: BOOKING_DATA_OUTPUT_PROPERTIES.id,
uid: BOOKING_DATA_OUTPUT_PROPERTIES.uid,
title: BOOKING_DATA_OUTPUT_PROPERTIES.title,
status: BOOKING_DATA_OUTPUT_PROPERTIES.status,
start: BOOKING_DATA_OUTPUT_PROPERTIES.start,
end: BOOKING_DATA_OUTPUT_PROPERTIES.end,
duration: BOOKING_DATA_OUTPUT_PROPERTIES.duration,
eventTypeId: BOOKING_DATA_OUTPUT_PROPERTIES.eventTypeId,
eventType: EVENT_TYPE_OUTPUT,
meetingUrl: BOOKING_DATA_OUTPUT_PROPERTIES.meetingUrl,
location: BOOKING_DATA_OUTPUT_PROPERTIES.location,
absentHost: BOOKING_DATA_OUTPUT_PROPERTIES.absentHost,
attendees: ATTENDEES_OUTPUT,
hosts: HOSTS_OUTPUT,
guests: BOOKING_DATA_OUTPUT_PROPERTIES.guests,
bookingFieldsResponses: BOOKING_DATA_OUTPUT_PROPERTIES.bookingFieldsResponses,
metadata: BOOKING_DATA_OUTPUT_PROPERTIES.metadata,
icsUid: BOOKING_DATA_OUTPUT_PROPERTIES.icsUid,
createdAt: BOOKING_DATA_OUTPUT_PROPERTIES.createdAt,
},
},
},
}

View File

@@ -0,0 +1,171 @@
import type {
CalcomCreateEventTypeParams,
CalcomCreateEventTypeResponse,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const createEventTypeTool: ToolConfig<
CalcomCreateEventTypeParams,
CalcomCreateEventTypeResponse
> = {
id: 'calcom_create_event_type',
name: 'Cal.com Create Event Type',
description: 'Create a new event type in Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
title: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Title of the event type',
},
slug: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Unique slug for the event type URL',
},
lengthInMinutes: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'Duration of the event in minutes',
},
description: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Description of the event type',
},
slotInterval: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Interval between available booking slots in minutes',
},
minimumBookingNotice: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Minimum notice required before booking in minutes',
},
beforeEventBuffer: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Buffer time before the event in minutes',
},
afterEventBuffer: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Buffer time after the event in minutes',
},
scheduleId: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'ID of the schedule to use for availability',
},
disableGuests: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Whether to disable guests from being added to bookings',
},
},
request: {
url: () => 'https://api.cal.com/v2/event-types',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-14',
}),
body: (params: CalcomCreateEventTypeParams) => {
const body: Record<string, unknown> = {
title: params.title,
slug: params.slug,
lengthInMinutes: params.lengthInMinutes,
}
if (params.description !== undefined) {
body.description = params.description
}
if (params.slotInterval !== undefined) {
body.slotInterval = params.slotInterval
}
if (params.minimumBookingNotice !== undefined) {
body.minimumBookingNotice = params.minimumBookingNotice
}
if (params.beforeEventBuffer !== undefined) {
body.beforeEventBuffer = params.beforeEventBuffer
}
if (params.afterEventBuffer !== undefined) {
body.afterEventBuffer = params.afterEventBuffer
}
if (params.scheduleId !== undefined) {
body.scheduleId = params.scheduleId
}
if (params.disableGuests !== undefined) {
body.disableGuests = params.disableGuests
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Created event type details',
properties: {
id: { type: 'number', description: 'Event type ID' },
title: { type: 'string', description: 'Event type title' },
slug: { type: 'string', description: 'Event type slug' },
description: { type: 'string', description: 'Event type description' },
lengthInMinutes: { type: 'number', description: 'Duration in minutes' },
slotInterval: { type: 'number', description: 'Slot interval in minutes' },
minimumBookingNotice: { type: 'number', description: 'Minimum booking notice in minutes' },
beforeEventBuffer: { type: 'number', description: 'Buffer before event in minutes' },
afterEventBuffer: { type: 'number', description: 'Buffer after event in minutes' },
scheduleId: { type: 'number', description: 'Schedule ID' },
disableGuests: { type: 'boolean', description: 'Whether guests are disabled' },
createdAt: { type: 'string', description: 'ISO timestamp of creation' },
updatedAt: { type: 'string', description: 'ISO timestamp of last update' },
},
},
},
}

View File

@@ -0,0 +1,160 @@
import type {
CalcomAvailability,
CalcomCreateScheduleParams,
CalcomCreateScheduleResponse,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const createScheduleTool: ToolConfig<
CalcomCreateScheduleParams,
CalcomCreateScheduleResponse
> = {
id: 'calcom_create_schedule',
name: 'Cal.com Create Schedule',
description: 'Create a new availability schedule in Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
name: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Name of the schedule',
},
timeZone: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Timezone for the schedule (e.g., America/New_York)',
},
isDefault: {
type: 'boolean',
required: true,
visibility: 'user-only',
description: 'Whether this schedule should be the default',
},
availability: {
type: 'array',
required: false,
visibility: 'user-only',
description: 'Availability intervals for the schedule',
items: {
type: 'object',
description: 'Availability interval',
properties: {
days: {
type: 'array',
description:
'Days of the week (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)',
},
startTime: {
type: 'string',
description: 'Start time in HH:MM format',
},
endTime: {
type: 'string',
description: 'End time in HH:MM format',
},
},
},
},
},
request: {
url: () => 'https://api.cal.com/v2/schedules',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-11',
}),
body: (params: CalcomCreateScheduleParams) => {
const body: {
name: string
timeZone: string
isDefault: boolean
availability?: CalcomAvailability[]
} = {
name: params.name,
timeZone: params.timeZone,
isDefault: params.isDefault,
}
if (params.availability) {
body.availability = params.availability
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Created schedule data',
properties: {
id: {
type: 'number',
description: 'Unique identifier for the schedule',
},
name: {
type: 'string',
description: 'Name of the schedule',
},
timeZone: {
type: 'string',
description: 'Timezone of the schedule',
},
isDefault: {
type: 'boolean',
description: 'Whether this is the default schedule',
},
availability: {
type: 'array',
description: 'Availability intervals',
items: {
type: 'object',
properties: {
days: {
type: 'array',
description: 'Days of the week',
},
startTime: {
type: 'string',
description: 'Start time in HH:MM format',
},
endTime: {
type: 'string',
description: 'End time in HH:MM format',
},
},
},
},
},
},
},
}

View File

@@ -0,0 +1,105 @@
import type { CalcomDeclineBookingParams, CalcomDeclineBookingResponse } from '@/tools/calcom/types'
import {
ATTENDEES_OUTPUT,
BOOKING_DATA_OUTPUT_PROPERTIES,
EVENT_TYPE_OUTPUT,
HOSTS_OUTPUT,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const declineBookingTool: ToolConfig<
CalcomDeclineBookingParams,
CalcomDeclineBookingResponse
> = {
id: 'calcom_decline_booking',
name: 'Cal.com Decline Booking',
description: 'Decline a pending booking request',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
bookingUid: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Unique identifier (UID) of the booking to decline',
},
reason: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Reason for declining the booking',
},
},
request: {
url: (params: CalcomDeclineBookingParams) => {
return `https://api.cal.com/v2/bookings/${encodeURIComponent(params.bookingUid)}/decline`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
}),
body: (params: CalcomDeclineBookingParams) => {
const body: Record<string, unknown> = {}
if (params.reason) {
body.reason = params.reason
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Declined booking details',
properties: {
id: BOOKING_DATA_OUTPUT_PROPERTIES.id,
uid: BOOKING_DATA_OUTPUT_PROPERTIES.uid,
title: BOOKING_DATA_OUTPUT_PROPERTIES.title,
status: {
type: 'string',
description: 'Booking status (should be cancelled/rejected)',
},
cancellationReason: BOOKING_DATA_OUTPUT_PROPERTIES.cancellationReason,
start: BOOKING_DATA_OUTPUT_PROPERTIES.start,
end: BOOKING_DATA_OUTPUT_PROPERTIES.end,
duration: BOOKING_DATA_OUTPUT_PROPERTIES.duration,
eventTypeId: BOOKING_DATA_OUTPUT_PROPERTIES.eventTypeId,
eventType: EVENT_TYPE_OUTPUT,
location: BOOKING_DATA_OUTPUT_PROPERTIES.location,
attendees: ATTENDEES_OUTPUT,
hosts: HOSTS_OUTPUT,
metadata: BOOKING_DATA_OUTPUT_PROPERTIES.metadata,
createdAt: BOOKING_DATA_OUTPUT_PROPERTIES.createdAt,
},
},
},
}

View File

@@ -0,0 +1,78 @@
import type {
CalcomDeleteEventTypeParams,
CalcomDeleteEventTypeResponse,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const deleteEventTypeTool: ToolConfig<
CalcomDeleteEventTypeParams,
CalcomDeleteEventTypeResponse
> = {
id: 'calcom_delete_event_type',
name: 'Cal.com Delete Event Type',
description: 'Delete an event type from Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
eventTypeId: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'Event type ID to delete',
},
},
request: {
url: (params: CalcomDeleteEventTypeParams) =>
`https://api.cal.com/v2/event-types/${params.eventTypeId}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-14',
}),
},
transformResponse: async (response: Response) => {
if (response.status === 204 || response.status === 200) {
return {
success: true,
output: {
deleted: true,
message: 'Event type deleted successfully',
},
}
}
const data = await response.json()
return {
success: false,
output: {
deleted: false,
message: data.message || 'Failed to delete event type',
},
}
},
outputs: {
deleted: {
type: 'boolean',
description: 'Whether the event type was successfully deleted',
},
message: {
type: 'string',
description: 'Status message',
},
},
}

View File

@@ -0,0 +1,81 @@
import type { CalcomDeleteScheduleParams, CalcomDeleteScheduleResponse } from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const deleteScheduleTool: ToolConfig<
CalcomDeleteScheduleParams,
CalcomDeleteScheduleResponse
> = {
id: 'calcom_delete_schedule',
name: 'Cal.com Delete Schedule',
description: 'Delete a schedule from Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ID of the schedule to delete',
},
},
request: {
url: (params: CalcomDeleteScheduleParams) =>
`https://api.cal.com/v2/schedules/${encodeURIComponent(params.scheduleId)}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-11',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Deleted schedule data',
properties: {
id: {
type: 'number',
description: 'Unique identifier of the deleted schedule',
},
name: {
type: 'string',
description: 'Name of the deleted schedule',
},
timeZone: {
type: 'string',
description: 'Timezone of the deleted schedule',
},
isDefault: {
type: 'boolean',
description: 'Whether this was the default schedule',
},
},
},
},
}

View File

@@ -0,0 +1,97 @@
import type { CalcomGetBookingParams, CalcomGetBookingResponse } from '@/tools/calcom/types'
import {
ATTENDEES_OUTPUT,
BOOKING_DATA_OUTPUT_PROPERTIES,
EVENT_TYPE_OUTPUT,
HOSTS_OUTPUT,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const getBookingTool: ToolConfig<CalcomGetBookingParams, CalcomGetBookingResponse> = {
id: 'calcom_get_booking',
name: 'Cal.com Get Booking',
description: 'Get details of a specific booking by its UID',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
bookingUid: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Unique identifier (UID) of the booking',
},
},
request: {
url: (params: CalcomGetBookingParams) => {
return `https://api.cal.com/v2/bookings/${encodeURIComponent(params.bookingUid)}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Booking details',
properties: {
id: BOOKING_DATA_OUTPUT_PROPERTIES.id,
uid: BOOKING_DATA_OUTPUT_PROPERTIES.uid,
title: BOOKING_DATA_OUTPUT_PROPERTIES.title,
description: BOOKING_DATA_OUTPUT_PROPERTIES.description,
status: BOOKING_DATA_OUTPUT_PROPERTIES.status,
start: BOOKING_DATA_OUTPUT_PROPERTIES.start,
end: BOOKING_DATA_OUTPUT_PROPERTIES.end,
duration: BOOKING_DATA_OUTPUT_PROPERTIES.duration,
eventTypeId: BOOKING_DATA_OUTPUT_PROPERTIES.eventTypeId,
eventType: EVENT_TYPE_OUTPUT,
meetingUrl: BOOKING_DATA_OUTPUT_PROPERTIES.meetingUrl,
location: BOOKING_DATA_OUTPUT_PROPERTIES.location,
absentHost: BOOKING_DATA_OUTPUT_PROPERTIES.absentHost,
attendees: ATTENDEES_OUTPUT,
hosts: HOSTS_OUTPUT,
guests: BOOKING_DATA_OUTPUT_PROPERTIES.guests,
bookingFieldsResponses: BOOKING_DATA_OUTPUT_PROPERTIES.bookingFieldsResponses,
metadata: BOOKING_DATA_OUTPUT_PROPERTIES.metadata,
rating: BOOKING_DATA_OUTPUT_PROPERTIES.rating,
icsUid: BOOKING_DATA_OUTPUT_PROPERTIES.icsUid,
cancellationReason: BOOKING_DATA_OUTPUT_PROPERTIES.cancellationReason,
reschedulingReason: BOOKING_DATA_OUTPUT_PROPERTIES.reschedulingReason,
rescheduledFromUid: BOOKING_DATA_OUTPUT_PROPERTIES.rescheduledFromUid,
rescheduledToUid: BOOKING_DATA_OUTPUT_PROPERTIES.rescheduledToUid,
cancelledByEmail: BOOKING_DATA_OUTPUT_PROPERTIES.cancelledByEmail,
rescheduledByEmail: BOOKING_DATA_OUTPUT_PROPERTIES.rescheduledByEmail,
createdAt: BOOKING_DATA_OUTPUT_PROPERTIES.createdAt,
updatedAt: BOOKING_DATA_OUTPUT_PROPERTIES.updatedAt,
},
},
},
}

View File

@@ -0,0 +1,98 @@
import type {
CalcomGetDefaultScheduleParams,
CalcomGetDefaultScheduleResponse,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const getDefaultScheduleTool: ToolConfig<
CalcomGetDefaultScheduleParams,
CalcomGetDefaultScheduleResponse
> = {
id: 'calcom_get_default_schedule',
name: 'Cal.com Get Default Schedule',
description: 'Get the default availability schedule from Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
},
request: {
url: () => 'https://api.cal.com/v2/schedules/default',
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-11',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Default schedule data',
properties: {
id: {
type: 'number',
description: 'Unique identifier for the schedule',
},
name: {
type: 'string',
description: 'Name of the schedule',
},
timeZone: {
type: 'string',
description: 'Timezone of the schedule',
},
isDefault: {
type: 'boolean',
description: 'Whether this is the default schedule (always true)',
},
availability: {
type: 'array',
description: 'Availability intervals',
items: {
type: 'object',
properties: {
days: {
type: 'array',
description: 'Days of the week',
},
startTime: {
type: 'string',
description: 'Start time in HH:MM format',
},
endTime: {
type: 'string',
description: 'End time in HH:MM format',
},
},
},
},
},
},
},
}

View File

@@ -0,0 +1,75 @@
import type { CalcomGetEventTypeParams, CalcomGetEventTypeResponse } from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const getEventTypeTool: ToolConfig<CalcomGetEventTypeParams, CalcomGetEventTypeResponse> = {
id: 'calcom_get_event_type',
name: 'Cal.com Get Event Type',
description: 'Get detailed information about a specific event type',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
eventTypeId: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'Event type ID to retrieve',
},
},
request: {
url: (params: CalcomGetEventTypeParams) =>
`https://api.cal.com/v2/event-types/${params.eventTypeId}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-14',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Event type details',
properties: {
id: { type: 'number', description: 'Event type ID' },
title: { type: 'string', description: 'Event type title' },
slug: { type: 'string', description: 'Event type slug' },
description: { type: 'string', description: 'Event type description' },
lengthInMinutes: { type: 'number', description: 'Duration in minutes' },
slotInterval: { type: 'number', description: 'Slot interval in minutes' },
minimumBookingNotice: { type: 'number', description: 'Minimum booking notice in minutes' },
beforeEventBuffer: { type: 'number', description: 'Buffer before event in minutes' },
afterEventBuffer: { type: 'number', description: 'Buffer after event in minutes' },
scheduleId: { type: 'number', description: 'Schedule ID' },
disableGuests: { type: 'boolean', description: 'Whether guests are disabled' },
createdAt: { type: 'string', description: 'ISO timestamp of creation' },
updatedAt: { type: 'string', description: 'ISO timestamp of last update' },
},
},
},
}

View File

@@ -0,0 +1,99 @@
import type { CalcomGetScheduleParams, CalcomGetScheduleResponse } from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const getScheduleTool: ToolConfig<CalcomGetScheduleParams, CalcomGetScheduleResponse> = {
id: 'calcom_get_schedule',
name: 'Cal.com Get Schedule',
description: 'Get a specific schedule by ID from Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ID of the schedule to retrieve',
},
},
request: {
url: (params: CalcomGetScheduleParams) =>
`https://api.cal.com/v2/schedules/${encodeURIComponent(params.scheduleId)}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-11',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Schedule data',
properties: {
id: {
type: 'number',
description: 'Unique identifier for the schedule',
},
name: {
type: 'string',
description: 'Name of the schedule',
},
timeZone: {
type: 'string',
description: 'Timezone of the schedule',
},
isDefault: {
type: 'boolean',
description: 'Whether this is the default schedule',
},
availability: {
type: 'array',
description: 'Availability intervals',
items: {
type: 'object',
properties: {
days: {
type: 'array',
description: 'Days of the week',
},
startTime: {
type: 'string',
description: 'Start time in HH:MM format',
},
endTime: {
type: 'string',
description: 'End time in HH:MM format',
},
},
},
},
},
},
},
}

View File

@@ -0,0 +1,168 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
export interface CalcomGetSlotsParams {
accessToken: string
start: string
end: string
eventTypeId?: number
eventTypeSlug?: string
username?: string
timeZone?: string
duration?: number
}
export interface CalcomGetSlotsResponse extends ToolResponse {
output: {
status: string
data: {
slots: Record<
string,
Array<{
time: string
}>
>
}
}
}
export const getSlotsTool: ToolConfig<CalcomGetSlotsParams, CalcomGetSlotsResponse> = {
id: 'calcom_get_slots',
name: 'Cal.com Get Slots',
description: 'Get available booking slots for a Cal.com event type within a time range',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
start: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Start of time range in UTC ISO 8601 format (e.g., 2024-01-15T00:00:00Z)',
},
end: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'End of time range in UTC ISO 8601 format (e.g., 2024-01-22T00:00:00Z)',
},
eventTypeId: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Event type ID for direct lookup',
},
eventTypeSlug: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Event type slug (requires username to be set)',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for personal event types (required when using eventTypeSlug)',
},
timeZone: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Timezone for returned slots (defaults to UTC)',
},
duration: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Slot length in minutes',
},
},
request: {
url: (params: CalcomGetSlotsParams) => {
const baseUrl = 'https://api.cal.com/v2/slots'
const queryParams: string[] = []
queryParams.push(`start=${encodeURIComponent(params.start)}`)
queryParams.push(`end=${encodeURIComponent(params.end)}`)
if (params.eventTypeId !== undefined) {
queryParams.push(`eventTypeId=${params.eventTypeId}`)
}
if (params.eventTypeSlug) {
queryParams.push(`eventTypeSlug=${encodeURIComponent(params.eventTypeSlug)}`)
}
if (params.username) {
queryParams.push(`username=${encodeURIComponent(params.username)}`)
}
if (params.timeZone) {
queryParams.push(`timeZone=${encodeURIComponent(params.timeZone)}`)
}
if (params.duration !== undefined) {
queryParams.push(`duration=${params.duration}`)
}
return `${baseUrl}?${queryParams.join('&')}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-09-04',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Slots data container',
properties: {
slots: {
type: 'object',
description: 'Available time slots grouped by date (YYYY-MM-DD keys)',
properties: {
'[date]': {
type: 'array',
description: 'Array of available slots for this date',
items: {
type: 'object',
properties: {
time: {
type: 'string',
description: 'ISO 8601 timestamp of the available slot',
},
},
},
},
},
},
},
},
},
}

View File

@@ -0,0 +1,39 @@
import { cancelBookingTool } from '@/tools/calcom/cancel_booking'
import { confirmBookingTool } from '@/tools/calcom/confirm_booking'
import { createBookingTool } from '@/tools/calcom/create_booking'
import { createEventTypeTool } from '@/tools/calcom/create_event_type'
import { createScheduleTool } from '@/tools/calcom/create_schedule'
import { declineBookingTool } from '@/tools/calcom/decline_booking'
import { deleteEventTypeTool } from '@/tools/calcom/delete_event_type'
import { deleteScheduleTool } from '@/tools/calcom/delete_schedule'
import { getBookingTool } from '@/tools/calcom/get_booking'
import { getDefaultScheduleTool } from '@/tools/calcom/get_default_schedule'
import { getEventTypeTool } from '@/tools/calcom/get_event_type'
import { getScheduleTool } from '@/tools/calcom/get_schedule'
import { getSlotsTool } from '@/tools/calcom/get_slots'
import { listBookingsTool } from '@/tools/calcom/list_bookings'
import { listEventTypesTool } from '@/tools/calcom/list_event_types'
import { listSchedulesTool } from '@/tools/calcom/list_schedules'
import { rescheduleBookingTool } from '@/tools/calcom/reschedule_booking'
import { updateEventTypeTool } from '@/tools/calcom/update_event_type'
import { updateScheduleTool } from '@/tools/calcom/update_schedule'
export const calcomCancelBookingTool = cancelBookingTool
export const calcomConfirmBookingTool = confirmBookingTool
export const calcomCreateBookingTool = createBookingTool
export const calcomCreateEventTypeTool = createEventTypeTool
export const calcomCreateScheduleTool = createScheduleTool
export const calcomDeclineBookingTool = declineBookingTool
export const calcomDeleteEventTypeTool = deleteEventTypeTool
export const calcomDeleteScheduleTool = deleteScheduleTool
export const calcomGetBookingTool = getBookingTool
export const calcomGetDefaultScheduleTool = getDefaultScheduleTool
export const calcomGetEventTypeTool = getEventTypeTool
export const calcomGetScheduleTool = getScheduleTool
export const calcomGetSlotsTool = getSlotsTool
export const calcomListBookingsTool = listBookingsTool
export const calcomListEventTypesTool = listEventTypesTool
export const calcomListSchedulesTool = listSchedulesTool
export const calcomRescheduleBookingTool = rescheduleBookingTool
export const calcomUpdateEventTypeTool = updateEventTypeTool
export const calcomUpdateScheduleTool = updateScheduleTool

View File

@@ -0,0 +1,125 @@
import type { CalcomListBookingsParams, CalcomListBookingsResponse } from '@/tools/calcom/types'
import {
ATTENDEES_OUTPUT,
BOOKING_DATA_OUTPUT_PROPERTIES,
EVENT_TYPE_OUTPUT,
HOSTS_OUTPUT,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const listBookingsTool: ToolConfig<CalcomListBookingsParams, CalcomListBookingsResponse> = {
id: 'calcom_list_bookings',
name: 'Cal.com List Bookings',
description: 'List all bookings with optional status filter',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
status: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Filter bookings by status: upcoming, recurring, past, cancelled, or unconfirmed',
},
take: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of bookings to return (pagination limit)',
},
skip: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of bookings to skip (pagination offset)',
},
},
request: {
url: (params: CalcomListBookingsParams) => {
const baseUrl = 'https://api.cal.com/v2/bookings'
const queryParams: string[] = []
if (params.status) {
queryParams.push(`status=${encodeURIComponent(params.status)}`)
}
if (params.take !== undefined) {
queryParams.push(`take=${params.take}`)
}
if (params.skip !== undefined) {
queryParams.push(`skip=${params.skip}`)
}
return queryParams.length > 0 ? `${baseUrl}?${queryParams.join('&')}` : baseUrl
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'array',
description: 'Array of bookings',
items: {
type: 'object',
properties: {
id: BOOKING_DATA_OUTPUT_PROPERTIES.id,
uid: BOOKING_DATA_OUTPUT_PROPERTIES.uid,
title: BOOKING_DATA_OUTPUT_PROPERTIES.title,
description: BOOKING_DATA_OUTPUT_PROPERTIES.description,
status: BOOKING_DATA_OUTPUT_PROPERTIES.status,
start: BOOKING_DATA_OUTPUT_PROPERTIES.start,
end: BOOKING_DATA_OUTPUT_PROPERTIES.end,
duration: BOOKING_DATA_OUTPUT_PROPERTIES.duration,
eventTypeId: BOOKING_DATA_OUTPUT_PROPERTIES.eventTypeId,
eventType: EVENT_TYPE_OUTPUT,
meetingUrl: BOOKING_DATA_OUTPUT_PROPERTIES.meetingUrl,
location: BOOKING_DATA_OUTPUT_PROPERTIES.location,
absentHost: BOOKING_DATA_OUTPUT_PROPERTIES.absentHost,
attendees: ATTENDEES_OUTPUT,
hosts: HOSTS_OUTPUT,
guests: BOOKING_DATA_OUTPUT_PROPERTIES.guests,
bookingFieldsResponses: BOOKING_DATA_OUTPUT_PROPERTIES.bookingFieldsResponses,
metadata: BOOKING_DATA_OUTPUT_PROPERTIES.metadata,
rating: BOOKING_DATA_OUTPUT_PROPERTIES.rating,
icsUid: BOOKING_DATA_OUTPUT_PROPERTIES.icsUid,
cancellationReason: BOOKING_DATA_OUTPUT_PROPERTIES.cancellationReason,
reschedulingReason: BOOKING_DATA_OUTPUT_PROPERTIES.reschedulingReason,
rescheduledFromUid: BOOKING_DATA_OUTPUT_PROPERTIES.rescheduledFromUid,
createdAt: BOOKING_DATA_OUTPUT_PROPERTIES.createdAt,
updatedAt: BOOKING_DATA_OUTPUT_PROPERTIES.updatedAt,
},
},
},
},
}

View File

@@ -0,0 +1,92 @@
import type { CalcomListEventTypesParams, CalcomListEventTypesResponse } from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const listEventTypesTool: ToolConfig<
CalcomListEventTypesParams,
CalcomListEventTypesResponse
> = {
id: 'calcom_list_event_types',
name: 'Cal.com List Event Types',
description: 'Retrieve a list of all event types',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
sortCreatedAt: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Sort by creation date: "asc" or "desc"',
},
},
request: {
url: (params: CalcomListEventTypesParams) => {
const url = 'https://api.cal.com/v2/event-types'
const queryParams: string[] = []
if (params.sortCreatedAt) {
queryParams.push(`sortCreatedAt=${encodeURIComponent(params.sortCreatedAt)}`)
}
return queryParams.length > 0 ? `${url}?${queryParams.join('&')}` : url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-14',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'array',
description: 'Array of event types',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Event type ID' },
title: { type: 'string', description: 'Event type title' },
slug: { type: 'string', description: 'Event type slug' },
description: { type: 'string', description: 'Event type description' },
lengthInMinutes: { type: 'number', description: 'Duration in minutes' },
slotInterval: { type: 'number', description: 'Slot interval in minutes' },
minimumBookingNotice: {
type: 'number',
description: 'Minimum booking notice in minutes',
},
beforeEventBuffer: { type: 'number', description: 'Buffer before event in minutes' },
afterEventBuffer: { type: 'number', description: 'Buffer after event in minutes' },
scheduleId: { type: 'number', description: 'Schedule ID' },
disableGuests: { type: 'boolean', description: 'Whether guests are disabled' },
createdAt: { type: 'string', description: 'ISO timestamp of creation' },
updatedAt: { type: 'string', description: 'ISO timestamp of last update' },
},
},
},
},
}

View File

@@ -0,0 +1,96 @@
import type { CalcomListSchedulesParams, CalcomListSchedulesResponse } from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const listSchedulesTool: ToolConfig<CalcomListSchedulesParams, CalcomListSchedulesResponse> =
{
id: 'calcom_list_schedules',
name: 'Cal.com List Schedules',
description: 'List all availability schedules from Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
},
request: {
url: () => 'https://api.cal.com/v2/schedules',
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-11',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'array',
description: 'Array of schedule objects',
items: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'Unique identifier for the schedule',
},
name: {
type: 'string',
description: 'Name of the schedule',
},
timeZone: {
type: 'string',
description: 'Timezone of the schedule',
},
isDefault: {
type: 'boolean',
description: 'Whether this is the default schedule',
},
availability: {
type: 'array',
description: 'Availability intervals',
items: {
type: 'object',
properties: {
days: {
type: 'array',
description: 'Days of the week',
},
startTime: {
type: 'string',
description: 'Start time in HH:MM format',
},
endTime: {
type: 'string',
description: 'End time in HH:MM format',
},
},
},
},
},
},
},
},
}

View File

@@ -0,0 +1,127 @@
import type {
CalcomRescheduleBookingParams,
CalcomRescheduleBookingResponse,
} from '@/tools/calcom/types'
import {
ATTENDEES_OUTPUT,
BOOKING_DATA_OUTPUT_PROPERTIES,
EVENT_TYPE_OUTPUT,
HOSTS_OUTPUT,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const rescheduleBookingTool: ToolConfig<
CalcomRescheduleBookingParams,
CalcomRescheduleBookingResponse
> = {
id: 'calcom_reschedule_booking',
name: 'Cal.com Reschedule Booking',
description: 'Reschedule an existing booking to a new time',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
bookingUid: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Unique identifier (UID) of the booking to reschedule',
},
start: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'New start time in UTC ISO 8601 format (e.g., 2024-01-15T09:00:00Z)',
},
reschedulingReason: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Reason for rescheduling the booking',
},
},
request: {
url: (params: CalcomRescheduleBookingParams) => {
return `https://api.cal.com/v2/bookings/${encodeURIComponent(params.bookingUid)}/reschedule`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
}),
body: (params: CalcomRescheduleBookingParams) => {
const body: Record<string, unknown> = {
start: params.start,
}
if (params.reschedulingReason) {
body.reschedulingReason = params.reschedulingReason
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Rescheduled booking details',
properties: {
id: BOOKING_DATA_OUTPUT_PROPERTIES.id,
uid: {
type: 'string',
description: 'Unique identifier for the new booking',
},
title: BOOKING_DATA_OUTPUT_PROPERTIES.title,
status: BOOKING_DATA_OUTPUT_PROPERTIES.status,
reschedulingReason: BOOKING_DATA_OUTPUT_PROPERTIES.reschedulingReason,
rescheduledFromUid: BOOKING_DATA_OUTPUT_PROPERTIES.rescheduledFromUid,
rescheduledByEmail: BOOKING_DATA_OUTPUT_PROPERTIES.rescheduledByEmail,
start: {
type: 'string',
description: 'New start time in ISO 8601 format',
},
end: {
type: 'string',
description: 'New end time in ISO 8601 format',
},
duration: BOOKING_DATA_OUTPUT_PROPERTIES.duration,
eventTypeId: BOOKING_DATA_OUTPUT_PROPERTIES.eventTypeId,
eventType: EVENT_TYPE_OUTPUT,
meetingUrl: BOOKING_DATA_OUTPUT_PROPERTIES.meetingUrl,
location: BOOKING_DATA_OUTPUT_PROPERTIES.location,
attendees: ATTENDEES_OUTPUT,
hosts: HOSTS_OUTPUT,
guests: BOOKING_DATA_OUTPUT_PROPERTIES.guests,
metadata: BOOKING_DATA_OUTPUT_PROPERTIES.metadata,
icsUid: BOOKING_DATA_OUTPUT_PROPERTIES.icsUid,
createdAt: BOOKING_DATA_OUTPUT_PROPERTIES.createdAt,
},
},
},
}

View File

@@ -0,0 +1,550 @@
import type { OutputProperty, ToolResponse } from '@/tools/types'
/**
* Shared output property definitions for Cal.com booking responses.
* These are reusable across all booking-related tools to ensure consistency.
*/
/**
* Output definition for attendee objects in booking responses
*/
export const ATTENDEE_OUTPUT_PROPERTIES = {
name: { type: 'string', description: 'Attendee name' },
email: { type: 'string', description: 'Attendee email' },
timeZone: { type: 'string', description: 'Attendee timezone' },
phoneNumber: { type: 'string', description: 'Attendee phone number' },
language: { type: 'string', description: 'Attendee language preference' },
absent: { type: 'boolean', description: 'Whether attendee was absent' },
} as const satisfies Record<string, OutputProperty>
/**
* Output definition for host objects in booking responses
*/
export const HOST_OUTPUT_PROPERTIES = {
id: { type: 'number', description: 'Host user ID' },
name: { type: 'string', description: 'Host name' },
email: { type: 'string', description: 'Host email' },
username: { type: 'string', description: 'Host username' },
timeZone: { type: 'string', description: 'Host timezone' },
} as const satisfies Record<string, OutputProperty>
/**
* Output definition for event type objects in booking responses
*/
export const EVENT_TYPE_OUTPUT_PROPERTIES = {
id: { type: 'number', description: 'Event type ID' },
slug: { type: 'string', description: 'Event type slug' },
} as const satisfies Record<string, OutputProperty>
/**
* Complete attendees array output definition with destructured items
*/
export const ATTENDEES_OUTPUT: OutputProperty = {
type: 'array',
description: 'List of attendees',
items: {
type: 'object',
properties: ATTENDEE_OUTPUT_PROPERTIES,
},
}
/**
* Complete hosts array output definition with destructured items
*/
export const HOSTS_OUTPUT: OutputProperty = {
type: 'array',
description: 'List of hosts',
items: {
type: 'object',
properties: HOST_OUTPUT_PROPERTIES,
},
}
/**
* Complete event type object output definition
*/
export const EVENT_TYPE_OUTPUT: OutputProperty = {
type: 'object',
description: 'Event type details',
properties: EVENT_TYPE_OUTPUT_PROPERTIES,
}
/**
* Common booking data output properties shared across all booking tools
*/
export const BOOKING_DATA_OUTPUT_PROPERTIES = {
id: {
type: 'number',
description: 'Numeric booking ID',
},
uid: {
type: 'string',
description: 'Unique identifier for the booking',
},
title: {
type: 'string',
description: 'Title of the booking',
},
description: {
type: 'string',
description: 'Description of the booking',
},
status: {
type: 'string',
description: 'Booking status (e.g., accepted, pending, cancelled)',
},
start: {
type: 'string',
description: 'Start time in ISO 8601 format',
},
end: {
type: 'string',
description: 'End time in ISO 8601 format',
},
duration: {
type: 'number',
description: 'Duration in minutes',
},
eventTypeId: {
type: 'number',
description: 'Event type ID',
},
eventType: EVENT_TYPE_OUTPUT,
meetingUrl: {
type: 'string',
description: 'URL to join the meeting',
},
location: {
type: 'string',
description: 'Location of the booking',
},
absentHost: {
type: 'boolean',
description: 'Whether the host was absent',
},
attendees: ATTENDEES_OUTPUT,
hosts: HOSTS_OUTPUT,
guests: {
type: 'array',
description: 'Guest email addresses',
items: {
type: 'string',
description: 'Guest email address',
},
},
bookingFieldsResponses: {
type: 'object',
description: 'Custom booking field responses',
},
metadata: {
type: 'object',
description: 'Custom metadata attached to the booking',
},
rating: {
type: 'number',
description: 'Booking rating',
},
icsUid: {
type: 'string',
description: 'ICS calendar UID',
},
createdAt: {
type: 'string',
description: 'When the booking was created',
},
updatedAt: {
type: 'string',
description: 'When the booking was last updated',
},
cancellationReason: {
type: 'string',
description: 'Reason for cancellation if cancelled',
},
reschedulingReason: {
type: 'string',
description: 'Reason for rescheduling if rescheduled',
},
rescheduledFromUid: {
type: 'string',
description: 'Original booking UID if this booking was rescheduled',
},
rescheduledToUid: {
type: 'string',
description: 'New booking UID after reschedule',
},
cancelledByEmail: {
type: 'string',
description: 'Email of person who cancelled the booking',
},
rescheduledByEmail: {
type: 'string',
description: 'Email of person who rescheduled the booking',
},
} as const satisfies Record<string, OutputProperty>
export interface CalcomCreateEventTypeParams {
accessToken: string
title: string
slug: string
lengthInMinutes: number
description?: string
slotInterval?: number
minimumBookingNotice?: number
beforeEventBuffer?: number
afterEventBuffer?: number
scheduleId?: number
disableGuests?: boolean
}
export interface CalcomEventType {
id: number
title: string
slug: string
description: string | null
lengthInMinutes: number
slotInterval: number | null
minimumBookingNotice: number
beforeEventBuffer: number
afterEventBuffer: number
scheduleId: number | null
disableGuests: boolean
createdAt: string
updatedAt: string
}
export interface CalcomCreateEventTypeResponse extends ToolResponse {
output: {
status: string
data: CalcomEventType
}
}
export interface CalcomGetEventTypeParams {
accessToken: string
eventTypeId: number
}
export interface CalcomGetEventTypeResponse extends ToolResponse {
output: {
status: string
data: CalcomEventType
}
}
export interface CalcomListEventTypesParams {
accessToken: string
sortCreatedAt?: 'asc' | 'desc'
}
export interface CalcomListEventTypesResponse extends ToolResponse {
output: {
status: string
data: CalcomEventType[]
}
}
export interface CalcomUpdateEventTypeParams {
accessToken: string
eventTypeId: number
title?: string
slug?: string
lengthInMinutes?: number
description?: string
slotInterval?: number
minimumBookingNotice?: number
beforeEventBuffer?: number
afterEventBuffer?: number
scheduleId?: number
disableGuests?: boolean
}
export interface CalcomUpdateEventTypeResponse extends ToolResponse {
output: {
status: string
data: CalcomEventType
}
}
export interface CalcomDeleteEventTypeParams {
accessToken: string
eventTypeId: number
}
export interface CalcomDeleteEventTypeResponse extends ToolResponse {
output: {
deleted: boolean
message: string
}
}
/**
* Common attendee structure for Cal.com bookings
*/
export interface CalcomAttendee {
name: string
email?: string
timeZone: string
phoneNumber?: string
}
/**
* Common booking structure returned by Cal.com API
*/
export interface CalcomBooking {
uid: string
title: string
description?: string
hosts: Array<{
id: number
name: string
email: string
timeZone: string
}>
status: string
cancellationReason?: string
reschedulingReason?: string
rescheduledFromUid?: string
start: string
end: string
duration: number
eventTypeId: number
eventType: {
id: number
slug: string
}
meetingUrl?: string
location?: string
absentHost: boolean
createdAt: string
updatedAt?: string
metadata?: Record<string, unknown>
rating?: number
attendees: Array<{
name: string
email: string
timeZone: string
phoneNumber?: string
language: string
absent: boolean
}>
guests?: string[]
bookingFieldsResponses?: Record<string, unknown>
}
/**
* Create booking params
*/
export interface CalcomCreateBookingParams {
accessToken: string
eventTypeId: number
start: string
attendee: CalcomAttendee
guests?: string[]
lengthInMinutes?: number
metadata?: Record<string, unknown>
}
export interface CalcomCreateBookingResponse extends ToolResponse {
output: {
status: string
data: CalcomBooking
}
}
/**
* Get booking params
*/
export interface CalcomGetBookingParams {
accessToken: string
bookingUid: string
}
export interface CalcomGetBookingResponse extends ToolResponse {
output: {
status: string
data: CalcomBooking
}
}
/**
* List bookings params
*/
export interface CalcomListBookingsParams {
accessToken: string
status?: 'upcoming' | 'recurring' | 'past' | 'cancelled' | 'unconfirmed'
take?: number
skip?: number
}
export interface CalcomListBookingsResponse extends ToolResponse {
output: {
status: string
data: CalcomBooking[]
}
}
/**
* Cancel booking params
*/
export interface CalcomCancelBookingParams {
accessToken: string
bookingUid: string
cancellationReason?: string
}
export interface CalcomCancelBookingResponse extends ToolResponse {
output: {
status: string
data: CalcomBooking
}
}
/**
* Reschedule booking params
*/
export interface CalcomRescheduleBookingParams {
accessToken: string
bookingUid: string
start: string
reschedulingReason?: string
}
export interface CalcomRescheduleBookingResponse extends ToolResponse {
output: {
status: string
data: CalcomBooking
}
}
/**
* Confirm booking params
*/
export interface CalcomConfirmBookingParams {
accessToken: string
bookingUid: string
}
export interface CalcomConfirmBookingResponse extends ToolResponse {
output: {
status: string
data: CalcomBooking
}
}
/**
* Decline booking params
*/
export interface CalcomDeclineBookingParams {
accessToken: string
bookingUid: string
reason?: string
}
export interface CalcomDeclineBookingResponse extends ToolResponse {
output: {
status: string
data: CalcomBooking
}
}
/**
* Availability interval for a schedule
*/
export interface CalcomAvailability {
days: ('Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday')[]
startTime: string
endTime: string
}
/**
* Schedule object returned by Cal.com API
*/
export interface CalcomSchedule {
id: number
name: string
timeZone: string
isDefault: boolean
availability: CalcomAvailability[]
}
export interface CalcomCreateScheduleParams {
accessToken: string
name: string
timeZone: string
isDefault: boolean
availability?: CalcomAvailability[]
}
export interface CalcomCreateScheduleResponse extends ToolResponse {
output: {
status: string
data: CalcomSchedule
}
}
export interface CalcomGetScheduleParams {
accessToken: string
scheduleId: string
}
export interface CalcomGetScheduleResponse extends ToolResponse {
output: {
status: string
data: CalcomSchedule
}
}
export interface CalcomListSchedulesParams {
accessToken: string
}
export interface CalcomListSchedulesResponse extends ToolResponse {
output: {
status: string
data: CalcomSchedule[]
}
}
export interface CalcomUpdateScheduleParams {
accessToken: string
scheduleId: string
name?: string
timeZone?: string
isDefault?: boolean
availability?: CalcomAvailability[]
}
export interface CalcomUpdateScheduleResponse extends ToolResponse {
output: {
status: string
data: CalcomSchedule
}
}
export interface CalcomDeleteScheduleParams {
accessToken: string
scheduleId: string
}
export interface CalcomDeleteScheduleResponse extends ToolResponse {
output: {
status: string
data: {
id: number
name: string
timeZone: string
isDefault: boolean
}
}
}
export interface CalcomGetDefaultScheduleParams {
accessToken: string
}
export interface CalcomGetDefaultScheduleResponse extends ToolResponse {
output: {
status: string
data: CalcomSchedule
}
}

View File

@@ -0,0 +1,186 @@
import type {
CalcomUpdateEventTypeParams,
CalcomUpdateEventTypeResponse,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const updateEventTypeTool: ToolConfig<
CalcomUpdateEventTypeParams,
CalcomUpdateEventTypeResponse
> = {
id: 'calcom_update_event_type',
name: 'Cal.com Update Event Type',
description: 'Update an existing event type in Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
eventTypeId: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'Event type ID to update',
},
title: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Title of the event type',
},
slug: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Unique slug for the event type URL',
},
lengthInMinutes: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Duration of the event in minutes',
},
description: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Description of the event type',
},
slotInterval: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Interval between available booking slots in minutes',
},
minimumBookingNotice: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Minimum notice required before booking in minutes',
},
beforeEventBuffer: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Buffer time before the event in minutes',
},
afterEventBuffer: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Buffer time after the event in minutes',
},
scheduleId: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'ID of the schedule to use for availability',
},
disableGuests: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Whether to disable guests from being added to bookings',
},
},
request: {
url: (params: CalcomUpdateEventTypeParams) =>
`https://api.cal.com/v2/event-types/${params.eventTypeId}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-14',
}),
body: (params: CalcomUpdateEventTypeParams) => {
const body: Record<string, unknown> = {}
if (params.title !== undefined) {
body.title = params.title
}
if (params.slug !== undefined) {
body.slug = params.slug
}
if (params.lengthInMinutes !== undefined) {
body.lengthInMinutes = params.lengthInMinutes
}
if (params.description !== undefined) {
body.description = params.description
}
if (params.slotInterval !== undefined) {
body.slotInterval = params.slotInterval
}
if (params.minimumBookingNotice !== undefined) {
body.minimumBookingNotice = params.minimumBookingNotice
}
if (params.beforeEventBuffer !== undefined) {
body.beforeEventBuffer = params.beforeEventBuffer
}
if (params.afterEventBuffer !== undefined) {
body.afterEventBuffer = params.afterEventBuffer
}
if (params.scheduleId !== undefined) {
body.scheduleId = params.scheduleId
}
if (params.disableGuests !== undefined) {
body.disableGuests = params.disableGuests
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Updated event type details',
properties: {
id: { type: 'number', description: 'Event type ID' },
title: { type: 'string', description: 'Event type title' },
slug: { type: 'string', description: 'Event type slug' },
description: { type: 'string', description: 'Event type description' },
lengthInMinutes: { type: 'number', description: 'Duration in minutes' },
slotInterval: { type: 'number', description: 'Slot interval in minutes' },
minimumBookingNotice: { type: 'number', description: 'Minimum booking notice in minutes' },
beforeEventBuffer: { type: 'number', description: 'Buffer before event in minutes' },
afterEventBuffer: { type: 'number', description: 'Buffer after event in minutes' },
scheduleId: { type: 'number', description: 'Schedule ID' },
disableGuests: { type: 'boolean', description: 'Whether guests are disabled' },
createdAt: { type: 'string', description: 'ISO timestamp of creation' },
updatedAt: { type: 'string', description: 'ISO timestamp of last update' },
},
},
},
}

View File

@@ -0,0 +1,175 @@
import type {
CalcomAvailability,
CalcomUpdateScheduleParams,
CalcomUpdateScheduleResponse,
} from '@/tools/calcom/types'
import type { ToolConfig } from '@/tools/types'
export const updateScheduleTool: ToolConfig<
CalcomUpdateScheduleParams,
CalcomUpdateScheduleResponse
> = {
id: 'calcom_update_schedule',
name: 'Cal.com Update Schedule',
description: 'Update an existing schedule in Cal.com',
version: '1.0.0',
oauth: {
required: true,
provider: 'calcom',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Cal.com OAuth access token',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ID of the schedule to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'New name for the schedule',
},
timeZone: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'New timezone for the schedule (e.g., America/New_York)',
},
isDefault: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Whether this schedule should be the default',
},
availability: {
type: 'array',
required: false,
visibility: 'user-only',
description: 'New availability intervals for the schedule',
items: {
type: 'object',
description: 'Availability interval',
properties: {
days: {
type: 'array',
description:
'Days of the week (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)',
},
startTime: {
type: 'string',
description: 'Start time in HH:MM format',
},
endTime: {
type: 'string',
description: 'End time in HH:MM format',
},
},
},
},
},
request: {
url: (params: CalcomUpdateScheduleParams) =>
`https://api.cal.com/v2/schedules/${encodeURIComponent(params.scheduleId)}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-06-11',
}),
body: (params: CalcomUpdateScheduleParams) => {
const body: {
name?: string
timeZone?: string
isDefault?: boolean
availability?: CalcomAvailability[]
} = {}
if (params.name !== undefined) {
body.name = params.name
}
if (params.timeZone !== undefined) {
body.timeZone = params.timeZone
}
if (params.isDefault !== undefined) {
body.isDefault = params.isDefault
}
if (params.availability !== undefined) {
body.availability = params.availability
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
status: {
type: 'string',
description: 'Response status',
},
data: {
type: 'object',
description: 'Updated schedule data',
properties: {
id: {
type: 'number',
description: 'Unique identifier for the schedule',
},
name: {
type: 'string',
description: 'Name of the schedule',
},
timeZone: {
type: 'string',
description: 'Timezone of the schedule',
},
isDefault: {
type: 'boolean',
description: 'Whether this is the default schedule',
},
availability: {
type: 'array',
description: 'Availability intervals',
items: {
type: 'object',
properties: {
days: {
type: 'array',
description: 'Days of the week',
},
startTime: {
type: 'string',
description: 'Start time in HH:MM format',
},
endTime: {
type: 'string',
description: 'End time in HH:MM format',
},
},
},
},
},
},
},
}

View File

@@ -62,6 +62,27 @@ import {
asanaUpdateTaskTool,
} from '@/tools/asana'
import { browserUseRunTaskTool } from '@/tools/browser_use'
import {
calcomCancelBookingTool,
calcomConfirmBookingTool,
calcomCreateBookingTool,
calcomCreateEventTypeTool,
calcomCreateScheduleTool,
calcomDeclineBookingTool,
calcomDeleteEventTypeTool,
calcomDeleteScheduleTool,
calcomGetBookingTool,
calcomGetDefaultScheduleTool,
calcomGetEventTypeTool,
calcomGetScheduleTool,
calcomGetSlotsTool,
calcomListBookingsTool,
calcomListEventTypesTool,
calcomListSchedulesTool,
calcomRescheduleBookingTool,
calcomUpdateEventTypeTool,
calcomUpdateScheduleTool,
} from '@/tools/calcom'
import {
calendlyCancelEventTool,
calendlyCreateWebhookTool,
@@ -1974,6 +1995,25 @@ export const tools: Record<string, ToolConfig> = {
calendly_list_webhooks: calendlyListWebhooksTool,
calendly_create_webhook: calendlyCreateWebhookTool,
calendly_delete_webhook: calendlyDeleteWebhookTool,
calcom_create_booking: calcomCreateBookingTool,
calcom_get_booking: calcomGetBookingTool,
calcom_list_bookings: calcomListBookingsTool,
calcom_cancel_booking: calcomCancelBookingTool,
calcom_reschedule_booking: calcomRescheduleBookingTool,
calcom_confirm_booking: calcomConfirmBookingTool,
calcom_decline_booking: calcomDeclineBookingTool,
calcom_create_event_type: calcomCreateEventTypeTool,
calcom_get_event_type: calcomGetEventTypeTool,
calcom_list_event_types: calcomListEventTypesTool,
calcom_update_event_type: calcomUpdateEventTypeTool,
calcom_delete_event_type: calcomDeleteEventTypeTool,
calcom_create_schedule: calcomCreateScheduleTool,
calcom_get_schedule: calcomGetScheduleTool,
calcom_list_schedules: calcomListSchedulesTool,
calcom_update_schedule: calcomUpdateScheduleTool,
calcom_delete_schedule: calcomDeleteScheduleTool,
calcom_get_default_schedule: calcomGetDefaultScheduleTool,
calcom_get_slots: calcomGetSlotsTool,
typeform_responses: typeformResponsesTool,
typeform_files: typeformFilesTool,
typeform_insights: typeformInsightsTool,

View File

@@ -0,0 +1,35 @@
import { CalComIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildCancelledOutputs,
calcomSetupInstructions,
calcomTriggerOptions,
calcomWebhookSecretField,
} from '@/triggers/calcom/utils'
import type { TriggerConfig } from '@/triggers/types'
export const calcomBookingCancelledTrigger: TriggerConfig = {
id: 'calcom_booking_cancelled',
name: 'CalCom Booking Cancelled',
provider: 'calcom',
description: 'Trigger workflow when a booking is cancelled in Cal.com',
version: '1.0.0',
icon: CalComIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'calcom_booking_cancelled',
triggerOptions: calcomTriggerOptions,
setupInstructions: calcomSetupInstructions('cancelled'),
extraFields: [calcomWebhookSecretField('calcom_booking_cancelled')],
}),
outputs: buildCancelledOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Cal-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,36 @@
import { CalComIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildBookingOutputs,
calcomSetupInstructions,
calcomTriggerOptions,
calcomWebhookSecretField,
} from '@/triggers/calcom/utils'
import type { TriggerConfig } from '@/triggers/types'
export const calcomBookingCreatedTrigger: TriggerConfig = {
id: 'calcom_booking_created',
name: 'CalCom Booking Created',
provider: 'calcom',
description: 'Trigger workflow when a new booking is created in Cal.com',
version: '1.0.0',
icon: CalComIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'calcom_booking_created',
triggerOptions: calcomTriggerOptions,
includeDropdown: true,
setupInstructions: calcomSetupInstructions('created'),
extraFields: [calcomWebhookSecretField('calcom_booking_created')],
}),
outputs: buildBookingOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Cal-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,35 @@
import { CalComIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildRescheduledOutputs,
calcomSetupInstructions,
calcomTriggerOptions,
calcomWebhookSecretField,
} from '@/triggers/calcom/utils'
import type { TriggerConfig } from '@/triggers/types'
export const calcomBookingRescheduledTrigger: TriggerConfig = {
id: 'calcom_booking_rescheduled',
name: 'CalCom Booking Rescheduled',
provider: 'calcom',
description: 'Trigger workflow when a booking is rescheduled in Cal.com',
version: '1.0.0',
icon: CalComIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'calcom_booking_rescheduled',
triggerOptions: calcomTriggerOptions,
setupInstructions: calcomSetupInstructions('rescheduled'),
extraFields: [calcomWebhookSecretField('calcom_booking_rescheduled')],
}),
outputs: buildRescheduledOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Cal-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,3 @@
export { calcomBookingCancelledTrigger } from './booking_cancelled'
export { calcomBookingCreatedTrigger } from './booking_created'
export { calcomBookingRescheduledTrigger } from './booking_rescheduled'

View File

@@ -0,0 +1,313 @@
import type { SubBlockConfig } from '@/blocks/types'
import type { TriggerOutput } from '@/triggers/types'
/**
* Shared output property definitions for Cal.com trigger payloads.
*/
/**
* Organizer output definition with destructured properties
*/
const ORGANIZER_OUTPUT: TriggerOutput = {
type: 'object',
description: 'Organizer details',
properties: {
id: { type: 'number', description: 'Organizer user ID' },
name: { type: 'string', description: 'Organizer name' },
email: { type: 'string', description: 'Organizer email' },
username: { type: 'string', description: 'Organizer username' },
timeZone: { type: 'string', description: 'Organizer timezone' },
},
}
/**
* Attendees array output definition with destructured items
*/
const ATTENDEES_TRIGGER_OUTPUT: TriggerOutput = {
type: 'array',
description: 'List of attendees',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Attendee name' },
email: { type: 'string', description: 'Attendee email' },
timeZone: { type: 'string', description: 'Attendee timezone' },
language: { type: 'string', description: 'Attendee language preference' },
},
},
}
export const calcomTriggerOptions = [
{ label: 'Booking Created', id: 'calcom_booking_created' },
{ label: 'Booking Cancelled', id: 'calcom_booking_cancelled' },
{ label: 'Booking Rescheduled', id: 'calcom_booking_rescheduled' },
]
/**
* Creates the webhook secret field subBlock for a CalCom trigger
*/
export function calcomWebhookSecretField(triggerId: string): SubBlockConfig {
return {
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Enter the same secret you configured in Cal.com',
description: 'Used to verify webhook requests via X-Cal-Signature-256 header.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: triggerId,
},
}
}
/**
* Generates setup instructions HTML for CalCom triggers
*/
export function calcomSetupInstructions(
eventType: 'created' | 'cancelled' | 'rescheduled'
): string {
const eventDescriptions = {
created: 'This webhook triggers when a new booking is created.',
cancelled: 'This webhook triggers when a booking is cancelled.',
rescheduled: 'This webhook triggers when a booking is rescheduled.',
}
const eventNames = {
created: 'BOOKING_CREATED',
cancelled: 'BOOKING_CANCELLED',
rescheduled: 'BOOKING_RESCHEDULED',
}
return [
'Copy the webhook URL above.',
'Go to your Cal.com account Settings > Developer > Webhooks.',
'Click "New Webhook" and paste the URL.',
`Select the <strong>${eventNames[eventType]}</strong> event trigger.`,
eventDescriptions[eventType],
'If you add a secret key in Cal.com, enter the same secret in the <strong>Webhook Secret</strong> field above to verify webhook authenticity.',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join('')
}
/**
* Builds common booking outputs for CalCom triggers
*/
export function buildBookingOutputs(): Record<string, TriggerOutput> {
return {
triggerEvent: {
type: 'string',
description: 'The webhook event type',
},
createdAt: {
type: 'string',
description: 'When the webhook event was created (ISO 8601)',
},
payload: {
title: {
type: 'string',
description: 'Booking title',
},
description: {
type: 'string',
description: 'Booking description',
},
eventTypeId: {
type: 'number',
description: 'Event type ID',
},
startTime: {
type: 'string',
description: 'Booking start time (ISO 8601)',
},
endTime: {
type: 'string',
description: 'Booking end time (ISO 8601)',
},
uid: {
type: 'string',
description: 'Unique booking identifier',
},
bookingId: {
type: 'number',
description: 'Numeric booking ID',
},
status: {
type: 'string',
description: 'Booking status',
},
location: {
type: 'string',
description: 'Meeting location or URL',
},
organizer: ORGANIZER_OUTPUT,
attendees: ATTENDEES_TRIGGER_OUTPUT,
responses: {
type: 'object',
description: 'Form responses from booking',
},
metadata: {
type: 'object',
description: 'Custom metadata',
},
videoCallData: {
type: 'object',
description: 'Video call details if applicable',
},
},
} as any
}
/**
* Builds outputs specific to cancelled bookings
*/
export function buildCancelledOutputs(): Record<string, TriggerOutput> {
return {
triggerEvent: {
type: 'string',
description: 'The webhook event type',
},
createdAt: {
type: 'string',
description: 'When the webhook event was created (ISO 8601)',
},
payload: {
title: {
type: 'string',
description: 'Booking title',
},
description: {
type: 'string',
description: 'Booking description',
},
eventTypeId: {
type: 'number',
description: 'Event type ID',
},
startTime: {
type: 'string',
description: 'Booking start time (ISO 8601)',
},
endTime: {
type: 'string',
description: 'Booking end time (ISO 8601)',
},
uid: {
type: 'string',
description: 'Unique booking identifier',
},
bookingId: {
type: 'number',
description: 'Numeric booking ID',
},
status: {
type: 'string',
description: 'Booking status',
},
location: {
type: 'string',
description: 'Meeting location or URL',
},
cancellationReason: {
type: 'string',
description: 'Reason for cancellation',
},
organizer: ORGANIZER_OUTPUT,
attendees: ATTENDEES_TRIGGER_OUTPUT,
responses: {
type: 'object',
description: 'Form responses from booking',
},
metadata: {
type: 'object',
description: 'Custom metadata',
},
},
} as any
}
/**
* Builds outputs specific to rescheduled bookings
*/
export function buildRescheduledOutputs(): Record<string, TriggerOutput> {
return {
triggerEvent: {
type: 'string',
description: 'The webhook event type',
},
createdAt: {
type: 'string',
description: 'When the webhook event was created (ISO 8601)',
},
payload: {
title: {
type: 'string',
description: 'Booking title',
},
description: {
type: 'string',
description: 'Booking description',
},
eventTypeId: {
type: 'number',
description: 'Event type ID',
},
startTime: {
type: 'string',
description: 'New booking start time (ISO 8601)',
},
endTime: {
type: 'string',
description: 'New booking end time (ISO 8601)',
},
uid: {
type: 'string',
description: 'Unique booking identifier',
},
bookingId: {
type: 'number',
description: 'Numeric booking ID',
},
status: {
type: 'string',
description: 'Booking status',
},
location: {
type: 'string',
description: 'Meeting location or URL',
},
rescheduleId: {
type: 'number',
description: 'Previous booking ID',
},
rescheduleUid: {
type: 'string',
description: 'Previous booking UID',
},
rescheduleStartTime: {
type: 'string',
description: 'Original start time (ISO 8601)',
},
rescheduleEndTime: {
type: 'string',
description: 'Original end time (ISO 8601)',
},
organizer: ORGANIZER_OUTPUT,
attendees: ATTENDEES_TRIGGER_OUTPUT,
responses: {
type: 'object',
description: 'Form responses from booking',
},
metadata: {
type: 'object',
description: 'Custom metadata',
},
},
} as any
}

View File

@@ -4,8 +4,68 @@ import { TRIGGER_REGISTRY } from '@/triggers/registry'
import type { TriggerConfig } from '@/triggers/types'
/**
* Gets a trigger config and injects samplePayload subblock with condition
* The condition assumes the trigger will be used in a multi-trigger block
* IDs that should NOT be namespaced because they are shared across triggers
* or are the control mechanism for trigger selection
*/
const SHARED_SUBBLOCK_IDS = new Set(['selectedTriggerId'])
/**
* Checks if a subBlock is display-only (not user-editable).
* Display-only subBlocks should be namespaced to avoid conflicts when
* multiple triggers show different content for the same conceptual field.
*/
function isDisplayOnlySubBlock(subBlock: SubBlockConfig): boolean {
// Text type is always display-only
if (subBlock.type === 'text') {
return true
}
// ReadOnly inputs are display-only
if (subBlock.readOnly === true) {
return true
}
return false
}
/**
* Namespaces a subBlock ID with the trigger ID to avoid conflicts when
* multiple triggers are merged into a single block.
*
* Only namespaces display-only subBlocks (readOnly or text type) that have
* a condition on selectedTriggerId. User-input fields are NOT namespaced
* so their values persist when switching between triggers.
*/
function namespaceSubBlockId(subBlock: SubBlockConfig, triggerId: string): SubBlockConfig {
// Don't namespace shared IDs
if (SHARED_SUBBLOCK_IDS.has(subBlock.id)) {
return subBlock
}
// Only namespace display-only subBlocks to avoid content conflicts
// User-input fields should remain shared so values persist across trigger switches
if (!isDisplayOnlySubBlock(subBlock)) {
return subBlock
}
// Only namespace if the subBlock has a condition on selectedTriggerId
// These are the ones that are trigger-specific and will conflict when merged
const condition =
typeof subBlock.condition === 'function' ? subBlock.condition() : subBlock.condition
if (condition?.field === 'selectedTriggerId') {
return {
...subBlock,
id: `${subBlock.id}_${triggerId}`,
}
}
return subBlock
}
/**
* Gets a trigger config and injects samplePayload subblock with condition.
* Also namespaces subBlock IDs to avoid conflicts when multiple triggers
* are merged into a single block (e.g., ...getTrigger('a').subBlocks, ...getTrigger('b').subBlocks).
*/
export function getTrigger(triggerId: string): TriggerConfig {
const trigger = TRIGGER_REGISTRY[triggerId]
@@ -13,21 +73,25 @@ export function getTrigger(triggerId: string): TriggerConfig {
throw new Error(`Trigger not found: ${triggerId}`)
}
const clonedTrigger = { ...trigger, subBlocks: [...trigger.subBlocks] }
clonedTrigger.subBlocks = clonedTrigger.subBlocks.filter(
(subBlock) => subBlock.id !== 'triggerSave' && subBlock.type !== 'trigger-save'
)
// Clone and filter out deprecated trigger-save subblocks
const subBlocks = trigger.subBlocks
.filter((subBlock) => subBlock.id !== 'triggerSave' && subBlock.type !== 'trigger-save')
.map((subBlock) => namespaceSubBlockId(subBlock, triggerId))
const clonedTrigger = { ...trigger, subBlocks }
// Inject samplePayload for webhooks/pollers with condition
if (trigger.webhook || trigger.id.includes('webhook') || trigger.id.includes('poller')) {
const samplePayloadExists = clonedTrigger.subBlocks.some((sb) => sb.id === 'samplePayload')
const samplePayloadExists = clonedTrigger.subBlocks.some(
(sb) => sb.id === 'samplePayload' || sb.id === `samplePayload_${triggerId}`
)
if (!samplePayloadExists && trigger.outputs) {
const mockPayload = generateMockPayloadFromOutputsDefinition(trigger.outputs)
const generatedPayload = JSON.stringify(mockPayload, null, 2)
const samplePayloadSubBlock: SubBlockConfig = {
id: 'samplePayload',
id: `samplePayload_${triggerId}`,
title: 'Event Payload Example',
type: 'code',
language: 'json',
@@ -140,6 +204,7 @@ export function buildTriggerSubBlocks(options: BuildTriggerSubBlocksOptions): Su
}
// Webhook URL display (common to all triggers)
// ID will be namespaced by getTrigger() when merged into blocks
blocks.push({
id: 'webhookUrlDisplay',
title: 'Webhook URL',
@@ -157,8 +222,8 @@ export function buildTriggerSubBlocks(options: BuildTriggerSubBlocksOptions): Su
blocks.push(...extraFields)
}
// Save button
// Setup instructions
// ID will be namespaced by getTrigger() when merged into blocks
blocks.push({
id: 'triggerInstructions',
title: 'Setup Instructions',

View File

@@ -1,4 +1,9 @@
import { airtableWebhookTrigger } from '@/triggers/airtable'
import {
calcomBookingCancelledTrigger,
calcomBookingCreatedTrigger,
calcomBookingRescheduledTrigger,
} from '@/triggers/calcom'
import {
calendlyInviteeCanceledTrigger,
calendlyInviteeCreatedTrigger,
@@ -120,6 +125,9 @@ export const TRIGGER_REGISTRY: TriggerRegistry = {
calendly_invitee_created: calendlyInviteeCreatedTrigger,
calendly_invitee_canceled: calendlyInviteeCanceledTrigger,
calendly_routing_form_submitted: calendlyRoutingFormSubmittedTrigger,
calcom_booking_created: calcomBookingCreatedTrigger,
calcom_booking_cancelled: calcomBookingCancelledTrigger,
calcom_booking_rescheduled: calcomBookingRescheduledTrigger,
generic_webhook: genericWebhookTrigger,
github_webhook: githubWebhookTrigger,
github_issue_opened: githubIssueOpenedTrigger,