diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 9da5a57508..62ff50e936 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -3956,6 +3956,17 @@ export function IntercomIcon(props: SVGProps) { ) } +export function LumaIcon(props: SVGProps) { + return ( + + + + ) +} + export function MailchimpIcon(props: SVGProps) { return ( = { linear: LinearIcon, linkedin: LinkedInIcon, linkup: LinkupIcon, + luma: LumaIcon, mailchimp: MailchimpIcon, mailgun: MailgunIcon, mem0: Mem0Icon, diff --git a/apps/docs/content/docs/en/tools/luma.mdx b/apps/docs/content/docs/en/tools/luma.mdx new file mode 100644 index 0000000000..e1582de494 --- /dev/null +++ b/apps/docs/content/docs/en/tools/luma.mdx @@ -0,0 +1,272 @@ +--- +title: Luma +description: Manage events and guests on Luma +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Luma](https://lu.ma/) is an event management platform that makes it easy to create, manage, and share events with your community. + +With Luma integrated into Sim, your agents can: + +- **Create events**: Set up new events with name, time, timezone, description, and visibility settings. +- **Update events**: Modify existing event details like name, time, description, and visibility. +- **Get event details**: Retrieve full details for any event by its ID. +- **List calendar events**: Browse your calendar's events with date range filtering and pagination. +- **Manage guest lists**: View attendees for an event, filtered by approval status. +- **Add guests**: Invite new guests to events programmatically. + +By connecting Sim with Luma, you can automate event operations within your agent workflows. Automatically create events based on triggers, sync guest lists, monitor registrations, and manage your event calendar—all handled directly by your agents via the Luma API. + +Whether you're running community meetups, conferences, or internal team events, the Luma tool makes it easy to coordinate event management within your Sim workflows. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Luma into the workflow. Can create events, update events, get event details, list calendar events, get guest lists, and add guests to events. + + + +## Tools + +### `luma_get_event` + +Retrieve details of a Luma event including name, time, location, hosts, and visibility settings. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Luma API key | +| `eventId` | string | Yes | Event ID \(starts with evt-\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `event` | object | Event details | +| ↳ `id` | string | Event ID | +| ↳ `name` | string | Event name | +| ↳ `startAt` | string | Event start time \(ISO 8601\) | +| ↳ `endAt` | string | Event end time \(ISO 8601\) | +| ↳ `timezone` | string | Event timezone \(IANA\) | +| ↳ `durationInterval` | string | Event duration \(ISO 8601 interval, e.g. PT2H\) | +| ↳ `createdAt` | string | Event creation timestamp \(ISO 8601\) | +| ↳ `description` | string | Event description \(plain text\) | +| ↳ `descriptionMd` | string | Event description \(Markdown\) | +| ↳ `coverUrl` | string | Event cover image URL | +| ↳ `url` | string | Event page URL on lu.ma | +| ↳ `visibility` | string | Event visibility \(public, members-only, private\) | +| ↳ `meetingUrl` | string | Virtual meeting URL | +| ↳ `geoAddressJson` | json | Structured location/address data | +| ↳ `geoLatitude` | string | Venue latitude coordinate | +| ↳ `geoLongitude` | string | Venue longitude coordinate | +| ↳ `calendarId` | string | Associated calendar ID | +| `hosts` | array | Event hosts | +| ↳ `name` | string | Host name | +| ↳ `email` | string | Host email address | + +### `luma_create_event` + +Create a new event on Luma with a name, start time, timezone, and optional details like description, location, and visibility. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Luma API key | +| `name` | string | Yes | Event name/title | +| `startAt` | string | Yes | Event start time in ISO 8601 format \(e.g., 2025-03-15T18:00:00Z\) | +| `timezone` | string | Yes | IANA timezone \(e.g., America/New_York, Europe/London\) | +| `endAt` | string | No | Event end time in ISO 8601 format \(e.g., 2025-03-15T20:00:00Z\) | +| `durationInterval` | string | No | Event duration as ISO 8601 interval \(e.g., PT2H for 2 hours, PT30M for 30 minutes\). Used if endAt is not provided. | +| `descriptionMd` | string | No | Event description in Markdown format | +| `meetingUrl` | string | No | Virtual meeting URL for online events \(e.g., Zoom, Google Meet link\) | +| `visibility` | string | No | Event visibility: public, members-only, or private \(defaults to public\) | +| `coverUrl` | string | No | Cover image URL \(must be a Luma CDN URL from images.lumacdn.com\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `event` | object | Created event details | +| ↳ `id` | string | Event ID | +| ↳ `name` | string | Event name | +| ↳ `startAt` | string | Event start time \(ISO 8601\) | +| ↳ `endAt` | string | Event end time \(ISO 8601\) | +| ↳ `timezone` | string | Event timezone \(IANA\) | +| ↳ `durationInterval` | string | Event duration \(ISO 8601 interval, e.g. PT2H\) | +| ↳ `createdAt` | string | Event creation timestamp \(ISO 8601\) | +| ↳ `description` | string | Event description \(plain text\) | +| ↳ `descriptionMd` | string | Event description \(Markdown\) | +| ↳ `coverUrl` | string | Event cover image URL | +| ↳ `url` | string | Event page URL on lu.ma | +| ↳ `visibility` | string | Event visibility \(public, members-only, private\) | +| ↳ `meetingUrl` | string | Virtual meeting URL | +| ↳ `geoAddressJson` | json | Structured location/address data | +| ↳ `geoLatitude` | string | Venue latitude coordinate | +| ↳ `geoLongitude` | string | Venue longitude coordinate | +| ↳ `calendarId` | string | Associated calendar ID | +| `hosts` | array | Event hosts | +| ↳ `name` | string | Host name | +| ↳ `email` | string | Host email address | + +### `luma_update_event` + +Update an existing Luma event. Only the fields you provide will be changed; all other fields remain unchanged. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Luma API key | +| `eventId` | string | Yes | Event ID to update \(starts with evt-\) | +| `name` | string | No | New event name/title | +| `startAt` | string | No | New start time in ISO 8601 format \(e.g., 2025-03-15T18:00:00Z\) | +| `timezone` | string | No | New IANA timezone \(e.g., America/New_York, Europe/London\) | +| `endAt` | string | No | New end time in ISO 8601 format \(e.g., 2025-03-15T20:00:00Z\) | +| `durationInterval` | string | No | New duration as ISO 8601 interval \(e.g., PT2H for 2 hours\). Used if endAt is not provided. | +| `descriptionMd` | string | No | New event description in Markdown format | +| `meetingUrl` | string | No | New virtual meeting URL \(e.g., Zoom, Google Meet link\) | +| `visibility` | string | No | New visibility: public, members-only, or private | +| `coverUrl` | string | No | New cover image URL \(must be a Luma CDN URL from images.lumacdn.com\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `event` | object | Updated event details | +| ↳ `id` | string | Event ID | +| ↳ `name` | string | Event name | +| ↳ `startAt` | string | Event start time \(ISO 8601\) | +| ↳ `endAt` | string | Event end time \(ISO 8601\) | +| ↳ `timezone` | string | Event timezone \(IANA\) | +| ↳ `durationInterval` | string | Event duration \(ISO 8601 interval, e.g. PT2H\) | +| ↳ `createdAt` | string | Event creation timestamp \(ISO 8601\) | +| ↳ `description` | string | Event description \(plain text\) | +| ↳ `descriptionMd` | string | Event description \(Markdown\) | +| ↳ `coverUrl` | string | Event cover image URL | +| ↳ `url` | string | Event page URL on lu.ma | +| ↳ `visibility` | string | Event visibility \(public, members-only, private\) | +| ↳ `meetingUrl` | string | Virtual meeting URL | +| ↳ `geoAddressJson` | json | Structured location/address data | +| ↳ `geoLatitude` | string | Venue latitude coordinate | +| ↳ `geoLongitude` | string | Venue longitude coordinate | +| ↳ `calendarId` | string | Associated calendar ID | +| `hosts` | array | Event hosts | +| ↳ `name` | string | Host name | +| ↳ `email` | string | Host email address | + +### `luma_list_events` + +List events from your Luma calendar with optional date range filtering, sorting, and pagination. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Luma API key | +| `after` | string | No | Return events after this ISO 8601 datetime \(e.g., 2025-01-01T00:00:00Z\) | +| `before` | string | No | Return events before this ISO 8601 datetime \(e.g., 2025-12-31T23:59:59Z\) | +| `paginationLimit` | number | No | Maximum number of events to return per page | +| `paginationCursor` | string | No | Pagination cursor from a previous response \(next_cursor\) to fetch the next page of results | +| `sortColumn` | string | No | Column to sort by \(e.g., start_at\) | +| `sortDirection` | string | No | Sort direction: asc, desc, asc nulls last, or desc nulls last | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `events` | array | List of calendar events | +| ↳ `id` | string | Event ID | +| ↳ `name` | string | Event name | +| ↳ `startAt` | string | Event start time \(ISO 8601\) | +| ↳ `endAt` | string | Event end time \(ISO 8601\) | +| ↳ `timezone` | string | Event timezone \(IANA\) | +| ↳ `durationInterval` | string | Event duration \(ISO 8601 interval, e.g. PT2H\) | +| ↳ `createdAt` | string | Event creation timestamp \(ISO 8601\) | +| ↳ `description` | string | Event description \(plain text\) | +| ↳ `descriptionMd` | string | Event description \(Markdown\) | +| ↳ `coverUrl` | string | Event cover image URL | +| ↳ `url` | string | Event page URL on lu.ma | +| ↳ `visibility` | string | Event visibility \(public, members-only, private\) | +| ↳ `meetingUrl` | string | Virtual meeting URL | +| ↳ `geoAddressJson` | json | Structured location/address data | +| ↳ `geoLatitude` | string | Venue latitude coordinate | +| ↳ `geoLongitude` | string | Venue longitude coordinate | +| ↳ `calendarId` | string | Associated calendar ID | +| `hasMore` | boolean | Whether more results are available for pagination | +| `nextCursor` | string | Cursor to pass as paginationCursor to fetch the next page | + +### `luma_get_guests` + +Retrieve the guest list for a Luma event with optional filtering by approval status, sorting, and pagination. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Luma API key | +| `eventId` | string | Yes | Event ID \(starts with evt-\) | +| `approvalStatus` | string | No | Filter by approval status: approved, session, pending_approval, invited, declined, or waitlist | +| `paginationLimit` | number | No | Maximum number of guests to return per page | +| `paginationCursor` | string | No | Pagination cursor from a previous response \(next_cursor\) to fetch the next page of results | +| `sortColumn` | string | No | Column to sort by: name, email, created_at, registered_at, or checked_in_at | +| `sortDirection` | string | No | Sort direction: asc, desc, asc nulls last, or desc nulls last | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `guests` | array | List of event guests | +| ↳ `id` | string | Guest ID | +| ↳ `email` | string | Guest email address | +| ↳ `name` | string | Guest full name | +| ↳ `firstName` | string | Guest first name | +| ↳ `lastName` | string | Guest last name | +| ↳ `approvalStatus` | string | Guest approval status \(approved, session, pending_approval, invited, declined, waitlist\) | +| ↳ `registeredAt` | string | Registration timestamp \(ISO 8601\) | +| ↳ `invitedAt` | string | Invitation timestamp \(ISO 8601\) | +| ↳ `joinedAt` | string | Join timestamp \(ISO 8601\) | +| ↳ `checkedInAt` | string | Check-in timestamp \(ISO 8601\) | +| ↳ `phoneNumber` | string | Guest phone number | +| `hasMore` | boolean | Whether more results are available for pagination | +| `nextCursor` | string | Cursor to pass as paginationCursor to fetch the next page | + +### `luma_add_guests` + +Add guests to a Luma event by email. Guests are added with Going (approved) status and receive one ticket of the default ticket type. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Luma API key | +| `eventId` | string | Yes | Event ID \(starts with evt-\) | +| `guests` | string | Yes | JSON array of guest objects. Each guest requires an "email" field and optionally "name", "first_name", "last_name". Example: \[\{"email": "user@example.com", "name": "John Doe"\}\] | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `guests` | array | List of added guests with their assigned status and ticket info | +| ↳ `id` | string | Guest ID | +| ↳ `email` | string | Guest email address | +| ↳ `name` | string | Guest full name | +| ↳ `firstName` | string | Guest first name | +| ↳ `lastName` | string | Guest last name | +| ↳ `approvalStatus` | string | Guest approval status \(approved, session, pending_approval, invited, declined, waitlist\) | +| ↳ `registeredAt` | string | Registration timestamp \(ISO 8601\) | +| ↳ `invitedAt` | string | Invitation timestamp \(ISO 8601\) | +| ↳ `joinedAt` | string | Join timestamp \(ISO 8601\) | +| ↳ `checkedInAt` | string | Check-in timestamp \(ISO 8601\) | +| ↳ `phoneNumber` | string | Guest phone number | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index ec0851ee59..725805c1cd 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -74,6 +74,7 @@ "linear", "linkedin", "linkup", + "luma", "mailchimp", "mailgun", "mem0", diff --git a/apps/sim/blocks/blocks/luma.ts b/apps/sim/blocks/blocks/luma.ts new file mode 100644 index 0000000000..f4d078548f --- /dev/null +++ b/apps/sim/blocks/blocks/luma.ts @@ -0,0 +1,380 @@ +import { LumaIcon } from '@/components/icons' +import { AuthMode, type BlockConfig } from '@/blocks/types' + +export const LumaBlock: BlockConfig = { + type: 'luma', + name: 'Luma', + description: 'Manage events and guests on Luma', + longDescription: + 'Integrate Luma into the workflow. Can create events, update events, get event details, list calendar events, get guest lists, and add guests to events.', + docsLink: 'https://docs.sim.ai/tools/luma', + category: 'tools', + bgColor: '#FF5C35', + icon: LumaIcon, + authMode: AuthMode.ApiKey, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Get Event', id: 'get_event' }, + { label: 'Create Event', id: 'create_event' }, + { label: 'Update Event', id: 'update_event' }, + { label: 'List Events', id: 'list_events' }, + { label: 'Get Guests', id: 'get_guests' }, + { label: 'Add Guests', id: 'add_guests' }, + ], + value: () => 'get_event', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your Luma API key', + password: true, + required: true, + }, + + // Event ID: used by get_event, update_event, get_guests, add_guests + { + id: 'eventId', + title: 'Event ID', + type: 'short-input', + placeholder: 'evt-...', + required: { + field: 'operation', + value: ['get_event', 'update_event', 'get_guests', 'add_guests'], + }, + condition: { + field: 'operation', + value: ['get_event', 'update_event', 'get_guests', 'add_guests'], + }, + }, + + // Event Name: required for create, optional for update + { + id: 'name', + title: 'Event Name', + type: 'short-input', + placeholder: 'My Event', + required: { field: 'operation', value: 'create_event' }, + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + }, + + // Start Time: required for create, optional for update + { + id: 'startAt', + title: 'Start Time', + type: 'short-input', + placeholder: '2025-03-15T18:00:00Z', + required: { field: 'operation', value: 'create_event' }, + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp based on the user's description. + +Examples: +- "tomorrow at 6pm EST" -> 2025-03-16T18:00:00-05:00 +- "next Friday at noon" -> appropriate ISO 8601 date +- "March 20th 2025 at 3pm UTC" -> 2025-03-20T15:00:00Z +- "in 2 weeks at 10am" -> appropriate ISO 8601 date + +Return ONLY the ISO 8601 timestamp - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the start time (e.g., "next Friday at 6pm EST")...', + generationType: 'timestamp', + }, + }, + + // Timezone: required for create, optional for update + { + id: 'timezone', + title: 'Timezone', + type: 'short-input', + placeholder: 'America/New_York', + required: { field: 'operation', value: 'create_event' }, + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + wandConfig: { + enabled: true, + prompt: `Generate an IANA timezone identifier based on the user's description. + +Examples: +- "eastern time" -> America/New_York +- "pacific" -> America/Los_Angeles +- "london" -> Europe/London +- "tokyo" -> Asia/Tokyo +- "central european" -> Europe/Berlin +- "india" -> Asia/Kolkata +- "UTC" -> UTC + +Return ONLY the IANA timezone string - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the timezone (e.g., "eastern time", "london")...', + }, + }, + + // End Time + { + id: 'endAt', + title: 'End Time', + type: 'short-input', + placeholder: '2025-03-15T20:00:00Z', + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp for the event end time based on the user's description. + +Examples: +- "2 hours after start" -> appropriate ISO 8601 date +- "8pm" -> appropriate ISO 8601 date with 20:00:00 +- "March 20th at 5pm UTC" -> 2025-03-20T17:00:00Z + +Return ONLY the ISO 8601 timestamp - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the end time (e.g., "2 hours after start", "8pm")...', + generationType: 'timestamp', + }, + }, + + // Duration + { + id: 'durationInterval', + title: 'Duration', + type: 'short-input', + placeholder: 'PT2H (2 hours)', + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 duration interval based on the user's description. + +Examples: +- "2 hours" -> PT2H +- "30 minutes" -> PT30M +- "1 hour 30 minutes" -> PT1H30M +- "3 hours" -> PT3H +- "45 minutes" -> PT45M +- "1 day" -> P1D + +Return ONLY the ISO 8601 duration - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the duration (e.g., "2 hours", "90 minutes")...', + }, + }, + + // Description + { + id: 'descriptionMd', + title: 'Description', + type: 'long-input', + placeholder: 'Event description (Markdown supported)', + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + }, + + // Meeting URL + { + id: 'meetingUrl', + title: 'Meeting URL', + type: 'short-input', + placeholder: 'https://zoom.us/j/...', + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + mode: 'advanced', + }, + + // Visibility + { + id: 'visibility', + title: 'Visibility', + type: 'dropdown', + options: [ + { label: 'Public', id: 'public' }, + { label: 'Members Only', id: 'members-only' }, + { label: 'Private', id: 'private' }, + ], + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + }, + + // Cover Image URL + { + id: 'coverUrl', + title: 'Cover Image URL', + type: 'short-input', + placeholder: 'https://images.lumacdn.com/...', + condition: { field: 'operation', value: ['create_event', 'update_event'] }, + mode: 'advanced', + }, + + // Get Guests: filter + { + id: 'approvalStatus', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Approved', id: 'approved' }, + { label: 'Session', id: 'session' }, + { label: 'Pending Approval', id: 'pending_approval' }, + { label: 'Invited', id: 'invited' }, + { label: 'Declined', id: 'declined' }, + { label: 'Waitlist', id: 'waitlist' }, + ], + condition: { field: 'operation', value: 'get_guests' }, + }, + + // Add Guests: guest list + { + id: 'guests', + title: 'Guests', + type: 'long-input', + placeholder: '[{"email": "user@example.com", "name": "John Doe"}]', + required: { field: 'operation', value: 'add_guests' }, + condition: { field: 'operation', value: 'add_guests' }, + wandConfig: { + enabled: true, + prompt: `Generate a JSON array of guest objects for adding to a Luma event. + +Each guest object requires an "email" field and optionally "name", "first_name", "last_name". + +Examples: +- "add john@example.com" -> [{"email": "john@example.com"}] +- "invite John Doe at john@example.com and Jane Smith at jane@example.com" -> [{"email": "john@example.com", "name": "John Doe"}, {"email": "jane@example.com", "name": "Jane Smith"}] +- "add alice@co.com as Alice Johnson" -> [{"email": "alice@co.com", "first_name": "Alice", "last_name": "Johnson"}] + +Return ONLY the JSON array - no explanations, no markdown formatting, no extra text.`, + placeholder: + 'Describe the guests to add (e.g., "invite john@example.com and jane@example.com")...', + }, + }, + + // List Events: date filters + { + id: 'after', + title: 'After Date', + type: 'short-input', + placeholder: '2025-01-01T00:00:00Z', + condition: { field: 'operation', value: 'list_events' }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp for filtering events after this date. + +Examples: +- "today" -> current date at 00:00:00Z +- "last week" -> 7 days ago at 00:00:00Z +- "beginning of this month" -> first day of current month at 00:00:00Z +- "January 1st 2025" -> 2025-01-01T00:00:00Z +- "6 months ago" -> appropriate ISO 8601 date + +Return ONLY the ISO 8601 timestamp - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the start date (e.g., "beginning of this month")...', + generationType: 'timestamp', + }, + }, + { + id: 'before', + title: 'Before Date', + type: 'short-input', + placeholder: '2025-12-31T23:59:59Z', + condition: { field: 'operation', value: 'list_events' }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp for filtering events before this date. + +Examples: +- "end of this month" -> last day of current month at 23:59:59Z +- "next week" -> 7 days from now at 23:59:59Z +- "December 31st 2025" -> 2025-12-31T23:59:59Z +- "tomorrow" -> tomorrow at 23:59:59Z + +Return ONLY the ISO 8601 timestamp - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the end date (e.g., "end of this month")...', + generationType: 'timestamp', + }, + }, + + // Shared pagination/sorting (list_events and get_guests) + { + id: 'paginationLimit', + title: 'Limit', + type: 'short-input', + placeholder: 'Max results per page', + condition: { field: 'operation', value: ['list_events', 'get_guests'] }, + mode: 'advanced', + }, + { + id: 'paginationCursor', + title: 'Pagination Cursor', + type: 'short-input', + placeholder: 'Cursor from previous response', + condition: { field: 'operation', value: ['list_events', 'get_guests'] }, + mode: 'advanced', + }, + { + id: 'sortColumn', + title: 'Sort By', + type: 'short-input', + placeholder: 'e.g., start_at, name, registered_at', + condition: { field: 'operation', value: ['list_events', 'get_guests'] }, + mode: 'advanced', + }, + { + id: 'sortDirection', + title: 'Sort Direction', + type: 'dropdown', + options: [ + { label: 'Ascending', id: 'asc' }, + { label: 'Descending', id: 'desc' }, + ], + condition: { field: 'operation', value: ['list_events', 'get_guests'] }, + mode: 'advanced', + }, + ], + + tools: { + access: [ + 'luma_get_event', + 'luma_create_event', + 'luma_update_event', + 'luma_list_events', + 'luma_get_guests', + 'luma_add_guests', + ], + config: { + tool: (params) => `luma_${params.operation}`, + params: (params) => { + const result: Record = {} + if (params.paginationLimit) result.paginationLimit = Number(params.paginationLimit) + return result + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Luma API key' }, + eventId: { type: 'string', description: 'Event ID (starts with evt-)' }, + name: { type: 'string', description: 'Event name' }, + startAt: { type: 'string', description: 'Event start time (ISO 8601)' }, + timezone: { type: 'string', description: 'Event timezone (IANA)' }, + durationInterval: { type: 'string', description: 'Event duration (ISO 8601 interval)' }, + endAt: { type: 'string', description: 'Event end time (ISO 8601)' }, + descriptionMd: { type: 'string', description: 'Event description (Markdown)' }, + meetingUrl: { type: 'string', description: 'Virtual meeting URL' }, + visibility: { type: 'string', description: 'Event visibility' }, + coverUrl: { type: 'string', description: 'Cover image URL (Luma CDN)' }, + approvalStatus: { type: 'string', description: 'Guest approval status filter' }, + guests: { type: 'string', description: 'JSON array of guest objects' }, + after: { type: 'string', description: 'Filter events after this date (ISO 8601)' }, + before: { type: 'string', description: 'Filter events before this date (ISO 8601)' }, + paginationLimit: { type: 'number', description: 'Max results per page' }, + paginationCursor: { type: 'string', description: 'Pagination cursor from previous response' }, + sortColumn: { type: 'string', description: 'Column to sort by' }, + sortDirection: { type: 'string', description: 'Sort direction (asc or desc)' }, + }, + + outputs: { + event: { type: 'json', description: 'Event details' }, + hosts: { type: 'json', description: 'Event hosts' }, + events: { type: 'json', description: 'List of events' }, + guests: { type: 'json', description: 'List of guests' }, + hasMore: { type: 'boolean', description: 'Whether more results are available' }, + nextCursor: { type: 'string', description: 'Pagination cursor for next page' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index c203af4f66..0a03b89759 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -82,6 +82,7 @@ import { LemlistBlock } from '@/blocks/blocks/lemlist' import { LinearBlock } from '@/blocks/blocks/linear' import { LinkedInBlock } from '@/blocks/blocks/linkedin' import { LinkupBlock } from '@/blocks/blocks/linkup' +import { LumaBlock } from '@/blocks/blocks/luma' import { MailchimpBlock } from '@/blocks/blocks/mailchimp' import { MailgunBlock } from '@/blocks/blocks/mailgun' import { ManualTriggerBlock } from '@/blocks/blocks/manual_trigger' @@ -277,6 +278,7 @@ export const registry: Record = { linear: LinearBlock, linkedin: LinkedInBlock, linkup: LinkupBlock, + luma: LumaBlock, mailchimp: MailchimpBlock, mailgun: MailgunBlock, manual_trigger: ManualTriggerBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 9da5a57508..62ff50e936 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -3956,6 +3956,17 @@ export function IntercomIcon(props: SVGProps) { ) } +export function LumaIcon(props: SVGProps) { + return ( + + + + ) +} + export function MailchimpIcon(props: SVGProps) { return ( = { + id: 'luma_add_guests', + name: 'Luma Add Guests', + description: + 'Add guests to a Luma event by email. Guests are added with Going (approved) status and receive one ticket of the default ticket type.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Luma API key', + }, + eventId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Event ID (starts with evt-)', + }, + guests: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'JSON array of guest objects. Each guest requires an "email" field and optionally "name", "first_name", "last_name". Example: [{"email": "user@example.com", "name": "John Doe"}]', + }, + }, + + request: { + url: 'https://public-api.luma.com/v1/event/add-guests', + method: 'POST', + headers: (params) => ({ + 'x-luma-api-key': params.apiKey, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + let guestsArray: unknown[] + try { + guestsArray = typeof params.guests === 'string' ? JSON.parse(params.guests) : params.guests + } catch { + guestsArray = [{ email: params.guests }] + } + return { + event_id: params.eventId, + guests: guestsArray, + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to add guests') + } + + const guests = (data.entries ?? []).map((entry: Record) => { + const guest = entry.guest as Record + return { + id: (guest.id as string) ?? null, + email: (guest.user_email as string) ?? null, + name: (guest.user_name as string) ?? null, + firstName: (guest.user_first_name as string) ?? null, + lastName: (guest.user_last_name as string) ?? null, + approvalStatus: (guest.approval_status as string) ?? null, + registeredAt: (guest.registered_at as string) ?? null, + invitedAt: (guest.invited_at as string) ?? null, + joinedAt: (guest.joined_at as string) ?? null, + checkedInAt: (guest.checked_in_at as string) ?? null, + phoneNumber: (guest.phone_number as string) ?? null, + } + }) + + return { + success: true, + output: { + guests, + }, + } + }, + + outputs: { + guests: { + type: 'array', + description: 'List of added guests with their assigned status and ticket info', + items: { + type: 'object', + properties: LUMA_GUEST_OUTPUT_PROPERTIES, + }, + }, + }, +} diff --git a/apps/sim/tools/luma/create_event.ts b/apps/sim/tools/luma/create_event.ts new file mode 100644 index 0000000000..5324fb7d24 --- /dev/null +++ b/apps/sim/tools/luma/create_event.ts @@ -0,0 +1,155 @@ +import type { LumaCreateEventParams, LumaCreateEventResponse } from '@/tools/luma/types' +import { LUMA_EVENT_OUTPUT_PROPERTIES, LUMA_HOST_OUTPUT_PROPERTIES } from '@/tools/luma/types' +import type { ToolConfig } from '@/tools/types' + +export const createEventTool: ToolConfig = { + id: 'luma_create_event', + name: 'Luma Create Event', + description: + 'Create a new event on Luma with a name, start time, timezone, and optional details like description, location, and visibility.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Luma API key', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Event name/title', + }, + startAt: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Event start time in ISO 8601 format (e.g., 2025-03-15T18:00:00Z)', + }, + timezone: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'IANA timezone (e.g., America/New_York, Europe/London)', + }, + endAt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Event end time in ISO 8601 format (e.g., 2025-03-15T20:00:00Z)', + }, + durationInterval: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Event duration as ISO 8601 interval (e.g., PT2H for 2 hours, PT30M for 30 minutes). Used if endAt is not provided.', + }, + descriptionMd: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Event description in Markdown format', + }, + meetingUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Virtual meeting URL for online events (e.g., Zoom, Google Meet link)', + }, + visibility: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Event visibility: public, members-only, or private (defaults to public)', + }, + coverUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cover image URL (must be a Luma CDN URL from images.lumacdn.com)', + }, + }, + + request: { + url: 'https://public-api.luma.com/v1/event/create', + method: 'POST', + headers: (params) => ({ + 'x-luma-api-key': params.apiKey, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = { + name: params.name, + start_at: params.startAt, + timezone: params.timezone, + } + if (params.endAt) body.end_at = params.endAt + if (params.durationInterval) body.duration_interval = params.durationInterval + if (params.descriptionMd) body.description_md = params.descriptionMd + if (params.meetingUrl) body.meeting_url = params.meetingUrl + if (params.visibility) body.visibility = params.visibility + if (params.coverUrl) body.cover_url = params.coverUrl + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to create event') + } + + const event = data.event + const hosts = (data.hosts ?? []).map((h: Record) => ({ + name: (h.name as string) ?? null, + email: (h.email as string) ?? null, + })) + + return { + success: true, + output: { + event: { + id: event.id ?? null, + name: event.name ?? null, + startAt: event.start_at ?? null, + endAt: event.end_at ?? null, + timezone: event.timezone ?? null, + durationInterval: event.duration_interval ?? null, + createdAt: event.created_at ?? null, + description: event.description ?? null, + descriptionMd: event.description_md ?? null, + coverUrl: event.cover_url ?? null, + url: event.url ?? null, + visibility: event.visibility ?? null, + meetingUrl: event.meeting_url ?? null, + geoAddressJson: event.geo_address_json ?? null, + geoLatitude: event.geo_latitude ?? null, + geoLongitude: event.geo_longitude ?? null, + calendarId: event.calendar_id ?? null, + }, + hosts, + }, + } + }, + + outputs: { + event: { + type: 'object', + description: 'Created event details', + properties: LUMA_EVENT_OUTPUT_PROPERTIES, + }, + hosts: { + type: 'array', + description: 'Event hosts', + items: { + type: 'object', + properties: LUMA_HOST_OUTPUT_PROPERTIES, + }, + }, + }, +} diff --git a/apps/sim/tools/luma/get_event.ts b/apps/sim/tools/luma/get_event.ts new file mode 100644 index 0000000000..f5bf1cd031 --- /dev/null +++ b/apps/sim/tools/luma/get_event.ts @@ -0,0 +1,95 @@ +import type { LumaGetEventParams, LumaGetEventResponse } from '@/tools/luma/types' +import { LUMA_EVENT_OUTPUT_PROPERTIES, LUMA_HOST_OUTPUT_PROPERTIES } from '@/tools/luma/types' +import type { ToolConfig } from '@/tools/types' + +export const getEventTool: ToolConfig = { + id: 'luma_get_event', + name: 'Luma Get Event', + description: + 'Retrieve details of a Luma event including name, time, location, hosts, and visibility settings.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Luma API key', + }, + eventId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Event ID (starts with evt-)', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://public-api.luma.com/v1/event/get') + url.searchParams.set('id', params.eventId) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + 'x-luma-api-key': params.apiKey, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get event') + } + + const event = data.event + const hosts = (data.hosts ?? []).map((h: Record) => ({ + name: (h.name as string) ?? null, + email: (h.email as string) ?? null, + })) + + return { + success: true, + output: { + event: { + id: event.id ?? null, + name: event.name ?? null, + startAt: event.start_at ?? null, + endAt: event.end_at ?? null, + timezone: event.timezone ?? null, + durationInterval: event.duration_interval ?? null, + createdAt: event.created_at ?? null, + description: event.description ?? null, + descriptionMd: event.description_md ?? null, + coverUrl: event.cover_url ?? null, + url: event.url ?? null, + visibility: event.visibility ?? null, + meetingUrl: event.meeting_url ?? null, + geoAddressJson: event.geo_address_json ?? null, + geoLatitude: event.geo_latitude ?? null, + geoLongitude: event.geo_longitude ?? null, + calendarId: event.calendar_id ?? null, + }, + hosts, + }, + } + }, + + outputs: { + event: { + type: 'object', + description: 'Event details', + properties: LUMA_EVENT_OUTPUT_PROPERTIES, + }, + hosts: { + type: 'array', + description: 'Event hosts', + items: { + type: 'object', + properties: LUMA_HOST_OUTPUT_PROPERTIES, + }, + }, + }, +} diff --git a/apps/sim/tools/luma/get_guests.ts b/apps/sim/tools/luma/get_guests.ts new file mode 100644 index 0000000000..1511fe862b --- /dev/null +++ b/apps/sim/tools/luma/get_guests.ts @@ -0,0 +1,132 @@ +import type { LumaGetGuestsParams, LumaGetGuestsResponse } from '@/tools/luma/types' +import { LUMA_GUEST_OUTPUT_PROPERTIES } from '@/tools/luma/types' +import type { ToolConfig } from '@/tools/types' + +export const getGuestsTool: ToolConfig = { + id: 'luma_get_guests', + name: 'Luma Get Guests', + description: + 'Retrieve the guest list for a Luma event with optional filtering by approval status, sorting, and pagination.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Luma API key', + }, + eventId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Event ID (starts with evt-)', + }, + approvalStatus: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by approval status: approved, session, pending_approval, invited, declined, or waitlist', + }, + paginationLimit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of guests to return per page', + }, + paginationCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor from a previous response (next_cursor) to fetch the next page of results', + }, + sortColumn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Column to sort by: name, email, created_at, registered_at, or checked_in_at', + }, + sortDirection: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort direction: asc, desc, asc nulls last, or desc nulls last', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://public-api.luma.com/v1/event/get-guests') + url.searchParams.set('event_id', params.eventId) + if (params.approvalStatus) url.searchParams.set('approval_status', params.approvalStatus) + if (params.paginationLimit) + url.searchParams.set('pagination_limit', String(params.paginationLimit)) + if (params.paginationCursor) + url.searchParams.set('pagination_cursor', params.paginationCursor) + if (params.sortColumn) url.searchParams.set('sort_column', params.sortColumn) + if (params.sortDirection) url.searchParams.set('sort_direction', params.sortDirection) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + 'x-luma-api-key': params.apiKey, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get guests') + } + + const guests = (data.entries ?? []).map((entry: Record) => { + const guest = entry.guest as Record + return { + id: (guest.id as string) ?? null, + email: (guest.user_email as string) ?? null, + name: (guest.user_name as string) ?? null, + firstName: (guest.user_first_name as string) ?? null, + lastName: (guest.user_last_name as string) ?? null, + approvalStatus: (guest.approval_status as string) ?? null, + registeredAt: (guest.registered_at as string) ?? null, + invitedAt: (guest.invited_at as string) ?? null, + joinedAt: (guest.joined_at as string) ?? null, + checkedInAt: (guest.checked_in_at as string) ?? null, + phoneNumber: (guest.phone_number as string) ?? null, + } + }) + + return { + success: true, + output: { + guests, + hasMore: data.has_more ?? false, + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + guests: { + type: 'array', + description: 'List of event guests', + items: { + type: 'object', + properties: LUMA_GUEST_OUTPUT_PROPERTIES, + }, + }, + hasMore: { + type: 'boolean', + description: 'Whether more results are available for pagination', + }, + nextCursor: { + type: 'string', + description: 'Cursor to pass as paginationCursor to fetch the next page', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/luma/index.ts b/apps/sim/tools/luma/index.ts new file mode 100644 index 0000000000..c74f1d40a0 --- /dev/null +++ b/apps/sim/tools/luma/index.ts @@ -0,0 +1,13 @@ +import { addGuestsTool } from '@/tools/luma/add_guests' +import { createEventTool } from '@/tools/luma/create_event' +import { getEventTool } from '@/tools/luma/get_event' +import { getGuestsTool } from '@/tools/luma/get_guests' +import { listEventsTool } from '@/tools/luma/list_events' +import { updateEventTool } from '@/tools/luma/update_event' + +export const lumaAddGuestsTool = addGuestsTool +export const lumaCreateEventTool = createEventTool +export const lumaGetEventTool = getEventTool +export const lumaGetGuestsTool = getGuestsTool +export const lumaListEventsTool = listEventsTool +export const lumaUpdateEventTool = updateEventTool diff --git a/apps/sim/tools/luma/list_events.ts b/apps/sim/tools/luma/list_events.ts new file mode 100644 index 0000000000..36339ed650 --- /dev/null +++ b/apps/sim/tools/luma/list_events.ts @@ -0,0 +1,137 @@ +import type { LumaListEventsParams, LumaListEventsResponse } from '@/tools/luma/types' +import { LUMA_EVENT_OUTPUT_PROPERTIES } from '@/tools/luma/types' +import type { ToolConfig } from '@/tools/types' + +export const listEventsTool: ToolConfig = { + id: 'luma_list_events', + name: 'Luma List Events', + description: + 'List events from your Luma calendar with optional date range filtering, sorting, and pagination.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Luma API key', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return events after this ISO 8601 datetime (e.g., 2025-01-01T00:00:00Z)', + }, + before: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return events before this ISO 8601 datetime (e.g., 2025-12-31T23:59:59Z)', + }, + paginationLimit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of events to return per page', + }, + paginationCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor from a previous response (next_cursor) to fetch the next page of results', + }, + sortColumn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Column to sort by (e.g., start_at)', + }, + sortDirection: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort direction: asc, desc, asc nulls last, or desc nulls last', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://public-api.luma.com/v1/calendar/list-events') + if (params.after) url.searchParams.set('after', params.after) + if (params.before) url.searchParams.set('before', params.before) + if (params.paginationLimit) + url.searchParams.set('pagination_limit', String(params.paginationLimit)) + if (params.paginationCursor) + url.searchParams.set('pagination_cursor', params.paginationCursor) + if (params.sortColumn) url.searchParams.set('sort_column', params.sortColumn) + if (params.sortDirection) url.searchParams.set('sort_direction', params.sortDirection) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + 'x-luma-api-key': params.apiKey, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list events') + } + + const events = (data.entries ?? []).map((entry: Record) => { + const event = entry.event as Record + return { + id: (event.id as string) ?? null, + name: (event.name as string) ?? null, + startAt: (event.start_at as string) ?? null, + endAt: (event.end_at as string) ?? null, + timezone: (event.timezone as string) ?? null, + durationInterval: (event.duration_interval as string) ?? null, + createdAt: (event.created_at as string) ?? null, + description: (event.description as string) ?? null, + descriptionMd: (event.description_md as string) ?? null, + coverUrl: (event.cover_url as string) ?? null, + url: (event.url as string) ?? null, + visibility: (event.visibility as string) ?? null, + meetingUrl: (event.meeting_url as string) ?? null, + geoAddressJson: (event.geo_address_json as Record) ?? null, + geoLatitude: (event.geo_latitude as string) ?? null, + geoLongitude: (event.geo_longitude as string) ?? null, + calendarId: (event.calendar_id as string) ?? null, + } + }) + + return { + success: true, + output: { + events, + hasMore: data.has_more ?? false, + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + events: { + type: 'array', + description: 'List of calendar events', + items: { + type: 'object', + properties: LUMA_EVENT_OUTPUT_PROPERTIES, + }, + }, + hasMore: { + type: 'boolean', + description: 'Whether more results are available for pagination', + }, + nextCursor: { + type: 'string', + description: 'Cursor to pass as paginationCursor to fetch the next page', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/luma/types.ts b/apps/sim/tools/luma/types.ts new file mode 100644 index 0000000000..8a7782943b --- /dev/null +++ b/apps/sim/tools/luma/types.ts @@ -0,0 +1,190 @@ +import type { ToolResponse } from '@/tools/types' + +export interface LumaGetEventParams { + apiKey: string + eventId: string +} + +export interface LumaCreateEventParams { + apiKey: string + name: string + startAt: string + timezone: string + durationInterval?: string + endAt?: string + descriptionMd?: string + meetingUrl?: string + visibility?: string + coverUrl?: string +} + +export interface LumaListEventsParams { + apiKey: string + after?: string + before?: string + paginationLimit?: number + paginationCursor?: string + sortColumn?: string + sortDirection?: string +} + +export interface LumaGetGuestsParams { + apiKey: string + eventId: string + approvalStatus?: string + paginationLimit?: number + paginationCursor?: string + sortColumn?: string + sortDirection?: string +} + +export interface LumaUpdateEventParams { + apiKey: string + eventId: string + name?: string + startAt?: string + timezone?: string + endAt?: string + durationInterval?: string + descriptionMd?: string + meetingUrl?: string + visibility?: string + coverUrl?: string +} + +export interface LumaAddGuestsParams { + apiKey: string + eventId: string + guests: string +} + +export interface LumaHostEntry { + name: string | null + email: string | null +} + +export interface LumaEventEntry { + id: string + name: string + startAt: string | null + endAt: string | null + timezone: string | null + durationInterval: string | null + createdAt: string | null + description: string | null + descriptionMd: string | null + coverUrl: string | null + url: string | null + visibility: string | null + meetingUrl: string | null + geoAddressJson: Record | null + geoLatitude: string | null + geoLongitude: string | null + calendarId: string | null +} + +export interface LumaGuestEntry { + id: string + email: string | null + name: string | null + firstName: string | null + lastName: string | null + approvalStatus: string | null + registeredAt: string | null + invitedAt: string | null + joinedAt: string | null + checkedInAt: string | null + phoneNumber: string | null +} + +export interface LumaGetEventResponse extends ToolResponse { + output: { + event: LumaEventEntry + hosts: LumaHostEntry[] + } +} + +export interface LumaCreateEventResponse extends ToolResponse { + output: { + event: LumaEventEntry + hosts: LumaHostEntry[] + } +} + +export interface LumaUpdateEventResponse extends ToolResponse { + output: { + event: LumaEventEntry + hosts: LumaHostEntry[] + } +} + +export interface LumaListEventsResponse extends ToolResponse { + output: { + events: LumaEventEntry[] + hasMore: boolean + nextCursor: string | null + } +} + +export interface LumaGetGuestsResponse extends ToolResponse { + output: { + guests: LumaGuestEntry[] + hasMore: boolean + nextCursor: string | null + } +} + +export interface LumaAddGuestsResponse extends ToolResponse { + output: { + guests: LumaGuestEntry[] + } +} + +export const LUMA_HOST_OUTPUT_PROPERTIES = { + name: { type: 'string' as const, description: 'Host name' }, + email: { type: 'string' as const, description: 'Host email address' }, +} + +export const LUMA_EVENT_OUTPUT_PROPERTIES = { + id: { type: 'string' as const, description: 'Event ID' }, + name: { type: 'string' as const, description: 'Event name' }, + startAt: { type: 'string' as const, description: 'Event start time (ISO 8601)' }, + endAt: { type: 'string' as const, description: 'Event end time (ISO 8601)' }, + timezone: { type: 'string' as const, description: 'Event timezone (IANA)' }, + durationInterval: { + type: 'string' as const, + description: 'Event duration (ISO 8601 interval, e.g. PT2H)', + }, + createdAt: { type: 'string' as const, description: 'Event creation timestamp (ISO 8601)' }, + description: { type: 'string' as const, description: 'Event description (plain text)' }, + descriptionMd: { type: 'string' as const, description: 'Event description (Markdown)' }, + coverUrl: { type: 'string' as const, description: 'Event cover image URL' }, + url: { type: 'string' as const, description: 'Event page URL on lu.ma' }, + visibility: { + type: 'string' as const, + description: 'Event visibility (public, members-only, private)', + }, + meetingUrl: { type: 'string' as const, description: 'Virtual meeting URL' }, + geoAddressJson: { type: 'json' as const, description: 'Structured location/address data' }, + geoLatitude: { type: 'string' as const, description: 'Venue latitude coordinate' }, + geoLongitude: { type: 'string' as const, description: 'Venue longitude coordinate' }, + calendarId: { type: 'string' as const, description: 'Associated calendar ID' }, +} + +export const LUMA_GUEST_OUTPUT_PROPERTIES = { + id: { type: 'string' as const, description: 'Guest ID' }, + email: { type: 'string' as const, description: 'Guest email address' }, + name: { type: 'string' as const, description: 'Guest full name' }, + firstName: { type: 'string' as const, description: 'Guest first name' }, + lastName: { type: 'string' as const, description: 'Guest last name' }, + approvalStatus: { + type: 'string' as const, + description: + 'Guest approval status (approved, session, pending_approval, invited, declined, waitlist)', + }, + registeredAt: { type: 'string' as const, description: 'Registration timestamp (ISO 8601)' }, + invitedAt: { type: 'string' as const, description: 'Invitation timestamp (ISO 8601)' }, + joinedAt: { type: 'string' as const, description: 'Join timestamp (ISO 8601)' }, + checkedInAt: { type: 'string' as const, description: 'Check-in timestamp (ISO 8601)' }, + phoneNumber: { type: 'string' as const, description: 'Guest phone number' }, +} diff --git a/apps/sim/tools/luma/update_event.ts b/apps/sim/tools/luma/update_event.ts new file mode 100644 index 0000000000..85a0ad21a1 --- /dev/null +++ b/apps/sim/tools/luma/update_event.ts @@ -0,0 +1,162 @@ +import type { LumaUpdateEventParams, LumaUpdateEventResponse } from '@/tools/luma/types' +import { LUMA_EVENT_OUTPUT_PROPERTIES, LUMA_HOST_OUTPUT_PROPERTIES } from '@/tools/luma/types' +import type { ToolConfig } from '@/tools/types' + +export const updateEventTool: ToolConfig = { + id: 'luma_update_event', + name: 'Luma Update Event', + description: + 'Update an existing Luma event. Only the fields you provide will be changed; all other fields remain unchanged.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Luma API key', + }, + eventId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Event ID to update (starts with evt-)', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New event name/title', + }, + startAt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New start time in ISO 8601 format (e.g., 2025-03-15T18:00:00Z)', + }, + timezone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New IANA timezone (e.g., America/New_York, Europe/London)', + }, + endAt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New end time in ISO 8601 format (e.g., 2025-03-15T20:00:00Z)', + }, + durationInterval: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'New duration as ISO 8601 interval (e.g., PT2H for 2 hours). Used if endAt is not provided.', + }, + descriptionMd: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New event description in Markdown format', + }, + meetingUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New virtual meeting URL (e.g., Zoom, Google Meet link)', + }, + visibility: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New visibility: public, members-only, or private', + }, + coverUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New cover image URL (must be a Luma CDN URL from images.lumacdn.com)', + }, + }, + + request: { + url: 'https://public-api.luma.com/v1/event/update', + method: 'POST', + headers: (params) => ({ + 'x-luma-api-key': params.apiKey, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = { + id: params.eventId, + } + if (params.name) body.name = params.name + if (params.startAt) body.start_at = params.startAt + if (params.timezone) body.timezone = params.timezone + if (params.endAt) body.end_at = params.endAt + if (params.durationInterval) body.duration_interval = params.durationInterval + if (params.descriptionMd) body.description_md = params.descriptionMd + if (params.meetingUrl) body.meeting_url = params.meetingUrl + if (params.visibility) body.visibility = params.visibility + if (params.coverUrl) body.cover_url = params.coverUrl + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to update event') + } + + const event = data.event + const hosts = (data.hosts ?? []).map((h: Record) => ({ + name: (h.name as string) ?? null, + email: (h.email as string) ?? null, + })) + + return { + success: true, + output: { + event: { + id: event.id ?? null, + name: event.name ?? null, + startAt: event.start_at ?? null, + endAt: event.end_at ?? null, + timezone: event.timezone ?? null, + durationInterval: event.duration_interval ?? null, + createdAt: event.created_at ?? null, + description: event.description ?? null, + descriptionMd: event.description_md ?? null, + coverUrl: event.cover_url ?? null, + url: event.url ?? null, + visibility: event.visibility ?? null, + meetingUrl: event.meeting_url ?? null, + geoAddressJson: event.geo_address_json ?? null, + geoLatitude: event.geo_latitude ?? null, + geoLongitude: event.geo_longitude ?? null, + calendarId: event.calendar_id ?? null, + }, + hosts, + }, + } + }, + + outputs: { + event: { + type: 'object', + description: 'Updated event details', + properties: LUMA_EVENT_OUTPUT_PROPERTIES, + }, + hosts: { + type: 'array', + description: 'Event hosts', + items: { + type: 'object', + properties: LUMA_HOST_OUTPUT_PROPERTIES, + }, + }, + }, +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 4bcbc9a13a..155dc57942 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1162,6 +1162,14 @@ import { import { linkedInGetProfileTool, linkedInSharePostTool } from '@/tools/linkedin' import { linkupSearchTool } from '@/tools/linkup' import { llmChatTool } from '@/tools/llm' +import { + lumaAddGuestsTool, + lumaCreateEventTool, + lumaGetEventTool, + lumaGetGuestsTool, + lumaListEventsTool, + lumaUpdateEventTool, +} from '@/tools/luma' import { mailchimpAddMemberTagsTool, mailchimpAddMemberTool, @@ -2238,6 +2246,12 @@ export const tools: Record = { jina_read_url: jinaReadUrlTool, jina_search: jinaSearchTool, linkup_search: linkupSearchTool, + luma_add_guests: lumaAddGuestsTool, + luma_create_event: lumaCreateEventTool, + luma_get_event: lumaGetEventTool, + luma_get_guests: lumaGetGuestsTool, + luma_list_events: lumaListEventsTool, + luma_update_event: lumaUpdateEventTool, linkedin_share_post: linkedInSharePostTool, linkedin_get_profile: linkedInGetProfileTool, resend_send: mailSendTool,