diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index f9b47f43c..60ad1d6fe 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -5113,3 +5113,42 @@ export function PulseIcon(props: SVGProps) { ) } + +export function CalComIcon(props: SVGProps) { + return ( + + + + + + + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 046410d29..97c0e93d4 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -13,6 +13,7 @@ import { AsanaIcon, BrainIcon, BrowserUseIcon, + CalComIcon, CalendlyIcon, CirclebackIcon, ClayIcon, @@ -141,6 +142,7 @@ export const blockTypeToIconMap: Record = { arxiv: ArxivIcon, asana: AsanaIcon, browser_use: BrowserUseIcon, + calcom: CalComIcon, calendly: CalendlyIcon, circleback: CirclebackIcon, clay: ClayIcon, diff --git a/apps/docs/content/docs/en/tools/calcom.mdx b/apps/docs/content/docs/en/tools/calcom.mdx new file mode 100644 index 000000000..0667e6257 --- /dev/null +++ b/apps/docs/content/docs/en/tools/calcom.mdx @@ -0,0 +1,515 @@ +--- +title: CalCom +description: Manage Cal.com bookings, event types, schedules, and availability +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* 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\) | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 0c5635a23..d71daf186 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -9,6 +9,7 @@ "arxiv", "asana", "browser_use", + "calcom", "calendly", "circleback", "clay", diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx index 60e74716f..606f120a7 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx @@ -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('') 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({ <> } - 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({
@@ -404,27 +383,6 @@ export const ShortInput = memo(function ShortInput({ }} - {/* Copy Button */} - {showCopyButton && value && ( -
- -
- )} - {/* Wand Button - only show if not hidden by parent */} {isWandEnabled && !isPreview && !wandHook.isStreaming && !hideInternalWand && (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx index d3bf23e40..a55721238 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx @@ -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 = ( )}
+ {showCopy && ( + + + + + +

{copyState.copied ? 'Copied!' : 'Copy'}

+
+
+ )} {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(null) const wandControlRef = useRef(null) + // Use webhook management hook when config has useWebhookUrl enabled + const webhookManagement = useWebhookManagement({ + blockId, + triggerId: undefined, + isPreview, + useWebhookUrl: config.useWebhookUrl, + }) + const handleMouseDown = (e: MouseEvent): 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()}
diff --git a/apps/sim/blocks/blocks/calcom.ts b/apps/sim/blocks/blocks/calcom.ts new file mode 100644 index 000000000..7a3d42f19 --- /dev/null +++ b/apps/sim/blocks/blocks/calcom.ts @@ -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 = { + 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 = { ...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'], + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 202628998..9e1d21668 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -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 = { arxiv: ArxivBlock, asana: AsanaBlock, browser_use: BrowserUseBlock, + calcom: CalComBlock, calendly: CalendlyBlock, chat_trigger: ChatTriggerBlock, circleback: CirclebackBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index f9b47f43c..60ad1d6fe 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -5113,3 +5113,42 @@ export function PulseIcon(props: SVGProps) { ) } + +export function CalComIcon(props: SVGProps) { + return ( + + + + + + + + + + ) +} diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 0a70d7903..a35e24b7c 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -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 diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index f4ba4f7ef..113b6d521 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -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 diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index c8cff3dde..eed4f43a8 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -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 = { }, 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, diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 055568881..961c7a0b1 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -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 diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 709fa05d2..8a029159c 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -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 diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index 368b36f9f..04d66f696 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -2537,3 +2537,49 @@ export function convertSquareBracketsToTwiML(twiml: string | undefined): string // Replace [Tag] with and [/Tag] with 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 + } +} diff --git a/apps/sim/tools/calcom/cancel_booking.ts b/apps/sim/tools/calcom/cancel_booking.ts new file mode 100644 index 000000000..7d7514104 --- /dev/null +++ b/apps/sim/tools/calcom/cancel_booking.ts @@ -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 = + { + 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 = {} + + 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, + }, + }, + }, + } diff --git a/apps/sim/tools/calcom/confirm_booking.ts b/apps/sim/tools/calcom/confirm_booking.ts new file mode 100644 index 000000000..a35c85e69 --- /dev/null +++ b/apps/sim/tools/calcom/confirm_booking.ts @@ -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, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/create_booking.ts b/apps/sim/tools/calcom/create_booking.ts new file mode 100644 index 000000000..7ddf9cd43 --- /dev/null +++ b/apps/sim/tools/calcom/create_booking.ts @@ -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 = + { + 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 = { + 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, + }, + }, + }, + } diff --git a/apps/sim/tools/calcom/create_event_type.ts b/apps/sim/tools/calcom/create_event_type.ts new file mode 100644 index 000000000..8b5c98164 --- /dev/null +++ b/apps/sim/tools/calcom/create_event_type.ts @@ -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 = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/create_schedule.ts b/apps/sim/tools/calcom/create_schedule.ts new file mode 100644 index 000000000..9f94abc91 --- /dev/null +++ b/apps/sim/tools/calcom/create_schedule.ts @@ -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', + }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/decline_booking.ts b/apps/sim/tools/calcom/decline_booking.ts new file mode 100644 index 000000000..fa4ad691a --- /dev/null +++ b/apps/sim/tools/calcom/decline_booking.ts @@ -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 = {} + + 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, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/delete_event_type.ts b/apps/sim/tools/calcom/delete_event_type.ts new file mode 100644 index 000000000..f504a81d9 --- /dev/null +++ b/apps/sim/tools/calcom/delete_event_type.ts @@ -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', + }, + }, +} diff --git a/apps/sim/tools/calcom/delete_schedule.ts b/apps/sim/tools/calcom/delete_schedule.ts new file mode 100644 index 000000000..5a2a644a6 --- /dev/null +++ b/apps/sim/tools/calcom/delete_schedule.ts @@ -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', + }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/get_booking.ts b/apps/sim/tools/calcom/get_booking.ts new file mode 100644 index 000000000..3060643e0 --- /dev/null +++ b/apps/sim/tools/calcom/get_booking.ts @@ -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 = { + 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, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/get_default_schedule.ts b/apps/sim/tools/calcom/get_default_schedule.ts new file mode 100644 index 000000000..48970b93b --- /dev/null +++ b/apps/sim/tools/calcom/get_default_schedule.ts @@ -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', + }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/get_event_type.ts b/apps/sim/tools/calcom/get_event_type.ts new file mode 100644 index 000000000..6ab1a5d0d --- /dev/null +++ b/apps/sim/tools/calcom/get_event_type.ts @@ -0,0 +1,75 @@ +import type { CalcomGetEventTypeParams, CalcomGetEventTypeResponse } from '@/tools/calcom/types' +import type { ToolConfig } from '@/tools/types' + +export const getEventTypeTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/get_schedule.ts b/apps/sim/tools/calcom/get_schedule.ts new file mode 100644 index 000000000..9186f73ea --- /dev/null +++ b/apps/sim/tools/calcom/get_schedule.ts @@ -0,0 +1,99 @@ +import type { CalcomGetScheduleParams, CalcomGetScheduleResponse } from '@/tools/calcom/types' +import type { ToolConfig } from '@/tools/types' + +export const getScheduleTool: ToolConfig = { + 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', + }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/get_slots.ts b/apps/sim/tools/calcom/get_slots.ts new file mode 100644 index 000000000..15bda53b6 --- /dev/null +++ b/apps/sim/tools/calcom/get_slots.ts @@ -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 = { + 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', + }, + }, + }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/index.ts b/apps/sim/tools/calcom/index.ts new file mode 100644 index 000000000..2307da64e --- /dev/null +++ b/apps/sim/tools/calcom/index.ts @@ -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 diff --git a/apps/sim/tools/calcom/list_bookings.ts b/apps/sim/tools/calcom/list_bookings.ts new file mode 100644 index 000000000..2d07d8c5a --- /dev/null +++ b/apps/sim/tools/calcom/list_bookings.ts @@ -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 = { + 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, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/list_event_types.ts b/apps/sim/tools/calcom/list_event_types.ts new file mode 100644 index 000000000..3a8507ceb --- /dev/null +++ b/apps/sim/tools/calcom/list_event_types.ts @@ -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' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/list_schedules.ts b/apps/sim/tools/calcom/list_schedules.ts new file mode 100644 index 000000000..d2eb214f7 --- /dev/null +++ b/apps/sim/tools/calcom/list_schedules.ts @@ -0,0 +1,96 @@ +import type { CalcomListSchedulesParams, CalcomListSchedulesResponse } from '@/tools/calcom/types' +import type { ToolConfig } from '@/tools/types' + +export const listSchedulesTool: ToolConfig = + { + 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', + }, + }, + }, + }, + }, + }, + }, + }, + } diff --git a/apps/sim/tools/calcom/reschedule_booking.ts b/apps/sim/tools/calcom/reschedule_booking.ts new file mode 100644 index 000000000..d926221a6 --- /dev/null +++ b/apps/sim/tools/calcom/reschedule_booking.ts @@ -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 = { + 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, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/types.ts b/apps/sim/tools/calcom/types.ts new file mode 100644 index 000000000..0963e43ce --- /dev/null +++ b/apps/sim/tools/calcom/types.ts @@ -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 + +/** + * 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 + +/** + * 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 + +/** + * 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 + +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 + rating?: number + attendees: Array<{ + name: string + email: string + timeZone: string + phoneNumber?: string + language: string + absent: boolean + }> + guests?: string[] + bookingFieldsResponses?: Record +} + +/** + * Create booking params + */ +export interface CalcomCreateBookingParams { + accessToken: string + eventTypeId: number + start: string + attendee: CalcomAttendee + guests?: string[] + lengthInMinutes?: number + metadata?: Record +} + +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 + } +} diff --git a/apps/sim/tools/calcom/update_event_type.ts b/apps/sim/tools/calcom/update_event_type.ts new file mode 100644 index 000000000..d872ac821 --- /dev/null +++ b/apps/sim/tools/calcom/update_event_type.ts @@ -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 = {} + + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/calcom/update_schedule.ts b/apps/sim/tools/calcom/update_schedule.ts new file mode 100644 index 000000000..83e4c9f54 --- /dev/null +++ b/apps/sim/tools/calcom/update_schedule.ts @@ -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', + }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 29b0f5ae2..6f2120973 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -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 = { 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, diff --git a/apps/sim/triggers/calcom/booking_cancelled.ts b/apps/sim/triggers/calcom/booking_cancelled.ts new file mode 100644 index 000000000..4df2c8ffc --- /dev/null +++ b/apps/sim/triggers/calcom/booking_cancelled.ts @@ -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=...', + }, + }, +} diff --git a/apps/sim/triggers/calcom/booking_created.ts b/apps/sim/triggers/calcom/booking_created.ts new file mode 100644 index 000000000..0a91ffd56 --- /dev/null +++ b/apps/sim/triggers/calcom/booking_created.ts @@ -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=...', + }, + }, +} diff --git a/apps/sim/triggers/calcom/booking_rescheduled.ts b/apps/sim/triggers/calcom/booking_rescheduled.ts new file mode 100644 index 000000000..4d9c89218 --- /dev/null +++ b/apps/sim/triggers/calcom/booking_rescheduled.ts @@ -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=...', + }, + }, +} diff --git a/apps/sim/triggers/calcom/index.ts b/apps/sim/triggers/calcom/index.ts new file mode 100644 index 000000000..1241393aa --- /dev/null +++ b/apps/sim/triggers/calcom/index.ts @@ -0,0 +1,3 @@ +export { calcomBookingCancelledTrigger } from './booking_cancelled' +export { calcomBookingCreatedTrigger } from './booking_created' +export { calcomBookingRescheduledTrigger } from './booking_rescheduled' diff --git a/apps/sim/triggers/calcom/utils.ts b/apps/sim/triggers/calcom/utils.ts new file mode 100644 index 000000000..55be4b497 --- /dev/null +++ b/apps/sim/triggers/calcom/utils.ts @@ -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 ${eventNames[eventType]} event trigger.`, + eventDescriptions[eventType], + 'If you add a secret key in Cal.com, enter the same secret in the Webhook Secret field above to verify webhook authenticity.', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join('') +} + +/** + * Builds common booking outputs for CalCom triggers + */ +export function buildBookingOutputs(): Record { + 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 { + 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 { + 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 +} diff --git a/apps/sim/triggers/index.ts b/apps/sim/triggers/index.ts index d00cbf175..f99511c7d 100644 --- a/apps/sim/triggers/index.ts +++ b/apps/sim/triggers/index.ts @@ -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', diff --git a/apps/sim/triggers/registry.ts b/apps/sim/triggers/registry.ts index 1851d70d9..3a8baad68 100644 --- a/apps/sim/triggers/registry.ts +++ b/apps/sim/triggers/registry.ts @@ -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,