From 163cc9bf53129b58af19a2a0a61590c2ae1d3635 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sun, 9 Mar 2025 13:17:26 -0700 Subject: [PATCH] feat(tools): added guesty tools/block --- blocks/blocks/guesty.ts | 99 +++++++++++++++++++++++++++++++++++++ blocks/index.ts | 3 ++ components/icons.tsx | 18 +++++++ tools/guesty/guest.ts | 84 +++++++++++++++++++++++++++++++ tools/guesty/index.ts | 4 ++ tools/guesty/reservation.ts | 96 +++++++++++++++++++++++++++++++++++ tools/index.ts | 3 ++ 7 files changed, 307 insertions(+) create mode 100644 blocks/blocks/guesty.ts create mode 100644 tools/guesty/guest.ts create mode 100644 tools/guesty/index.ts create mode 100644 tools/guesty/reservation.ts diff --git a/blocks/blocks/guesty.ts b/blocks/blocks/guesty.ts new file mode 100644 index 000000000..6725e450e --- /dev/null +++ b/blocks/blocks/guesty.ts @@ -0,0 +1,99 @@ +import { GuestyIcon } from '@/components/icons' +import { GuestyGuestResponse } from '@/tools/guesty/guest' +import { GuestyReservationResponse } from '@/tools/guesty/reservation' +import { BlockConfig } from '../types' + +export const GuestyBlock: BlockConfig = { + type: 'guesty', + name: 'Guesty', + description: 'Interact with Guesty property management system', + longDescription: + 'Access Guesty property management data including reservations and guest information. Retrieve reservation details by ID or search for guests by phone number.', + category: 'tools', + bgColor: '#0051F8', // Guesty brand color + icon: GuestyIcon, + subBlocks: [ + { + id: 'action', + title: 'Action', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Get Reservation', id: 'reservation' }, + { label: 'Search Guest', id: 'guest' }, + ], + }, + { + id: 'reservationId', + title: 'Reservation ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter reservation ID', + condition: { + field: 'action', + value: 'reservation', + }, + }, + { + id: 'phoneNumber', + title: 'Phone Number', + type: 'short-input', + layout: 'full', + placeholder: 'Enter phone number', + condition: { + field: 'action', + value: 'guest', + }, + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + layout: 'full', + placeholder: 'Enter your Guesty API key', + password: true, + connectionDroppable: false, + }, + ], + tools: { + access: ['guesty_reservation', 'guesty_guest'], + config: { + tool: (params) => { + return params.action === 'reservation' ? 'guesty_reservation' : 'guesty_guest' + }, + params: (params) => { + if (params.action === 'reservation') { + return { + apiKey: params.apiKey, + reservationId: params.reservationId, + } + } else { + return { + apiKey: params.apiKey, + phoneNumber: params.phoneNumber, + } + } + }, + }, + }, + inputs: { + action: { type: 'string', required: true }, + apiKey: { type: 'string', required: true }, + reservationId: { type: 'string', required: false }, + phoneNumber: { type: 'string', required: false }, + }, + outputs: { + response: { + type: { + id: 'string', + guest: 'json', + checkIn: 'string', + checkOut: 'string', + status: 'string', + listing: 'json', + money: 'json', + guests: 'json', + }, + }, + }, +} diff --git a/blocks/index.ts b/blocks/index.ts index 9e80c18c1..97b4ef875 100644 --- a/blocks/index.ts +++ b/blocks/index.ts @@ -10,6 +10,7 @@ import { FirecrawlBlock } from './blocks/firecrawl' import { FunctionBlock } from './blocks/function' import { GitHubBlock } from './blocks/github' import { GmailBlock } from './blocks/gmail' +import { GuestyBlock } from './blocks/guesty' import { JinaBlock } from './blocks/jina' import { NotionBlock } from './blocks/notion' import { OpenAIBlock } from './blocks/openai' @@ -34,6 +35,7 @@ export { FunctionBlock, CrewAIVisionBlock, FirecrawlBlock, + GuestyBlock, JinaBlock, TranslateBlock, SlackBlock, @@ -71,6 +73,7 @@ const blocks: Record = { gmail: GmailBlock, google_drive: GoogleDriveBlock, google_sheets: GoogleSheetsBlock, + guesty: GuestyBlock, jina: JinaBlock, notion: NotionBlock, openai: OpenAIBlock, diff --git a/components/icons.tsx b/components/icons.tsx index deb7480ce..8dcb54451 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -1680,3 +1680,21 @@ export function StripeIcon(props: SVGProps) { ) } + +export function GuestyIcon(props: SVGProps) { + return ( + + + + ) +} diff --git a/tools/guesty/guest.ts b/tools/guesty/guest.ts new file mode 100644 index 000000000..2d7145bc1 --- /dev/null +++ b/tools/guesty/guest.ts @@ -0,0 +1,84 @@ +import { ToolConfig, ToolResponse } from '../types' + +export interface GuestyGuestParams { + apiKey: string + phoneNumber: string +} + +export interface GuestyGuestResponse extends ToolResponse { + output: { + guests: Array<{ + id: string + fullName: string + email: string + phone: string + address: string + city: string + country: string + }> + } +} + +export const guestyGuestTool: ToolConfig = { + id: 'guesty_guest', + name: 'Guesty Guest', + description: 'Search for guests in Guesty by phone number', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + requiredForToolCall: true, + description: 'Your Guesty API token', + }, + phoneNumber: { + type: 'string', + required: true, + description: 'The phone number to search for', + }, + }, + + request: { + url: 'https://open-api.guesty.com/v1/guests', + method: 'GET', + headers: (params: GuestyGuestParams) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params: GuestyGuestParams) => ({ + filters: { + phone: params.phoneNumber, + }, + fields: 'fullName,email,phone,address,city,country', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || 'Failed to search for guests in Guesty') + } + + return { + success: true, + output: { + guests: data.results.map((guest: any) => ({ + id: guest.id, + fullName: guest.fullName || 'N/A', + email: guest.email || 'N/A', + phone: guest.phone || 'N/A', + address: guest.address || 'N/A', + city: guest.city || 'N/A', + country: guest.country || 'N/A', + })), + }, + } + }, + + transformError: (error: any) => { + const message = error.message || 'Failed to search for guests in Guesty' + return message + }, +} diff --git a/tools/guesty/index.ts b/tools/guesty/index.ts new file mode 100644 index 000000000..ed3e953e3 --- /dev/null +++ b/tools/guesty/index.ts @@ -0,0 +1,4 @@ +import { guestyGuestTool } from './guest' +import { guestyReservationTool } from './reservation' + +export { guestyGuestTool, guestyReservationTool } diff --git a/tools/guesty/reservation.ts b/tools/guesty/reservation.ts new file mode 100644 index 000000000..6e48796b8 --- /dev/null +++ b/tools/guesty/reservation.ts @@ -0,0 +1,96 @@ +import { ToolConfig, ToolResponse } from '../types' + +export interface GuestyReservationParams { + apiKey: string + reservationId: string +} + +export interface GuestyReservationResponse extends ToolResponse { + output: { + id: string + guest: { + fullName: string + email: string + phone: string + } + checkIn: string + checkOut: string + status: string + listing: { + id: string + title: string + } + money: { + totalPaid: number + currency: string + } + } +} + +export const guestyReservationTool: ToolConfig = + { + id: 'guesty_reservation', + name: 'Guesty Reservation', + description: 'Fetch reservation details from Guesty by reservation ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + requiredForToolCall: true, + description: 'Your Guesty API token', + }, + reservationId: { + type: 'string', + required: true, + description: 'The ID of the reservation to fetch', + }, + }, + + request: { + url: (params: GuestyReservationParams) => + `https://open-api.guesty.com/v1/reservations/${params.reservationId}`, + method: 'GET', + headers: (params: GuestyReservationParams) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || 'Failed to fetch reservation from Guesty') + } + + return { + success: true, + output: { + id: data.id, + guest: { + fullName: data.guest?.fullName || 'N/A', + email: data.guest?.email || 'N/A', + phone: data.guest?.phone || 'N/A', + }, + checkIn: data.checkIn || 'N/A', + checkOut: data.checkOut || 'N/A', + status: data.status || 'N/A', + listing: { + id: data.listing?.id || 'N/A', + title: data.listing?.title || 'N/A', + }, + money: { + totalPaid: data.money?.totalPaid || 0, + currency: data.money?.currency || 'USD', + }, + }, + } + }, + + transformError: (error: any) => { + const message = error.message || 'Failed to fetch reservation from Guesty' + return message + }, + } diff --git a/tools/index.ts b/tools/index.ts index b7113ba2c..b9c9ec50f 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -13,6 +13,7 @@ import { commentTool } from './github/comment' import { prTool } from './github/pr' import { repoInfoTool } from './github/repo' import { gmailReadTool, gmailSearchTool, gmailSendTool } from './gmail' +import { guestyGuestTool, guestyReservationTool } from './guesty' import { requestTool as httpRequest } from './http/request' import { contactsTool as hubspotContacts } from './hubspot/contacts' import { readUrlTool } from './jina/reader' @@ -82,6 +83,8 @@ export const tools: Record = { google_sheets_read: sheetsReadTool, google_sheets_write: sheetsWriteTool, google_sheets_update: sheetsUpdateTool, + guesty_reservation: guestyReservationTool, + guesty_guest: guestyGuestTool, } // Get a tool by its ID