chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import {
countGuests,
type LumaAddGuestsParams,
type LumaAddGuestsResponse,
} from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const addGuestsTool: ToolConfig<LumaAddGuestsParams, LumaAddGuestsResponse> = {
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.trim(),
guests: guestsArray,
}
},
},
transformResponse: async (response: Response, params) => {
const data = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(data.message || data.error || 'Failed to add guests')
}
return {
success: true,
output: {
added: countGuests(params?.guests ?? ''),
},
}
},
outputs: {
added: {
type: 'number',
description: 'Number of guests submitted to the event (added with Going/approved status)',
},
},
}
+78
View File
@@ -0,0 +1,78 @@
import type { LumaCancelEventParams, LumaCancelEventResponse } from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const cancelEventTool: ToolConfig<LumaCancelEventParams, LumaCancelEventResponse> = {
id: 'luma_cancel_event',
name: 'Luma Cancel Event',
description:
'Cancel a Luma event. This is irreversible and notifies all registered guests. Requires a cancellation token obtained from the Request Event Cancellation endpoint.',
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 cancel (starts with evt-)',
},
cancellationToken: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Cancellation token from the Request Event Cancellation endpoint (POST /v1/event/cancel/request)',
},
shouldRefund: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to refund paid guests. Required if the event has paid registrations.',
},
},
request: {
url: 'https://public-api.luma.com/v1/event/cancel',
method: 'POST',
headers: (params) => ({
'x-luma-api-key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
event_id: params.eventId.trim(),
cancellation_token: params.cancellationToken.trim(),
}
if (params.shouldRefund !== undefined) body.should_refund = params.shouldRefund
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(data.message || data.error || 'Failed to cancel event')
}
return {
success: true,
output: {
cancelled: true,
},
}
},
outputs: {
cancelled: {
type: 'boolean',
description: 'Whether the event was successfully cancelled',
},
},
}
+151
View File
@@ -0,0 +1,151 @@
import type { LumaCreateEventParams, LumaCreateEventResponse } from '@/tools/luma/types'
import {
LUMA_EVENT_OUTPUT_PROPERTIES,
LUMA_HOST_OUTPUT_PROPERTIES,
lumaEventStub,
} from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const createEventTool: ToolConfig<LumaCreateEventParams, LumaCreateEventResponse> = {
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<string, unknown> = {
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 eventId: string | null = data.id ?? data.api_id ?? null
return {
success: true,
output: {
event: lumaEventStub(eventId),
hosts: [],
},
}
},
postProcess: async (result, params, executeTool) => {
const eventId = result.success ? result.output.event.id : null
if (!eventId) return result
const full = await executeTool('luma_get_event', {
apiKey: params.apiKey,
eventId,
})
if (full.success && full.output?.event) {
return full as LumaCreateEventResponse
}
return result
},
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,
},
},
},
}
+99
View File
@@ -0,0 +1,99 @@
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<LumaGetEventParams, LumaGetEventResponse> = {
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.trim())
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<string, unknown>) => ({
id: (h.id as string) ?? null,
name: (h.name as string) ?? null,
firstName: (h.first_name as string) ?? null,
lastName: (h.last_name as string) ?? null,
email: (h.email as string) ?? null,
avatarUrl: (h.avatar_url 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,
},
},
},
}
+84
View File
@@ -0,0 +1,84 @@
import type { LumaGetGuestParams, LumaGetGuestResponse } from '@/tools/luma/types'
import { LUMA_GUEST_OUTPUT_PROPERTIES, resolveGuestCheckedInAt } from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const getGuestTool: ToolConfig<LumaGetGuestParams, LumaGetGuestResponse> = {
id: 'luma_get_guest',
name: 'Luma Get Guest',
description:
"Retrieve a single guest's details on a Luma event, including approval status, registration timestamps, and contact info.",
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 the guest belongs to (starts with evt-)',
},
guestIdentifier: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
"Guest ID (gst-...), guest key (g-...), ticket key, or the guest's email address",
},
},
request: {
url: (params) => {
const url = new URL('https://public-api.luma.com/v1/events/guests/get')
url.searchParams.set('event_id', params.eventId.trim())
url.searchParams.set('id', params.guestIdentifier.trim())
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 guest')
}
const guest = (data.guest as Record<string, unknown>) ?? data
return {
success: true,
output: {
guest: {
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: resolveGuestCheckedInAt(guest),
phoneNumber: (guest.phone_number as string) ?? null,
},
},
}
},
outputs: {
guest: {
type: 'object',
description: 'Guest details',
properties: LUMA_GUEST_OUTPUT_PROPERTIES,
},
},
}
+132
View File
@@ -0,0 +1,132 @@
import type { LumaGetGuestsParams, LumaGetGuestsResponse } from '@/tools/luma/types'
import { LUMA_GUEST_OUTPUT_PROPERTIES, resolveGuestCheckedInAt } from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const getGuestsTool: ToolConfig<LumaGetGuestsParams, LumaGetGuestsResponse> = {
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.trim())
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<string, unknown>) => {
const guest = (entry.guest as Record<string, unknown>) ?? entry
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: resolveGuestCheckedInAt(guest),
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,
},
},
}
+25
View File
@@ -0,0 +1,25 @@
import { addGuestsTool } from '@/tools/luma/add_guests'
import { cancelEventTool } from '@/tools/luma/cancel_event'
import { createEventTool } from '@/tools/luma/create_event'
import { getEventTool } from '@/tools/luma/get_event'
import { getGuestTool } from '@/tools/luma/get_guest'
import { getGuestsTool } from '@/tools/luma/get_guests'
import { listEventsTool } from '@/tools/luma/list_events'
import { lookupEventTool } from '@/tools/luma/lookup_event'
import { sendInvitesTool } from '@/tools/luma/send_invites'
import { updateEventTool } from '@/tools/luma/update_event'
import { updateGuestStatusTool } from '@/tools/luma/update_guest_status'
export * from './types'
export const lumaAddGuestsTool = addGuestsTool
export const lumaCancelEventTool = cancelEventTool
export const lumaCreateEventTool = createEventTool
export const lumaGetEventTool = getEventTool
export const lumaGetGuestTool = getGuestTool
export const lumaGetGuestsTool = getGuestsTool
export const lumaListEventsTool = listEventsTool
export const lumaLookupEventTool = lookupEventTool
export const lumaSendInvitesTool = sendInvitesTool
export const lumaUpdateEventTool = updateEventTool
export const lumaUpdateGuestStatusTool = updateGuestStatusTool
+137
View File
@@ -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<LumaListEventsParams, LumaListEventsResponse> = {
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 (only start_at is supported)',
},
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<string, unknown>) => {
const event = (entry.event as Record<string, unknown>) ?? entry
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<string, unknown>) ?? 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,
},
},
}
+94
View File
@@ -0,0 +1,94 @@
import type { LumaLookupEventParams, LumaLookupEventResponse } from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const lookupEventTool: ToolConfig<LumaLookupEventParams, LumaLookupEventResponse> = {
id: 'luma_lookup_event',
name: 'Luma Lookup Event',
description:
'Look up an event by its public URL or event ID to resolve its canonical ID, API ID, and approval status.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Luma API key',
},
url: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Public event URL on lu.ma (provide this or an event ID)',
},
eventId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Event ID to look up (starts with evt-). Provide this or a URL.',
},
platform: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Event platform to look up: luma or external (defaults to luma)',
},
},
request: {
url: (params) => {
const url = new URL('https://public-api.luma.com/v1/calendar/lookup-event')
if (params.url) url.searchParams.set('url', params.url.trim())
if (params.eventId) url.searchParams.set('event_id', params.eventId.trim())
if (params.platform) url.searchParams.set('platform', params.platform)
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 look up event')
}
const event = data.event as Record<string, unknown> | null
return {
success: true,
output: {
found: Boolean(event),
eventId: (event?.id as string) ?? null,
apiId: (event?.api_id as string) ?? null,
status: (event?.status as string) ?? null,
},
}
},
outputs: {
found: {
type: 'boolean',
description: 'Whether a matching event was found',
},
eventId: {
type: 'string',
description: 'Resolved event ID',
optional: true,
},
apiId: {
type: 'string',
description: 'Resolved event API ID (deprecated identifier)',
optional: true,
},
status: {
type: 'string',
description: 'Event approval status (approved, pending, rejected)',
optional: true,
},
},
}
+88
View File
@@ -0,0 +1,88 @@
import {
countGuests,
type LumaSendInvitesParams,
type LumaSendInvitesResponse,
} from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const sendInvitesTool: ToolConfig<LumaSendInvitesParams, LumaSendInvitesResponse> = {
id: 'luma_send_invites',
name: 'Luma Send Invites',
description:
'Send email invitations to guests for a Luma event. Unlike Add Guests (which registers guests directly), this emails an invite that recipients can accept.',
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 invite guests to (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". Example: [{"email": "user@example.com", "name": "John Doe"}]',
},
message: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional custom message included in the invite email (max 200 characters)',
},
},
request: {
url: 'https://public-api.luma.com/v1/event/send-invites',
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 }]
}
const body: Record<string, unknown> = {
event_id: params.eventId.trim(),
guests: guestsArray,
}
if (params.message) body.message = params.message
return body
},
},
transformResponse: async (response: Response, params) => {
const data = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(data.message || data.error || 'Failed to send invites')
}
return {
success: true,
output: {
invited: countGuests(params?.guests ?? ''),
},
}
},
outputs: {
invited: {
type: 'number',
description: 'Number of guests invited to the event',
},
},
}
+331
View File
@@ -0,0 +1,331 @@
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 LumaSendInvitesParams {
apiKey: string
eventId: string
guests: string
message?: string
}
export interface LumaUpdateGuestStatusParams {
apiKey: string
eventId?: string
guestIdentifier: string
status: string
shouldRefund?: boolean
sendEmail?: boolean
}
export interface LumaGetGuestParams {
apiKey: string
eventId: string
guestIdentifier: string
}
export interface LumaCancelEventParams {
apiKey: string
eventId: string
cancellationToken: string
shouldRefund?: boolean
}
export interface LumaLookupEventParams {
apiKey: string
url?: string
eventId?: string
platform?: string
}
interface LumaHostEntry {
id: string | null
name: string | null
firstName: string | null
lastName: string | null
email: string | null
avatarUrl: string | null
}
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<string, unknown> | null
geoLatitude: string | null
geoLongitude: string | null
calendarId: string | null
}
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: {
added: number
}
}
export interface LumaSendInvitesResponse extends ToolResponse {
output: {
invited: number
}
}
export interface LumaUpdateGuestStatusResponse extends ToolResponse {
output: {
status: string
guest: string
}
}
export interface LumaGetGuestResponse extends ToolResponse {
output: {
guest: LumaGuestEntry
}
}
export interface LumaCancelEventResponse extends ToolResponse {
output: {
cancelled: boolean
}
}
export interface LumaLookupEventResponse extends ToolResponse {
output: {
found: boolean
eventId: string | null
apiId: string | null
status: string | null
}
}
/**
* Counts guests in a guests param value. Accepts a JSON array string (the
* standard input shape) or a bare email string. Used by Add Guests and Send
* Invites, whose endpoints return an empty body — the submitted count is the
* only meaningful signal to report back.
*/
export function countGuests(guests: string): number {
try {
const parsed = JSON.parse(guests)
if (Array.isArray(parsed)) return parsed.length
return parsed ? 1 : 0
} catch {
return guests.trim() ? 1 : 0
}
}
/**
* Resolves a guest's check-in time. Per the Luma API docs the canonical
* check-in lives per ticket on `event_tickets[].checked_in_at`; the top-level
* `checked_in_at` is deprecated. Returns the first checked-in ticket's
* timestamp, falling back to the deprecated top-level field.
*/
export function resolveGuestCheckedInAt(guest: Record<string, unknown>): string | null {
const tickets = guest.event_tickets
if (Array.isArray(tickets)) {
for (const ticket of tickets) {
const checkedInAt = (ticket as Record<string, unknown>)?.checked_in_at
if (checkedInAt) return checkedInAt as string
}
}
return (guest.checked_in_at as string) ?? null
}
/**
* Builds an event stub with only the id populated. Used by create/update,
* whose API responses do not return the full event object — the full record
* is fetched in a follow-up Get Event call via postProcess.
*/
export function lumaEventStub(id: string | null): LumaEventEntry {
return {
id: id ?? '',
name: '',
startAt: null,
endAt: null,
timezone: null,
durationInterval: null,
createdAt: null,
description: null,
descriptionMd: null,
coverUrl: null,
url: null,
visibility: null,
meetingUrl: null,
geoAddressJson: null,
geoLatitude: null,
geoLongitude: null,
calendarId: null,
}
}
export const LUMA_HOST_OUTPUT_PROPERTIES = {
id: { type: 'string' as const, description: 'Host ID' },
name: { type: 'string' as const, description: 'Host display name' },
firstName: { type: 'string' as const, description: 'Host first name' },
lastName: { type: 'string' as const, description: 'Host last name' },
email: { type: 'string' as const, description: 'Host email address' },
avatarUrl: { type: 'string' as const, description: 'Host avatar image URL' },
}
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 from the first checked-in ticket (ISO 8601)',
},
phoneNumber: { type: 'string' as const, description: 'Guest phone number' },
}
+156
View File
@@ -0,0 +1,156 @@
import type { LumaUpdateEventParams, LumaUpdateEventResponse } from '@/tools/luma/types'
import {
LUMA_EVENT_OUTPUT_PROPERTIES,
LUMA_HOST_OUTPUT_PROPERTIES,
lumaEventStub,
} from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const updateEventTool: ToolConfig<LumaUpdateEventParams, LumaUpdateEventResponse> = {
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<string, unknown> = {
event_id: params.eventId.trim(),
}
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, params) => {
const data = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(data.message || data.error || 'Failed to update event')
}
return {
success: true,
output: {
event: lumaEventStub(params?.eventId?.trim() ?? null),
hosts: [],
},
}
},
postProcess: async (result, params, executeTool) => {
const eventId = result.success ? result.output.event.id : null
if (!eventId) return result
const full = await executeTool('luma_get_event', {
apiKey: params.apiKey,
eventId,
})
if (full.success && full.output?.event) {
return full as LumaUpdateEventResponse
}
return result
},
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,
},
},
},
}
+105
View File
@@ -0,0 +1,105 @@
import type { LumaUpdateGuestStatusParams, LumaUpdateGuestStatusResponse } from '@/tools/luma/types'
import type { ToolConfig } from '@/tools/types'
export const updateGuestStatusTool: ToolConfig<
LumaUpdateGuestStatusParams,
LumaUpdateGuestStatusResponse
> = {
id: 'luma_update_guest_status',
name: 'Luma Update Guest Status',
description:
"Update a guest's approval status on a Luma event — approve, decline, waitlist, or set to pending. Identify the guest by email or guest ID.",
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Luma API key',
},
eventId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Event ID the guest belongs to (starts with evt-)',
},
guestIdentifier: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
"Guest email address or guest ID (gst-...). Values containing '@' are treated as emails; otherwise as a guest ID.",
},
status: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'New approval status: approved, declined, pending_approval, or waitlist',
},
shouldRefund: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
'Refund a paid guest when moving them out of an approved state (defaults to false)',
},
sendEmail: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to email the guest about the status change (defaults to true)',
},
},
request: {
url: 'https://public-api.luma.com/v1/event/update-guest-status',
method: 'POST',
headers: (params) => ({
'x-luma-api-key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const identifier = params.guestIdentifier.trim()
const guest = identifier.includes('@')
? { type: 'email', email: identifier }
: { type: 'api_id', api_id: identifier }
const body: Record<string, unknown> = {
guest,
status: params.status,
}
if (params.eventId) body.event_id = params.eventId.trim()
if (params.shouldRefund !== undefined) body.should_refund = params.shouldRefund
if (params.sendEmail !== undefined) body.send_email = params.sendEmail
return body
},
},
transformResponse: async (response: Response, params) => {
const data = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(data.message || data.error || 'Failed to update guest status')
}
return {
success: true,
output: {
status: params?.status ?? '',
guest: params?.guestIdentifier?.trim() ?? '',
},
}
},
outputs: {
status: {
type: 'string',
description: 'The approval status applied to the guest',
},
guest: {
type: 'string',
description: 'The guest identifier (email or ID) that was updated',
},
},
}