@elizaos/plugin-blooio 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/actions/sendMessage.ts","../src/constants.ts","../src/utils.ts","../src/providers/conversationHistory.ts","../src/service.ts","../src/types.ts","../src/tests.ts"],"sourcesContent":["import { type IAgentRuntime, type Plugin, logger } from \"@elizaos/core\";\nimport sendMessageAction from \"./actions/sendMessage\";\nimport conversationHistoryProvider from \"./providers/conversationHistory\";\nimport { BlooioService } from \"./service\";\nimport { BlooioTestSuite } from \"./tests\";\n\nconst blooioPlugin: Plugin = {\n name: \"blooio\",\n description: \"Blooio plugin for iMessage/SMS messaging integration\",\n services: [BlooioService],\n actions: [sendMessageAction],\n providers: [conversationHistoryProvider],\n tests: [new BlooioTestSuite()],\n init: async (_config: Record<string, string>, runtime: IAgentRuntime) => {\n const apiKey = runtime.getSetting(\"BLOOIO_API_KEY\") as string;\n const webhookUrl = runtime.getSetting(\"BLOOIO_WEBHOOK_URL\") as string;\n const webhookSecret = runtime.getSetting(\"BLOOIO_WEBHOOK_SECRET\") as string;\n\n if (!apiKey || apiKey.trim() === \"\") {\n logger.warn(\n \"Blooio API key not provided - Blooio plugin is loaded but will not be functional\",\n );\n logger.warn(\n \"To enable Blooio functionality, please provide BLOOIO_API_KEY in your .env file\",\n );\n return;\n }\n\n if (!webhookUrl || webhookUrl.trim() === \"\") {\n logger.warn(\n \"Blooio webhook URL not provided - Blooio will not receive incoming messages\",\n );\n logger.warn(\n \"To enable incoming communication, please provide BLOOIO_WEBHOOK_URL in your .env file\",\n );\n return;\n }\n\n if (!webhookSecret || webhookSecret.trim() === \"\") {\n logger.warn(\n \"Blooio webhook secret not provided - signature verification is disabled\",\n );\n }\n\n logger.info(\"Blooio plugin initialized successfully\");\n },\n};\n\nexport default blooioPlugin;\n","import {\n Action,\n type HandlerCallback,\n type IAgentRuntime,\n type Media,\n type Memory,\n type State,\n logger,\n} from \"@elizaos/core\";\nimport { BLOOIO_SERVICE_NAME } from \"../constants\";\nimport { BlooioService } from \"../service\";\nimport type { BlooioAttachment } from \"../types\";\nimport {\n extractAttachmentUrls,\n extractChatIdCandidates,\n stripChatIdsFromText,\n validateChatId,\n} from \"../utils\";\n\nconst sendMessageAction: Action = {\n name: \"SEND_MESSAGE\",\n description: \"Send a message via Blooio to a chat (phone, email, or group)\",\n validate: async (runtime: IAgentRuntime, message: Memory, _state?: State) => {\n const blooioService = runtime.getService(BLOOIO_SERVICE_NAME);\n if (!blooioService) {\n logger.error(\"Blooio service not found\");\n return false;\n }\n\n const text =\n typeof message.content?.text === \"string\" ? message.content.text : \"\";\n const candidates = extractChatIdCandidates(text);\n return candidates.some((candidate) => validateChatId(candidate));\n },\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n _state?: State,\n _options?: { [key: string]: unknown },\n callback?: HandlerCallback,\n ) => {\n try {\n const blooioService = runtime.getService(\n BLOOIO_SERVICE_NAME,\n ) as BlooioService | null;\n if (!blooioService) {\n throw new Error(\"Blooio service not available\");\n }\n\n const text =\n typeof message.content?.text === \"string\" ? message.content.text : \"\";\n const candidates = extractChatIdCandidates(text);\n const validRecipients = candidates.filter((candidate) =>\n validateChatId(candidate),\n );\n\n if (validRecipients.length === 0) {\n throw new Error(\"No valid chat identifier found in message\");\n }\n\n const chatId = validRecipients\n .map((recipient) => recipient.trim())\n .join(\",\");\n\n // Collect attachments from multiple sources\n const outboundAttachments: Array<string | BlooioAttachment> = [];\n\n // 1. Extract URLs mentioned in the text\n const urlsFromText = extractAttachmentUrls(text);\n for (const url of urlsFromText) {\n outboundAttachments.push(url);\n }\n\n // 2. Add attachments from message.content.attachments (Media[])\n const contentAttachments = message.content?.attachments;\n if (contentAttachments && Array.isArray(contentAttachments)) {\n for (const attachment of contentAttachments) {\n if (typeof attachment === \"object\" && attachment !== null) {\n const media = attachment as Media;\n if (media.url) {\n outboundAttachments.push({\n url: media.url,\n name: media.title ?? media.description ?? undefined,\n });\n }\n }\n }\n }\n\n // Clean up message text\n let messageContent = stripChatIdsFromText(text, validRecipients);\n for (const url of urlsFromText) {\n messageContent = messageContent.replace(url, \"\");\n }\n messageContent = messageContent\n // Remove command phrases like \"send a message\", \"send to\", \"text\", etc.\n .replace(/send\\s+(a\\s+)?(message|text|imessage|sms)?\\s*(to)?\\s*/gi, \"\")\n // Remove standalone \"to\" at the start\n .replace(/^\\s*to\\s+/i, \"\")\n // Remove \"saying\" or \"with\" followed by optional space\n .replace(/^\\s*(saying|with)\\s*/gi, \"\")\n // Remove quotes at start and end\n .replace(/^\\s*[\"']|[\"']\\s*$/g, \"\")\n // Normalize whitespace\n .replace(/\\s+/g, \" \")\n .trim();\n\n // Clear if only command keywords remain\n if (\n /^(send|message|text|imessage|sms|saying|with|to)?$/i.test(\n messageContent,\n )\n ) {\n messageContent = \"\";\n }\n\n if (!messageContent && outboundAttachments.length === 0) {\n messageContent = \"Hello from your assistant.\";\n }\n\n await blooioService.sendMessage(chatId, {\n text: messageContent || undefined,\n attachments:\n outboundAttachments.length > 0 ? outboundAttachments : undefined,\n });\n\n if (callback) {\n await callback({\n text: `Message sent successfully to ${chatId}`,\n success: true,\n });\n }\n } catch (error) {\n logger.error(\n { error: String(error) },\n \"Error sending message via Blooio\",\n );\n if (callback) {\n await callback({\n text: `Failed to send message: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n success: false,\n });\n }\n }\n },\n examples: [\n [\n {\n name: \"user\",\n content: {\n text: \"Send a message to +17147023671 saying 'Hello from Blooio!'\",\n },\n },\n {\n name: \"assistant\",\n content: {\n text: \"I'll send that message.\",\n action: \"SEND_MESSAGE\",\n },\n },\n ],\n [\n {\n name: \"user\",\n content: {\n text: \"Message jane@example.com with 'Your iMessage is ready.'\",\n },\n },\n {\n name: \"assistant\",\n content: {\n text: \"Sending that now.\",\n action: \"SEND_MESSAGE\",\n },\n },\n ],\n ],\n similes: [\"send message\", \"send imessage\", \"text\", \"message\"],\n};\n\nexport default sendMessageAction;\n","export const MESSAGE_CONSTANTS = {\n MAX_MESSAGES: 10,\n RECENT_MESSAGE_COUNT: 3,\n CHAT_HISTORY_COUNT: 5,\n INTEREST_DECAY_TIME: 5 * 60 * 1000, // 5 minutes\n PARTIAL_INTEREST_DECAY: 3 * 60 * 1000, // 3 minutes\n DEFAULT_SIMILARITY_THRESHOLD: 0.3,\n DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS: 0.2,\n} as const;\n\nexport const MESSAGE_LENGTH_THRESHOLDS = {\n LOSE_INTEREST: 100,\n SHORT_MESSAGE: 10,\n VERY_SHORT_MESSAGE: 2,\n IGNORE_RESPONSE: 4,\n} as const;\n\n/**\n * An array of words or phrases that indicate losing interest or annoyance.\n * @type {readonly [\"shut up\", \"stop\", \"please shut up\", \"shut up please\", \"dont talk\", \"silence\", \"stop talking\", \"be quiet\", \"hush\", \"wtf\", \"chill\", \"stfu\", \"stupid bot\", \"dumb bot\", \"stop responding\", \"god damn it\", \"god damn\", \"goddamnit\", \"can you not\", \"can you stop\", \"be quiet\", \"hate you\", \"hate this\", \"fuck up\"]}\n */\nexport const LOSE_INTEREST_WORDS = [\n \"shut up\",\n \"stop\",\n \"please shut up\",\n \"shut up please\",\n \"dont talk\",\n \"silence\",\n \"stop talking\",\n \"be quiet\",\n \"hush\",\n \"wtf\",\n \"chill\",\n \"stfu\",\n \"stupid bot\",\n \"dumb bot\",\n \"stop responding\",\n \"god damn it\",\n \"god damn\",\n \"goddamnit\",\n \"can you not\",\n \"can you stop\",\n \"be quiet\",\n \"hate you\",\n \"hate this\",\n \"fuck up\",\n] as const;\n\nexport const IGNORE_RESPONSE_WORDS = [\n \"lol\",\n \"nm\",\n \"uh\",\n \"wtf\",\n \"stfu\",\n \"dumb\",\n \"jfc\",\n \"omg\",\n] as const;\n\nexport const BLOOIO_SERVICE_NAME = \"blooio\";\n\n// Blooio constants\nexport const BLOOIO_CONSTANTS = {\n API_BASE_URL: \"https://backend.blooio.com/v2/api\",\n WEBHOOK_PATHS: {\n EVENTS: \"/webhook\",\n },\n SIGNATURE_TOLERANCE_SECONDS: 300,\n CACHE_TTL: {\n CONVERSATION: 3600, // 1 hour\n },\n} as const;\n\n// Error messages\nexport const ERROR_MESSAGES = {\n INVALID_CHAT_ID:\n \"Invalid chat identifier. Use E.164 (+15551234567), email, or group id (grp_xxxx).\",\n MISSING_API_KEY: \"Blooio API key not configured\",\n MISSING_WEBHOOK_URL: \"Blooio webhook URL not configured\",\n WEBHOOK_VALIDATION_FAILED: \"Failed to validate Blooio webhook signature\",\n} as const;\n","import crypto from \"crypto\";\nimport { logger } from \"@elizaos/core\";\nimport { BLOOIO_CONSTANTS } from \"./constants\";\n\nconst E164_REGEX = /^\\+\\d{1,15}$/;\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst GROUP_REGEX = /^grp_[A-Za-z0-9]+$/;\n\nexport function isE164(value: string): boolean {\n return E164_REGEX.test(value);\n}\n\nexport function isEmail(value: string): boolean {\n return EMAIL_REGEX.test(value);\n}\n\nexport function isGroupId(value: string): boolean {\n return GROUP_REGEX.test(value);\n}\n\nexport function validateChatId(chatId: string): boolean {\n const parts = chatId\n .split(\",\")\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n\n if (parts.length === 0) {\n return false;\n }\n\n return parts.every(\n (part) => isE164(part) || isEmail(part) || isGroupId(part),\n );\n}\n\nexport function normalizeChatIdList(recipients: string[]): string {\n return recipients\n .map((part) => part.trim())\n .filter(Boolean)\n .join(\",\");\n}\n\nexport function extractChatIdCandidates(text: string): string[] {\n const matches: Array<{ value: string; index: number }> = [];\n\n const capture = (regex: RegExp) => {\n let match: RegExpExecArray | null = regex.exec(text);\n while (match) {\n matches.push({ value: match[0], index: match.index });\n match = regex.exec(text);\n }\n };\n\n capture(/\\+\\d{1,15}/g);\n capture(/\\bgrp_[A-Za-z0-9]+\\b/g);\n capture(/\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g);\n\n matches.sort((a, b) => a.index - b.index);\n const unique: string[] = [];\n for (const match of matches) {\n if (!unique.includes(match.value)) {\n unique.push(match.value);\n }\n }\n return unique;\n}\n\nexport function extractAttachmentUrls(text: string): string[] {\n const urls = text.match(/https?:\\/\\/[^\\s)]+/g) ?? [];\n return Array.from(new Set(urls));\n}\n\nexport function stripChatIdsFromText(text: string, chatIds: string[]): string {\n let cleaned = text;\n for (const chatId of chatIds) {\n const escaped = escapeRegExp(chatId);\n cleaned = cleaned.replace(new RegExp(escaped, \"g\"), \"\");\n }\n return cleaned.trim();\n}\n\nexport function getWebhookPath(webhookUrl: string): string {\n try {\n const parsed = new URL(webhookUrl);\n return parsed.pathname || BLOOIO_CONSTANTS.WEBHOOK_PATHS.EVENTS;\n } catch (error) {\n logger.warn(\n { error: String(error) },\n \"Invalid webhook URL, using default path\",\n );\n return BLOOIO_CONSTANTS.WEBHOOK_PATHS.EVENTS;\n }\n}\n\nexport function verifyWebhookSignature(\n secret: string,\n signatureHeader: string,\n rawBody: string,\n toleranceSeconds: number = BLOOIO_CONSTANTS.SIGNATURE_TOLERANCE_SECONDS,\n): boolean {\n const parsed = parseSignatureHeader(signatureHeader);\n if (!parsed) {\n logger.warn(\n { signatureHeader: signatureHeader.substring(0, 50) },\n \"Failed to parse signature header\",\n );\n return false;\n }\n\n const { timestamp, signature } = parsed;\n const timestampNumber = Number(timestamp);\n if (!Number.isFinite(timestampNumber)) {\n logger.warn({ timestamp }, \"Invalid timestamp in signature\");\n return false;\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000);\n const timeDiff = Math.abs(nowSeconds - timestampNumber);\n if (timeDiff > toleranceSeconds) {\n logger.warn(\n { timestampNumber, nowSeconds, timeDiff, toleranceSeconds },\n \"Webhook signature timestamp out of tolerance\",\n );\n return false;\n }\n\n const payload = `${timestamp}.${rawBody}`;\n const expected = crypto\n .createHmac(\"sha256\", secret)\n .update(payload)\n .digest(\"hex\");\n const isValid = timingSafeEqual(expected, signature);\n\n if (!isValid) {\n logger.warn(\n {\n expectedFirst8: expected.substring(0, 8),\n actualFirst8: signature.substring(0, 8),\n bodyLength: rawBody.length,\n },\n \"Webhook signature mismatch\",\n );\n }\n\n return isValid;\n}\n\nfunction parseSignatureHeader(\n header: string,\n): { timestamp: string; signature: string } | null {\n const parts = header.split(\",\").map((part) => part.trim());\n const timestampPart = parts.find((part) => part.startsWith(\"t=\"));\n const signaturePart = parts.find((part) => part.startsWith(\"v1=\"));\n if (!timestampPart || !signaturePart) {\n return null;\n }\n const timestamp = timestampPart.split(\"=\")[1];\n const signature = signaturePart.split(\"=\")[1];\n if (!timestamp || !signature) {\n return null;\n }\n return { timestamp, signature };\n}\n\nfunction timingSafeEqual(expected: string, actual: string): boolean {\n const expectedBuffer = Buffer.from(expected, \"hex\");\n const actualBuffer = Buffer.from(actual, \"hex\");\n if (expectedBuffer.length !== actualBuffer.length) {\n return false;\n }\n return crypto.timingSafeEqual(expectedBuffer, actualBuffer);\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import type { IAgentRuntime, Memory, Provider, State } from \"@elizaos/core\";\nimport { BLOOIO_SERVICE_NAME } from \"../constants\";\nimport { BlooioService } from \"../service\";\n\nconst conversationHistoryProvider: Provider = {\n name: \"blooioConversationHistory\",\n description: \"Provides recent Blooio conversation history with a chat\",\n get: async (runtime: IAgentRuntime, message: Memory, _state?: State) => {\n try {\n const blooioService = runtime.getService(\n BLOOIO_SERVICE_NAME,\n ) as BlooioService | null;\n if (!blooioService) {\n return {\n text: \"No Blooio conversation history available - service not initialized\",\n };\n }\n\n if (typeof message.content === \"string\") {\n return {\n text: \"No chat identifier found in context\",\n };\n }\n\n const chatId =\n typeof message.content?.chatId === \"string\"\n ? message.content.chatId\n : typeof message.content?.phoneNumber === \"string\"\n ? message.content.phoneNumber\n : message.content?.text?.match(\n /(\\+\\d{1,15}|grp_[A-Za-z0-9]+|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,})/,\n )?.[0];\n\n if (!chatId || typeof chatId !== \"string\") {\n return {\n text: \"No chat identifier found in context\",\n };\n }\n\n const conversationHistory = blooioService.getConversationHistory(\n chatId,\n 10,\n );\n if (!conversationHistory || conversationHistory.length === 0) {\n return {\n text: `No recent conversation history with ${chatId}`,\n };\n }\n\n const history = conversationHistory\n .map((msg) => {\n const direction = msg.direction === \"inbound\" ? \"From\" : \"To\";\n const time = new Date(msg.timestamp).toLocaleString();\n const text = msg.text ?? \"(no text)\";\n return `[${time}] ${direction} ${chatId}: ${text}`;\n })\n .join(\"\\n\");\n\n return {\n text: `Recent Blooio conversation with ${chatId}:\\n${history}`,\n data: {\n chatId,\n messageCount: conversationHistory.length,\n lastMessage: conversationHistory[conversationHistory.length - 1],\n },\n };\n } catch (error) {\n console.error(\"Error in conversationHistoryProvider:\", error);\n return {\n text: \"Error retrieving conversation history\",\n };\n }\n },\n};\n\nexport default conversationHistoryProvider;\n","import {\n ChannelType,\n ContentType,\n EventType,\n Service,\n createMessageMemory,\n createUniqueUuid,\n logger,\n stringToUuid,\n type HandlerCallback,\n type IAgentRuntime,\n type Media,\n type Memory,\n} from \"@elizaos/core\";\nimport bodyParser from \"body-parser\";\nimport express, { type Express, type Request, type Response } from \"express\";\nimport type { Server } from \"http\";\nimport NodeCache from \"node-cache\";\nimport {\n BLOOIO_CONSTANTS,\n BLOOIO_SERVICE_NAME,\n ERROR_MESSAGES,\n} from \"./constants\";\nimport {\n CACHE_KEYS,\n type BlooioConfig,\n BlooioError,\n type BlooioMessage,\n type BlooioMessageDeliveredEvent,\n type BlooioMessageFailedEvent,\n type BlooioMessageReadEvent,\n type BlooioMessageReceivedEvent,\n type BlooioMessageSentEvent,\n type BlooioSendMessageRequest,\n type BlooioSendMessageResponse,\n type BlooioServiceInterface,\n type BlooioWebhookEvent,\n} from \"./types\";\nimport {\n extractAttachmentUrls,\n getWebhookPath,\n isE164,\n validateChatId,\n verifyWebhookSignature,\n} from \"./utils\";\n\ntype MessageService = {\n handleMessage: (\n runtime: IAgentRuntime,\n message: Memory,\n callback?: HandlerCallback,\n ) => Promise<void>;\n};\n\ntype RawBodyRequest = Request & { rawBody?: string };\n\nconst getMessageService = (runtime: IAgentRuntime): MessageService | null => {\n if (\"messageService\" in runtime) {\n const withMessageService = runtime as IAgentRuntime & {\n messageService?: MessageService | null;\n };\n return withMessageService.messageService ?? null;\n }\n return null;\n};\n\nexport class BlooioService extends Service implements BlooioServiceInterface {\n static serviceType: string = BLOOIO_SERVICE_NAME;\n\n static async start(runtime: IAgentRuntime): Promise<BlooioService> {\n const service = new BlooioService();\n await service.initialize(runtime);\n return service;\n }\n\n static async stop(_runtime: IAgentRuntime): Promise<void> {\n return;\n }\n\n private blooioConfig!: BlooioConfig;\n private app!: Express;\n private server: Server | null = null;\n private cache: NodeCache;\n private isInitialized = false;\n\n constructor() {\n super();\n this.cache = new NodeCache({ stdTTL: 600 });\n }\n\n async initialize(runtime: IAgentRuntime): Promise<void> {\n if (this.isInitialized) {\n logger.warn(\"BlooioService already initialized\");\n return;\n }\n\n this.runtime = runtime;\n\n const apiKey = runtime.getSetting(\"BLOOIO_API_KEY\") as string;\n const webhookUrl = runtime.getSetting(\"BLOOIO_WEBHOOK_URL\") as string;\n const webhookSecret = runtime.getSetting(\"BLOOIO_WEBHOOK_SECRET\") as string;\n const baseUrlSetting = runtime.getSetting(\"BLOOIO_BASE_URL\") as string;\n const webhookPortSetting = runtime.getSetting(\n \"BLOOIO_WEBHOOK_PORT\",\n ) as string;\n const fromNumber = runtime.getSetting(\"BLOOIO_FROM_NUMBER\") as string;\n const signatureToleranceSetting = runtime.getSetting(\n \"BLOOIO_SIGNATURE_TOLERANCE_SEC\",\n ) as string;\n const webhookPathSetting = runtime.getSetting(\n \"BLOOIO_WEBHOOK_PATH\",\n ) as string;\n\n const webhookPort = Number.parseInt(webhookPortSetting || \"3001\", 10);\n const signatureToleranceSeconds = Number.parseInt(\n signatureToleranceSetting || \"\",\n 10,\n );\n const resolvedSignatureTolerance =\n Number.isFinite(signatureToleranceSeconds) &&\n signatureToleranceSeconds > 0\n ? signatureToleranceSeconds\n : BLOOIO_CONSTANTS.SIGNATURE_TOLERANCE_SECONDS;\n\n if (!apiKey || apiKey.trim() === \"\") {\n throw new BlooioError(ERROR_MESSAGES.MISSING_API_KEY);\n }\n\n if (!webhookUrl || webhookUrl.trim() === \"\") {\n throw new BlooioError(ERROR_MESSAGES.MISSING_WEBHOOK_URL);\n }\n\n const webhookPathRaw =\n webhookPathSetting && webhookPathSetting.trim() !== \"\"\n ? webhookPathSetting\n : getWebhookPath(webhookUrl);\n const webhookPath = webhookPathRaw.startsWith(\"/\")\n ? webhookPathRaw\n : `/${webhookPathRaw}`;\n\n this.blooioConfig = {\n apiKey,\n webhookUrl,\n webhookPath,\n webhookPort: Number.isFinite(webhookPort) ? webhookPort : 3001,\n webhookSecret: webhookSecret?.trim() ? webhookSecret : undefined,\n baseUrl: baseUrlSetting?.trim()\n ? baseUrlSetting.trim()\n : BLOOIO_CONSTANTS.API_BASE_URL,\n fromNumber: fromNumber?.trim() ? fromNumber.trim() : undefined,\n signatureToleranceSeconds: resolvedSignatureTolerance,\n };\n\n if (!this.blooioConfig.webhookSecret) {\n logger.warn(\n \"Blooio webhook secret not set; signature validation disabled\",\n );\n }\n\n await this.setupWebhookServer();\n\n this.isInitialized = true;\n logger.info(\"BlooioService initialized successfully\");\n }\n\n async stop(): Promise<void> {\n await this.cleanup();\n }\n\n get capabilityDescription(): string {\n return \"Blooio iMessage/SMS integration service for bidirectional messaging\";\n }\n\n private async setupWebhookServer(): Promise<void> {\n this.app = express();\n\n this.app.use(\n bodyParser.json({\n verify: (req, _res, buf) => {\n const typed = req as RawBodyRequest;\n typed.rawBody = buf.toString(\"utf8\");\n },\n }),\n );\n\n // Health check endpoint\n this.app.get(\"/health\", (_req, res) => {\n res.json({ status: \"ok\", service: \"blooio\" });\n });\n\n this.app.post(\n this.blooioConfig.webhookPath,\n async (req: RawBodyRequest, res: Response) => {\n logger.info(\n {\n path: req.path,\n method: req.method,\n headers: {\n \"X-Blooio-Event\": req.header(\"X-Blooio-Event\"),\n \"X-Blooio-Message-Id\": req.header(\"X-Blooio-Message-Id\"),\n \"Content-Type\": req.header(\"Content-Type\"),\n },\n },\n \"Blooio webhook request received\",\n );\n\n try {\n const signatureHeader = req.header(\"X-Blooio-Signature\") ?? \"\";\n const eventHeader = req.header(\"X-Blooio-Event\") ?? \"\";\n const rawBody = typeof req.rawBody === \"string\" ? req.rawBody : \"\";\n\n if (this.blooioConfig.webhookSecret) {\n const valid = verifyWebhookSignature(\n this.blooioConfig.webhookSecret,\n signatureHeader,\n rawBody,\n this.blooioConfig.signatureToleranceSeconds,\n );\n if (!valid) {\n logger.warn(\"Blooio webhook signature validation failed\");\n res.status(401).send(ERROR_MESSAGES.WEBHOOK_VALIDATION_FAILED);\n return;\n }\n }\n\n const payload = req.body as BlooioWebhookEvent;\n if (!payload || typeof payload.event !== \"string\") {\n logger.warn({ body: req.body }, \"Invalid webhook payload received\");\n res.status(400).send(\"Invalid webhook payload\");\n return;\n }\n\n logger.info(\n {\n event: payload.event,\n message_id: payload.message_id,\n external_id: payload.external_id,\n },\n \"Processing Blooio webhook event\",\n );\n\n if (eventHeader && payload.event !== eventHeader) {\n logger.warn(\n { eventHeader, payloadEvent: payload.event },\n \"Blooio webhook event header mismatch\",\n );\n }\n\n await this.handleWebhookEvent(payload);\n logger.info(\n { event: payload.event },\n \"Blooio webhook processed successfully\",\n );\n res.sendStatus(200);\n } catch (error) {\n logger.error(\n { error: String(error) },\n \"Error handling Blooio webhook\",\n );\n res.sendStatus(500);\n }\n },\n );\n\n this.server = this.app.listen(this.blooioConfig.webhookPort, () => {\n logger.info(\n `Blooio webhook server listening on port ${this.blooioConfig.webhookPort} (${this.blooioConfig.webhookPath})`,\n );\n });\n }\n\n async sendMessage(\n chatId: string,\n request: BlooioSendMessageRequest,\n ): Promise<BlooioSendMessageResponse> {\n const normalizedChatId = chatId\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean)\n .join(\",\");\n\n if (!validateChatId(normalizedChatId)) {\n throw new BlooioError(ERROR_MESSAGES.INVALID_CHAT_ID);\n }\n\n const url = `${this.blooioConfig.baseUrl}/chats/${encodeURIComponent(normalizedChatId)}/messages`;\n const payload = {\n text: request.text,\n attachments: request.attachments,\n metadata: request.metadata,\n use_typing_indicator: request.use_typing_indicator,\n fromNumber: request.fromNumber ?? this.blooioConfig.fromNumber,\n };\n const cleanedPayload = Object.fromEntries(\n Object.entries(payload).filter(([, value]) => value !== undefined),\n );\n\n const idempotencyKey = request.idempotencyKey?.trim();\n const fromNumber = request.fromNumber ?? this.blooioConfig.fromNumber;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.blooioConfig.apiKey}`,\n \"Content-Type\": \"application/json\",\n ...(idempotencyKey ? { \"Idempotency-Key\": idempotencyKey } : {}),\n ...(fromNumber ? { \"X-From-Number\": fromNumber } : {}),\n },\n body: JSON.stringify(cleanedPayload),\n });\n\n const responseText = await response.text();\n if (!response.ok) {\n throw new BlooioError(\n `Blooio API error (${response.status})`,\n response.status,\n responseText,\n );\n }\n\n let data: BlooioSendMessageResponse;\n try {\n data = JSON.parse(responseText) as BlooioSendMessageResponse;\n } catch (error) {\n throw new BlooioError(\n \"Invalid JSON response from Blooio\",\n response.status,\n );\n }\n\n const messageId =\n data.message_id || (data.message_ids ? data.message_ids[0] : undefined);\n const message: BlooioMessage = {\n messageId:\n messageId ??\n createUniqueUuid(this.runtime, `${normalizedChatId}:${Date.now()}`),\n chatId: normalizedChatId,\n sender: fromNumber ?? \"blooio\",\n text: typeof request.text === \"string\" ? request.text : undefined,\n attachments: Array.isArray(request.attachments)\n ? request.attachments.map((item) =>\n typeof item === \"string\" ? item : item.url,\n )\n : undefined,\n direction: \"outbound\",\n status: data.status,\n timestamp: Date.now(),\n };\n\n this.cacheMessage(normalizedChatId, message);\n if (this.runtime) {\n this.runtime.emitEvent(\"blooio:message:sent\", message);\n }\n\n return data;\n }\n\n private async handleWebhookEvent(event: BlooioWebhookEvent): Promise<void> {\n switch (event.event) {\n case \"message.received\":\n await this.handleIncomingMessage(event);\n break;\n case \"message.sent\":\n this.handleMessageSent(event);\n break;\n case \"message.delivered\":\n this.handleMessageDelivered(event);\n break;\n case \"message.failed\":\n this.handleMessageFailed(event);\n break;\n case \"message.read\":\n this.handleMessageRead(event);\n break;\n case \"group.name_changed\":\n case \"group.icon_changed\":\n if (this.runtime) {\n this.runtime.emitEvent(`blooio:${event.event}`, event);\n }\n break;\n default:\n break;\n }\n }\n\n private handleMessageSent(event: BlooioMessageSentEvent): void {\n if (this.runtime) {\n this.runtime.emitEvent(\"blooio:message:sent\", event);\n }\n }\n\n private handleMessageDelivered(event: BlooioMessageDeliveredEvent): void {\n if (this.runtime) {\n this.runtime.emitEvent(\"blooio:message:delivered\", event);\n }\n }\n\n private handleMessageFailed(event: BlooioMessageFailedEvent): void {\n if (this.runtime) {\n this.runtime.emitEvent(\"blooio:message:failed\", event);\n }\n }\n\n private handleMessageRead(event: BlooioMessageReadEvent): void {\n if (this.runtime) {\n this.runtime.emitEvent(\"blooio:message:read\", event);\n }\n }\n\n private async handleIncomingMessage(\n webhook: BlooioMessageReceivedEvent,\n ): Promise<void> {\n const chatId = webhook.external_id ?? webhook.sender;\n if (!chatId) {\n logger.warn(\"Blooio webhook missing chat identifier\");\n return;\n }\n\n const inboundMessage: BlooioMessage = {\n messageId:\n webhook.message_id ??\n createUniqueUuid(this.runtime, `${chatId}:${Date.now()}`),\n chatId,\n sender: webhook.sender,\n text: webhook.text,\n attachments: this.normalizeAttachmentUrls(webhook.attachments),\n direction: \"inbound\",\n protocol: webhook.protocol,\n timestamp: webhook.received_at ?? webhook.timestamp,\n internalId: webhook.internal_id,\n };\n\n this.cacheMessage(chatId, inboundMessage);\n\n if (this.runtime) {\n this.runtime.emitEvent(\"blooio:message:received\", inboundMessage);\n }\n\n await this.processIncomingMessage(webhook, inboundMessage);\n }\n\n private async processIncomingMessage(\n webhook: BlooioMessageReceivedEvent,\n message: BlooioMessage,\n ): Promise<void> {\n try {\n const text = message.text?.trim();\n const hasAttachments =\n message.attachments && message.attachments.length > 0;\n if (!text && !hasAttachments) {\n return;\n }\n\n const channelType = webhook.is_group ? ChannelType.GROUP : ChannelType.DM;\n const entityId = createUniqueUuid(this.runtime, webhook.sender);\n const roomId = createUniqueUuid(this.runtime, `blooio:${message.chatId}`);\n const worldId = createUniqueUuid(\n this.runtime,\n `blooio:${webhook.internal_id ?? \"unknown\"}`,\n );\n\n await this.runtime.ensureConnection({\n entityId,\n roomId,\n worldId,\n userName: webhook.sender,\n source: \"blooio\",\n channelId: message.chatId,\n type: channelType,\n });\n\n // Use original webhook attachments to preserve names\n const attachments = this.buildMediaFromBlooioAttachments(\n webhook.attachments,\n );\n const memory = createMessageMemory({\n id: stringToUuid(message.messageId),\n entityId,\n roomId,\n content: {\n text: text ?? \"\",\n source: \"blooio\",\n channelType,\n chatId: message.chatId,\n phoneNumber: isE164(message.chatId) ? message.chatId : undefined,\n protocol: message.protocol,\n attachments: attachments.length > 0 ? attachments : undefined,\n },\n });\n\n const callback: HandlerCallback = async (content) => {\n const responseText =\n typeof content.text === \"string\" ? content.text.trim() : \"\";\n\n // Collect attachments from content.attachments (Media[]) and URLs in text\n const outboundAttachments: Array<\n string | { url: string; name?: string }\n > = [];\n\n // Add attachments from content.attachments (images, video, audio, etc.)\n if (content.attachments && Array.isArray(content.attachments)) {\n for (const attachment of content.attachments) {\n if (typeof attachment === \"object\" && attachment !== null) {\n const media = attachment as Media;\n if (media.url) {\n outboundAttachments.push({\n url: media.url,\n name: media.title ?? media.description ?? undefined,\n });\n }\n }\n }\n }\n\n // Also extract any URLs from the text itself\n const urlsFromText = extractAttachmentUrls(responseText);\n for (const url of urlsFromText) {\n outboundAttachments.push(url);\n }\n\n // Skip if nothing to send\n if (!responseText && outboundAttachments.length === 0) {\n return [];\n }\n\n await this.sendMessage(message.chatId, {\n text: responseText || undefined,\n attachments:\n outboundAttachments.length > 0 ? outboundAttachments : undefined,\n fromNumber: this.blooioConfig.fromNumber ?? webhook.internal_id,\n });\n return [];\n };\n\n const messageService = getMessageService(this.runtime);\n if (messageService) {\n await messageService.handleMessage(this.runtime, memory, callback);\n } else {\n logger.warn(\"messageService unavailable; falling back to event emit\");\n await this.runtime.emitEvent(EventType.MESSAGE_RECEIVED, {\n message: memory,\n callback,\n source: \"blooio\",\n });\n }\n } catch (error) {\n logger.error({ error: String(error) }, \"Error processing Blooio message\");\n }\n }\n\n private buildMediaAttachments(attachments?: string[]): Media[] {\n if (!attachments || attachments.length === 0) {\n return [];\n }\n\n return attachments.map((url) => ({\n id: createUniqueUuid(this.runtime, url),\n url,\n contentType: this.resolveContentType(url),\n }));\n }\n\n /**\n * Build Media attachments from Blooio webhook attachments (preserves names)\n */\n private buildMediaFromBlooioAttachments(\n attachments: Array<string | { url: string; name?: string }> | undefined,\n ): Media[] {\n if (!attachments || attachments.length === 0) {\n return [];\n }\n\n return attachments.map((item) => {\n const url = typeof item === \"string\" ? item : item.url;\n const name = typeof item === \"object\" ? item.name : undefined;\n return {\n id: createUniqueUuid(this.runtime, url),\n url,\n title: name,\n contentType: this.resolveContentType(url),\n };\n });\n }\n\n private normalizeAttachmentUrls(\n attachments: Array<string | { url: string; name?: string }> | undefined,\n ): string[] | undefined {\n if (!attachments || attachments.length === 0) {\n return undefined;\n }\n\n return attachments\n .map((item) => (typeof item === \"string\" ? item : item.url))\n .filter((url) => typeof url === \"string\" && url.length > 0);\n }\n\n private resolveContentType(url: string): ContentType | undefined {\n const lower = url.toLowerCase();\n // Image formats\n if (lower.match(/\\.(png|jpe?g|gif|webp|bmp|svg|ico|heic|heif|tiff?)$/)) {\n return ContentType.IMAGE;\n }\n // Video formats\n if (lower.match(/\\.(mp4|mov|webm|avi|mkv|m4v|3gp|flv|wmv)$/)) {\n return ContentType.VIDEO;\n }\n // Audio formats\n if (lower.match(/\\.(mp3|wav|m4a|ogg|aac|flac|wma|aiff?)$/)) {\n return ContentType.AUDIO;\n }\n // Document/other\n return ContentType.DOCUMENT;\n }\n\n /**\n * Resolve ContentType from a MIME type string (e.g., from webhook payload)\n */\n private resolveContentTypeFromMime(\n mimeType: string,\n ): ContentType | undefined {\n const lower = mimeType.toLowerCase();\n if (lower.startsWith(\"image/\")) {\n return ContentType.IMAGE;\n }\n if (lower.startsWith(\"video/\")) {\n return ContentType.VIDEO;\n }\n if (lower.startsWith(\"audio/\")) {\n return ContentType.AUDIO;\n }\n return ContentType.DOCUMENT;\n }\n\n private cacheMessage(chatId: string, message: BlooioMessage): void {\n const key = CACHE_KEYS.CONVERSATION(chatId);\n const history = (this.cache.get(key) as BlooioMessage[] | undefined) ?? [];\n history.push(message);\n const trimmed = history.length > 50 ? history.slice(-50) : history;\n this.cache.set(key, trimmed, BLOOIO_CONSTANTS.CACHE_TTL.CONVERSATION);\n }\n\n async cleanup(): Promise<void> {\n if (this.server) {\n this.server.close();\n }\n this.cache.flushAll();\n this.isInitialized = false;\n logger.info(\"BlooioService cleaned up\");\n }\n\n get serviceType(): string {\n return BLOOIO_SERVICE_NAME;\n }\n\n get serviceName(): string {\n return \"blooio\";\n }\n\n get isConnected(): boolean {\n return this.isInitialized;\n }\n\n getConversationHistory(chatId: string, limit: number = 10): BlooioMessage[] {\n const cacheKey = CACHE_KEYS.CONVERSATION(chatId);\n const messages = this.cache.get(cacheKey) as BlooioMessage[] | undefined;\n if (!messages) {\n return [];\n }\n return messages.slice(-limit);\n }\n\n get defaultFromNumber(): string | undefined {\n return this.blooioConfig.fromNumber;\n }\n}\n","import { z } from \"zod\";\n\nexport type BlooioProtocol = \"imessage\" | \"sms\" | \"rcs\" | \"non-imessage\";\n\nexport type BlooioEventType =\n | \"message.received\"\n | \"message.sent\"\n | \"message.delivered\"\n | \"message.failed\"\n | \"message.read\"\n | \"group.name_changed\"\n | \"group.icon_changed\";\n\nexport interface BlooioAttachment {\n url: string;\n name?: string;\n}\n\nexport interface BlooioMessage {\n messageId: string;\n chatId: string;\n sender: string;\n text?: string;\n attachments?: string[];\n direction: \"inbound\" | \"outbound\";\n status?: string;\n protocol?: BlooioProtocol;\n timestamp: number;\n internalId?: string;\n}\n\nexport interface BlooioSendMessageRequest {\n text?: string | string[];\n attachments?: Array<string | BlooioAttachment>;\n metadata?: Record<string, string | number | boolean | null>;\n use_typing_indicator?: boolean;\n fromNumber?: string;\n idempotencyKey?: string;\n}\n\nexport interface BlooioSendMessageResponse {\n message_id?: string;\n message_ids?: string[];\n status: string;\n group_id?: string;\n group_created?: boolean;\n participants?: string[];\n}\n\nexport interface BlooioWebhookBase {\n event: BlooioEventType;\n message_id?: string;\n external_id?: string;\n protocol?: BlooioProtocol;\n timestamp: number;\n internal_id?: string;\n}\n\nexport interface BlooioGroupParticipant {\n contact_id: string;\n identifier: string;\n name: string | null;\n}\n\nexport interface BlooioMessageReceivedEvent extends BlooioWebhookBase {\n event: \"message.received\";\n text?: string;\n attachments?: Array<string | BlooioAttachment>;\n received_at: number;\n sender: string;\n is_group: boolean;\n group_id?: string | null;\n group_name?: string | null;\n participants?: BlooioGroupParticipant[] | null;\n}\n\nexport interface BlooioMessageSentEvent extends BlooioWebhookBase {\n event: \"message.sent\";\n text?: string;\n attachments?: Array<string | BlooioAttachment>;\n sent_at: number;\n}\n\nexport interface BlooioMessageDeliveredEvent extends BlooioWebhookBase {\n event: \"message.delivered\";\n delivered_at: number;\n}\n\nexport interface BlooioMessageFailedEvent extends BlooioWebhookBase {\n event: \"message.failed\";\n error_code: string;\n error_message: string;\n}\n\nexport interface BlooioMessageReadEvent extends BlooioWebhookBase {\n event: \"message.read\";\n read_at: number;\n}\n\nexport interface BlooioGroupNameChangedEvent extends BlooioWebhookBase {\n event: \"group.name_changed\";\n group_id: string;\n name: string;\n previous_name: string | null;\n}\n\nexport interface BlooioGroupIconChangedEvent extends BlooioWebhookBase {\n event: \"group.icon_changed\";\n group_id: string;\n icon_url: string;\n previous_icon_url: string | null;\n}\n\nexport type BlooioWebhookEvent =\n | BlooioMessageReceivedEvent\n | BlooioMessageSentEvent\n | BlooioMessageDeliveredEvent\n | BlooioMessageFailedEvent\n | BlooioMessageReadEvent\n | BlooioGroupNameChangedEvent\n | BlooioGroupIconChangedEvent;\n\nexport interface BlooioConfig {\n apiKey: string;\n webhookUrl: string;\n webhookPath: string;\n webhookPort: number;\n webhookSecret?: string;\n baseUrl: string;\n fromNumber?: string;\n signatureToleranceSeconds: number;\n}\n\nexport class BlooioError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public details?: string,\n ) {\n super(message);\n this.name = \"BlooioError\";\n }\n}\n\nexport interface BlooioServiceInterface {\n sendMessage(\n chatId: string,\n request: BlooioSendMessageRequest,\n ): Promise<BlooioSendMessageResponse>;\n}\n\nexport const SendMessageSchema = z.object({\n chatId: z.string().min(1),\n text: z.string().min(1).optional(),\n attachments: z.array(z.string().url()).optional(),\n});\n\nexport const CACHE_KEYS = {\n CONVERSATION: (chatId: string) => `blooio:conversation:${chatId}`,\n} as const;\n","import {\n type IAgentRuntime,\n type TestCase,\n type TestSuite,\n logger,\n} from \"@elizaos/core\";\nimport { BlooioService } from \"./service\";\nimport { BLOOIO_SERVICE_NAME } from \"./constants\";\n\nexport class BlooioTestSuite implements TestSuite {\n name = \"Blooio Plugin Test Suite\";\n description = \"Tests for Blooio iMessage/SMS functionality\";\n\n tests: TestCase[] = [\n {\n name: \"Service Initialization Test\",\n fn: async (runtime: IAgentRuntime) => {\n const blooioService = runtime.getService(\n BLOOIO_SERVICE_NAME,\n ) as BlooioService | null;\n if (!blooioService) {\n throw new Error(\"Blooio service not initialized\");\n }\n\n if (!blooioService.isConnected) {\n throw new Error(\"Blooio service is not connected\");\n }\n\n logger.info(\"✅ Blooio service initialized\");\n },\n },\n {\n name: \"Send Message Test\",\n fn: async (runtime: IAgentRuntime) => {\n const blooioService = runtime.getService(\n BLOOIO_SERVICE_NAME,\n ) as BlooioService | null;\n if (!blooioService) {\n throw new Error(\"Blooio service not initialized\");\n }\n\n const testChatId = runtime.getSetting(\"BLOOIO_TEST_CHAT_ID\") as string;\n if (!testChatId) {\n logger.warn(\"BLOOIO_TEST_CHAT_ID not set, skipping send test\");\n return;\n }\n\n const result = await blooioService.sendMessage(testChatId, {\n text: \"Test message from Eliza Blooio plugin\",\n });\n\n logger.info(`✅ Message test queued. Status: ${result.status}`);\n },\n },\n {\n name: \"Conversation History Test\",\n fn: async (runtime: IAgentRuntime) => {\n const blooioService = runtime.getService(\n BLOOIO_SERVICE_NAME,\n ) as BlooioService | null;\n if (!blooioService) {\n throw new Error(\"Blooio service not initialized\");\n }\n\n const testChatId = runtime.getSetting(\"BLOOIO_TEST_CHAT_ID\") as string;\n if (!testChatId) {\n logger.warn(\"BLOOIO_TEST_CHAT_ID not set, skipping history test\");\n return;\n }\n\n await blooioService.sendMessage(testChatId, {\n text: \"History test message\",\n });\n\n const history = blooioService.getConversationHistory(testChatId, 5);\n if (history.length > 0) {\n logger.info(\n `✅ Conversation history retrieved: ${history.length} messages`,\n );\n logger.info(\n ` Latest message: ${history[history.length - 1].text ?? \"(no text)\"}`,\n );\n } else {\n logger.info(\n \"✅ Conversation history is empty (expected for new chat)\",\n );\n }\n },\n },\n {\n name: \"Error Handling Test\",\n fn: async (runtime: IAgentRuntime) => {\n const blooioService = runtime.getService(\n BLOOIO_SERVICE_NAME,\n ) as BlooioService | null;\n if (!blooioService) {\n throw new Error(\"Blooio service not initialized\");\n }\n\n try {\n await blooioService.sendMessage(\"invalid-chat-id\", { text: \"Test\" });\n throw new Error(\"Expected error for invalid chat id\");\n } catch (error) {\n logger.info(\"✅ Invalid chat id error handled correctly\");\n }\n },\n },\n ];\n}\n"],"mappings":"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","names":["logger","logger","logger","logger","logger","logger","logger","logger"]}
package/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "name": "@elizaos/plugin-blooio",
3
+ "version": "2.0.0-alpha.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/elizaos-plugins/plugin-blooio.git"
11
+ },
12
+ "exports": {
13
+ "./package.json": "./package.json",
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "dependencies": {
25
+ "@elizaos/core": "2.0.0-alpha.1",
26
+ "body-parser": "^1.20.2",
27
+ "express": "^4.18.2",
28
+ "node-cache": "^5.1.2",
29
+ "zod": "3.24.2"
30
+ },
31
+ "devDependencies": {
32
+ "@types/express": "^4.17.21",
33
+ "prettier": "3.5.3",
34
+ "tsup": "8.4.0",
35
+ "vitest": "1.6.1"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "dev": "tsup --watch",
40
+ "test": "elizaos test",
41
+ "test:unit": "vitest",
42
+ "lint": "prettier --write ./src",
43
+ "clean": "rm -rf dist .turbo node_modules .turbo-tsconfig.json tsconfig.tsbuildinfo",
44
+ "format": "prettier --write ./src",
45
+ "format:check": "prettier --check ./src"
46
+ },
47
+ "peerDependencies": {
48
+ "whatwg-url": "7.1.0"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "gitHead": "05d4ca11d769db8c7f54a722ee24b2ce2b951543",
54
+ "agentConfig": {
55
+ "pluginType": "elizaos:plugin:1.0.0",
56
+ "pluginParameters": {
57
+ "BLOOIO_API_KEY": {
58
+ "type": "string",
59
+ "description": "Blooio API key used to authenticate with the Blooio API",
60
+ "required": true,
61
+ "sensitive": true
62
+ },
63
+ "BLOOIO_WEBHOOK_URL": {
64
+ "type": "string",
65
+ "description": "Public URL for receiving Blooio webhooks (e.g., https://your-domain.com/webhook)",
66
+ "required": true,
67
+ "sensitive": false
68
+ },
69
+ "BLOOIO_WEBHOOK_SECRET": {
70
+ "type": "string",
71
+ "description": "Signing secret used to verify Blooio webhooks",
72
+ "required": true,
73
+ "sensitive": true
74
+ },
75
+ "BLOOIO_WEBHOOK_PORT": {
76
+ "type": "string",
77
+ "description": "Port to run the webhook server on (default: 3001)",
78
+ "required": false,
79
+ "sensitive": false
80
+ },
81
+ "BLOOIO_WEBHOOK_PATH": {
82
+ "type": "string",
83
+ "description": "Path to mount the webhook server (default: derived from BLOOIO_WEBHOOK_URL)",
84
+ "required": false,
85
+ "sensitive": false
86
+ },
87
+ "BLOOIO_BASE_URL": {
88
+ "type": "string",
89
+ "description": "Override Blooio API base URL",
90
+ "required": false,
91
+ "sensitive": false
92
+ },
93
+ "BLOOIO_FROM_NUMBER": {
94
+ "type": "string",
95
+ "description": "E.164 number to send messages from (must be assigned to your org)",
96
+ "required": false,
97
+ "sensitive": false
98
+ },
99
+ "BLOOIO_TEST_CHAT_ID": {
100
+ "type": "string",
101
+ "description": "Chat identifier to use for tests (phone, email, or group id)",
102
+ "required": false,
103
+ "sensitive": false
104
+ }
105
+ }
106
+ }
107
+ }