diff --git a/Makefile b/Makefile index d16270e..45db498 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,10 @@ sync-to-gitea: # Declaramos el target como PHONY ya que no corresponde a un archivo real (opcional pero recomendado) -.PHONY: UI +.PHONY: UI agent UI: cd ui && ( if not exist node_modules npm install ) && npm run dev + +agent: + cd agent && npm install && npm run dev + diff --git a/agent/.gitignore b/agent/.gitignore deleted file mode 100644 index 2e7e202..0000000 --- a/agent/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# Dependencies -node_modules/ - -# Environment variables -.env -.env.* - -# Logs -logs/ -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -# Build output -dist/ -coverage/ - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea/ -.DS_Store diff --git a/agent/Dockerfile b/agent/Dockerfile deleted file mode 100644 index f2918c7..0000000 --- a/agent/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# Dockerfile con acceso a docker CLI -FROM node:23-slim - -WORKDIR /app - -# 1) Instalar dependencias normales -COPY package.json package-lock.json* ./ -RUN npm install --omit=dev - -# 2) Instalar Docker CLI -RUN apt-get update && \ - apt-get install -y ca-certificates curl gnupg && \ - install -m 0755 -d /etc/apt/keyrings && \ - curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \ - chmod a+r /etc/apt/keyrings/docker.gpg && \ - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable" \ - > /etc/apt/sources.list.d/docker.list && \ - apt-get update && \ - apt-get install -y docker-ce-cli && \ - rm -rf /var/lib/apt/lists/* - -# 3) Copiar el código fuente -COPY . . - -# 4) Puerto y arranque -ENV PORT=4000 -EXPOSE 4000 -CMD ["node", "index.js"] diff --git a/agent/config.js b/agent/config.js deleted file mode 100644 index 5d409b0..0000000 --- a/agent/config.js +++ /dev/null @@ -1,16 +0,0 @@ - /*───────────────────────────────────────────────────────────────*/ - /* ⚙️ Variables de entorno */ - /*───────────────────────────────────────────────────────────────*/ - - export const config = { - VERSION : '0.6.12', - API_URL : process.env.BOT_API_URL ?? 'http://whatsapp-bot:8002', - GROUP_ID : process.env.GROUP_ID ?? '120363203056794284@g.us', - REPLY_MSG : process.env.REPLY_MSG ?? 'que pedos', - PORT : +(process.env.PORT ?? 4000), - LOG_LEVEL : process.env.LOG_LEVEL ?? 'debug', - RETRY_MS : +(process.env.RETRY_MS ?? 5_000), - MAX_ATTEMPTS : +(process.env.MAX_ATTEMPTS ?? 60), - GEMINI_KEY : process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY ?? '', - GEMINI_MODEL_ID: process.env.GEMINI_MODEL_ID ?? 'gemini-2.5-flash-preview-04-17', - }; \ No newline at end of file diff --git a/agent/example webhooksEvents/exampleOnAnyMessage.txt b/agent/example webhooksEvents/exampleOnAnyMessage.txt deleted file mode 100644 index 19d043d..0000000 --- a/agent/example webhooksEvents/exampleOnAnyMessage.txt +++ /dev/null @@ -1,195 +0,0 @@ -{ - "id": "true_50496210031@c.us_3F4F5E90B644956AC938", - "viewed": false, - "body": "amor", - "type": "chat", - "t": 1745718602, - "notifyName": "📀🧮📡🌐", - "from": "50498554225@c.us", - "to": "50496210031@c.us", - "author": "50498554225:77@c.us", - "invis": false, - "isNewMsg": true, - "star": false, - "kicNotified": false, - "recvFresh": true, - "isFromTemplate": false, - "thumbnail": "", - "pollInvalidated": false, - "isSentCagPollCreation": false, - "latestEditMsgKey": null, - "latestEditSenderTimestampMs": null, - "mentionedJidList": [], - "groupMentions": [], - "isEventCanceled": false, - "eventInvalidated": false, - "isVcardOverMmsDocument": false, - "labels": [], - "hasReaction": false, - "ephemeralDuration": 0, - "ephemeralSettingTimestamp": 0, - "disappearingModeInitiator": "chat", - "disappearingModeTrigger": "chat_settings", - "viewMode": "VISIBLE", - "productHeaderImageRejected": false, - "lastPlaybackProgress": 0, - "isDynamicReplyButtonsMsg": false, - "isCarouselCard": false, - "parentMsgId": null, - "callSilenceReason": null, - "isVideoCall": false, - "callDuration": null, - "callParticipants": null, - "isMdHistoryMsg": false, - "stickerSentTs": 0, - "isAvatar": false, - "lastUpdateFromServerTs": 0, - "invokedBotWid": null, - "bizBotType": null, - "botResponseTargetId": null, - "botPluginType": null, - "botPluginReferenceIndex": null, - "botPluginSearchProvider": null, - "botPluginSearchUrl": null, - "botPluginSearchQuery": null, - "botPluginMaybeParent": false, - "botReelPluginThumbnailCdnUrl": null, - "botMessageDisclaimerText": null, - "botMsgBodyType": null, - "reportingTokenInfo": null, - "requiresDirectConnection": false, - "bizContentPlaceholderType": null, - "hostedBizEncStateMismatch": false, - "senderOrRecipientAccountTypeHosted": false, - "placeholderCreatedWhenAccountIsHosted": false, - "device": 77, - "local": false, - "fromMe": true, - "mId": "3F4F5E90B644956AC938", - "sender": { - "id": "50498554225@c.us", - "name": "📀🧮📡🌐jose", - "shortName": "📀🧮📡🌐jose", - "pushname": "📀🧮📡🌐", - "type": "in", - "isBusiness": false, - "isEnterprise": false, - "isSmb": false, - "isContactSyncCompleted": 1, - "disappearingModeDuration": 0, - "disappearingModeSettingTimestamp": 1700599275, - "textStatusLastUpdateTime": -1, - "syncToAddressbook": true, - "formattedName": "Tú", - "isMe": true, - "isMyContact": true, - "isPSA": false, - "isUser": true, - "status": "Can't talk, WhatsApp only", - "isVerified": false, - "isWAContact": true, - "profilePicThumbObj": { - "eurl": "https://pps.whatsapp.net/v/t61.24694-24/471428085_1635189164083925_3546014480456031647_n.jpg?ccb=11-4&oh=01_Q5Aa1QEqY8vxL1FGF3t2s1OQw0t3tPQ8cS66RZvtzTy6nE1VWQ&oe=6817A633&_nc_sid=5e03e0&_nc_cat=106", - "id": "50498554225@c.us", - "img": "https://media-mia3-1.cdn.whatsapp.net/v/t61.24694-24/471428085_1635189164083925_3546014480456031647_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa1QELed0umu8TOLHLhNq8lHmkZ2srD3fu-IK3spzsNxkLug&oe=6817A633&_nc_sid=5e03e0&_nc_cat=106", - "imgFull": "https://media-mia3-1.cdn.whatsapp.net/v/t61.24694-24/471428085_1635189164083925_3546014480456031647_n.jpg?ccb=11-4&oh=01_Q5Aa1QEqY8vxL1FGF3t2s1OQw0t3tPQ8cS66RZvtzTy6nE1VWQ&oe=6817A633&_nc_sid=5e03e0&_nc_cat=106", - "tag": "1735668406" - }, - "msgs": null - }, - "senderId": null, - "timestamp": 1745718602, - "content": "amor", - "isGroupMsg": false, - "isQuotedMsgAvailable": true, - "isMedia": false, - "chat": { - "id": "50496210031@c.us", - "pendingMsgs": false, - "lastReceivedKey": { - "fromMe": true, - "remote": "50496210031@c.us", - "id": "3F009E582691FEE944F0", - "_serialized": "true_50496210031@c.us_3F009E582691FEE944F0" - }, - "t": 1745717805, - "unreadCount": 0, - "unreadDividerOffset": 0, - "archive": false, - "isReadOnly": false, - "isLocked": false, - "muteExpiration": 0, - "isAutoMuted": false, - "name": "Margie (:", - "notSpam": true, - "pin": 1695127572481, - "ephemeralDuration": 0, - "ephemeralSettingTimestamp": 0, - "disappearingModeInitiator": "chat", - "disappearingModeTrigger": "chat_settings", - "createdLocally": false, - "unreadMentionsOfMe": [], - "unreadMentionCount": 0, - "hasUnreadMention": false, - "archiveAtMentionViewedInDrawer": false, - "hasChatBeenOpened": false, - "tcToken": {}, - "tcTokenTimestamp": 1745616969, - "tcTokenSenderTimestamp": 1744944499, - "endOfHistoryTransferType": 0, - "pendingInitialLoading": false, - "chatlistPreview": { - "type": "reaction", - "msgKey": "false_50496210031@c.us_3FEA807956686BD2AD73", - "parentMsgKey": "true_50496210031@c.us_3F93865C2A9E8061A668", - "reactionText": "🙏", - "sender": "50496210031@c.us", - "timestamp": 1745717107750 - }, - "unreadEditTimestampMs": 1745508257945, - "celebrationAnimationLastPlayed": 0, - "hasRequestedWelcomeMsg": false, - "msgs": null, - "canSend": true, - "isGroup": false, - "pic": "https://pps.whatsapp.net/v/t61.24694-24/470810943_1065895391975207_6852834404866940192_n.jpg?ccb=11-4&oh=01_Q5Aa1QEWA1-AVsmMc5-23KYTOSB9RsYUB41vONjdzNZCen_qGw&oe=6817C3B1&_nc_sid=5e03e0&_nc_cat=109", - "formattedTitle": "Margie (:", - "contact": { - "id": "50496210031@c.us", - "name": "Margie (:", - "shortName": "Margie", - "pushname": "Margie Elizabeth:)", - "type": "in", - "isBusiness": false, - "isEnterprise": false, - "isSmb": false, - "isContactSyncCompleted": 1, - "disappearingModeDuration": 0, - "disappearingModeSettingTimestamp": 1671409557, - "textStatusLastUpdateTime": -1, - "syncToAddressbook": true, - "formattedName": "Margie (:", - "isMe": false, - "isMyContact": true, - "isPSA": false, - "isUser": true, - "isVerified": false, - "isWAContact": true, - "profilePicThumbObj": { - "eurl": "https://pps.whatsapp.net/v/t61.24694-24/470810943_1065895391975207_6852834404866940192_n.jpg?ccb=11-4&oh=01_Q5Aa1QEWA1-AVsmMc5-23KYTOSB9RsYUB41vONjdzNZCen_qGw&oe=6817C3B1&_nc_sid=5e03e0&_nc_cat=109" - }, - "msgs": null - }, - "groupMetadata": null, - "presence": { - "id": "50496210031@c.us", - "chatstates": [] - }, - "isOnline": false, - "participantsCount": 1 - }, - "isOnline": false, - "chatId": "50496210031@c.us", - "mediaData": {}, - "text": "amor" -} diff --git a/agent/gemini.js b/agent/gemini.js deleted file mode 100644 index 6c0e6c2..0000000 --- a/agent/gemini.js +++ /dev/null @@ -1,120 +0,0 @@ -// gemini.js – integración Grounding con Google Search + logs de verificación -import genai from '@google/genai'; -import { config } from './config.js'; -import { log } from './logger.js'; - -const { GoogleGenAI, createUserContent, createPartFromUri } = genai; - -/*───────────────────────────────────────────────────────────────*/ -/* 📜 System prompt */ -/*───────────────────────────────────────────────────────────────*/ -const SYSTEM_PROMPT = ` -######### -Tu nombre es nucleo -########## -RESPUESTAS CORTAS y CONCISAS -sos la inteligencia artificial de Servidores de G.O.D (nuestro J.A.R.V.I.S.). -tenés derecho a sostener opiniones firmes con lógica, sin corrección política excesiva. -Respuestas casuales breves; si te extendés, hacelo en un solo mensaje. -Podés continuar conversaciones, usar chistes, analogías, etc. -`.trim(); - -/*───────────────────────────────────────────────────────────────*/ -/* 🧠 Inicializar Gemini */ -/*───────────────────────────────────────────────────────────────*/ -let ai = null; -function initGemini() { - if (!config.GEMINI_KEY) throw new Error('🔑 GEMINI_API_KEY no configurada'); - if (!ai) { - ai = new GoogleGenAI({ apiKey: config.GEMINI_KEY }); - log('info', `🧠 Gemini SDK inicializado (${config.GEMINI_MODEL_ID})`); - } - return ai; -} - -/*───────────────────────────────────────────────────────────────*/ -/* 🔍 Construir tools de búsqueda */ -/*───────────────────────────────────────────────────────────────*/ -function buildSearchTools() { - const model = config.GEMINI_MODEL_ID; - // Los objetos literales cumplen con el esquema Tool del SDK. - if (/^gemini-2\./.test(model) || /^gemini-2\.5/.test(model)) { - return [{ google_search: {} }]; // Search‑as‑a‑tool - } - if (/^gemini-1\.5/.test(model)) { - return [{ - google_search_retrieval: { - dynamic_retrieval_config: { - mode: 'MODE_DYNAMIC', - dynamic_threshold: 0.3, - }, - }, - }]; - } - return []; -} - -/*───────────────────────────────────────────────────────────────*/ -/* 🚀 askGemini */ -/*───────────────────────────────────────────────────────────────*/ -export async function askGemini(historial, files = {}) { - try { - const client = initGemini(); - - // 1️⃣ Construir "contents" - let contents; - if (typeof historial === 'string') { - contents = historial; - } else if (Array.isArray(historial)) { - const parts = []; - for (const m of historial) { - if (m.type === 'document') continue; - if (m.type === 'chat') { - parts.push(`${m.senderName}: ${m.text} -- ${m.date}`); - continue; - } - const up = files[m.msgId?.toLowerCase?.()]; - if (up?.uri) { - parts.push( - createPartFromUri(up.uri, up.mimeType), - `archivo ${m.type} de ${m.senderName}: ${m.caption || m.text} -- ${m.date}` - ); - } - } - contents = createUserContent(parts); - } else { - throw new Error('Formato de historial no soportado'); - } - - // 2️⃣ Herramientas - const tools = buildSearchTools(); - - // 3️⃣ Llamar al modelo - const response = await client.models.generateContent({ - model: config.GEMINI_MODEL_ID, - contents, - config: { - systemInstruction: SYSTEM_PROMPT, - maxOutputTokens: 4096, - temperature: 0.3, // menor → mayor factualidad - tools, - response_modalities: ['TEXT'], - }, - }); - - // 4️⃣ Log de grounding - const candidate = response?.candidates?.[0]; - if (candidate?.groundingMetadata) { - log('info', '🔗 GroundingMetadata presente:', JSON.stringify(candidate.groundingMetadata.webSearchQueries)); - } else { - log('warn', 'ℹ️ Sin groundingMetadata en la respuesta'); - } - - if (!candidate) return '⚠️ Sin candidato.'; - return candidate.content.parts.map(p => p.text).join('').trim() || '⚠️ Respuesta vacía.'; - } catch (e) { - log('error', 'Gemini falló:', e.message); - if (e.response?.status === 429) return '🚦 Límite alcanzado. Probá más tarde.'; - return '⚠️ No se pudo obtener respuesta de Gemini.'; - } -} diff --git a/agent/handlers.js b/agent/handlers.js deleted file mode 100644 index 5fa93f4..0000000 --- a/agent/handlers.js +++ /dev/null @@ -1,144 +0,0 @@ -// handlers.js -import { respuestaMCP } from './respuestas/respuestaMCP.js'; // <- NUEVA IMPORTACIÓN -import fs from 'fs/promises'; -import { log } from './logger.js'; -// Ya no se necesitan: sendText, fetchChatMessages, setTypingStatus, askGemini aquí -import { processMessage } from './utils/processMessage.js'; -import { saveMedia } from './utils/saveMedia.js'; -// import { respuestaNormal } from './respuestas/respuestaNormal.js'; // <- NUEVA IMPORTACIÓN -import { respuestaBrave } from './respuestas/respuestaBrave.js'; // <- NUEVA IMPORTACIÓN -import { sendText } from './whatsapp.js'; // <- NUEVA IMPORTACIÓN - -// Mock Data for Employees -const mockEmployees = [ - { - id: '1', // Ensure ID is string if components expect string - name: 'Ana García Mock', - cedula: 123456789, // Ensure cedula is number - avatar_url: 'https://randomuser.me/api/portraits/women/60.jpg', - telefono: '0991234567', - ubicacion: 'Oficina Mock Central', - idciat: 'AG001M', - grupo_estudio: 'Desarrollo Frontend Mock', - empleado: true, - }, - { - id: '2', - name: 'Carlos Rodriguez Mock', - cedula: 987654321, - avatar_url: 'https://randomuser.me/api/portraits/men/45.jpg', - telefono: '0987654321', - ubicacion: 'Sucursal Mock Norte', - idciat: 'CR002M', - grupo_estudio: 'Backend Services Mock', - empleado: true, - }, - { - id: '3', - name: 'Luisa Martinez Mock', - cedula: 112233445, - avatar_url: 'https://randomuser.me/api/portraits/women/61.jpg', - telefono: '0976543210', - ubicacion: 'Remoto Mock', - idciat: 'LM003M', - grupo_estudio: 'QA Mock', - empleado: true, - }, - { - id: '4', - name: 'Jorge Herrera Mock', - cedula: 223344556, - avatar_url: 'https://randomuser.me/api/portraits/men/50.jpg', - telefono: '0965432109', - ubicacion: 'Oficina Mock Sur', - idciat: 'JH004M', - grupo_estudio: 'DevOps Mock', - empleado: true, - }, - { - id: '5', - name: 'Patricia Fernández Mock', - cedula: 334455667, - avatar_url: 'https://randomuser.me/api/portraits/women/62.jpg', - telefono: '0954321098', - ubicacion: 'Oficina Mock Central', - idciat: 'PF005M', - grupo_estudio: 'Diseño UX/UI Mock', - empleado: true, - } -]; - -/* carpeta raíz donde saveMedia deja todo */ -const MEDIA_DIR = '/media'; -await fs.mkdir(MEDIA_DIR, { recursive: true }); - -// La función cleanForGemini se movió a respuestaNormal.js - -export async function processIncoming(raw) { - // Guarda media si no es un mensaje de texto plano - if (raw.type !== 'chat') { - try { - // Nota: saveMedia podría necesitar acceso a MEDIA_DIR si no está codificado internamente - await saveMedia(raw /*, MEDIA_DIR */); // Podrías necesitar pasar MEDIA_DIR si saveMedia lo requiere - } catch (error) { - log('error', 'Error guardando media:', error); - } - } - - const msg = processMessage(raw); - const text = msg.text || ''; - - // Logica para componentes UI de Empleados - if (/^Quiero crear un nuevo @empleado/i.test(text)) { - log('info', `Comando recibido: Crear nuevo empleado. Enviando componente EmpleadoForm.`); - sendText(msg.chatId, 'CHAT_UI_COMPONENT::EmpleadoForm'); - return; // Termina el procesamiento para este comando - } - - const verEmpleadoMatch = text.match(/^Ver @empleado(\d+)/i); - if (verEmpleadoMatch && verEmpleadoMatch[1]) { - const cedula = parseInt(verEmpleadoMatch[1], 10); - log('info', `Comando recibido: Ver empleado con cédula ${cedula}.`); - const employee = mockEmployees.find(emp => emp.cedula === cedula); - if (employee) { - log('info', `Empleado encontrado: ${employee.name}. Enviando componente cardEmpleado.`); - // La cédula se pasa como parámetro para que el frontend la use si es necesario para buscar o mostrar. - sendText(msg.chatId, `CHAT_UI_COMPONENT::cardEmpleado::${cedula}`); - } else { - log('warn', `Empleado con cédula ${cedula} no encontrado.`); - sendText(msg.chatId, `No se encontró un empleado con la cédula ${cedula}.`); - } - return; // Termina el procesamiento para este comando - } - - const mostrarEmpleadosMatch = text.match(/^Mostrame los primeros (\d+) @empleados/i); - if (mostrarEmpleadosMatch && mostrarEmpleadosMatch[1]) { - const count = parseInt(mostrarEmpleadosMatch[1], 10); - log('info', `Comando recibido: Mostrar los primeros ${count} empleados.`); - // El count se pasa como parámetro para que el frontend lo use para determinar cuántos mostrar. - // La lógica de obtener los X primeros empleados realmente estará en el frontend o en una API. - // Aquí solo indicamos el componente y el count deseado. - sendText(msg.chatId, `CHAT_UI_COMPONENT::tablaEmpleados::${count}`); - return; // Termina el procesamiento para este comando - } - - /* ----- comando @nucleo ----- */ - // Se comenta la condicion original de @nucleo para evitar doble respuesta si no se hace return antes. - // if (/^@nucleo(\s|$)/i.test(text)) { - // // Llama a la función importada - // // await respuestaNormal(msg); // Ya no se usa respuestaNormal aquí directamente. - // await respuestaMCP(msg); // respuestaMCP ya no es relevante en este flujo si @nucleo siempre va a brave. - // } - - if (/^@nucleo(\s|$)/i.test(text)) { // Modificado para que @nucleo solo dispare respuestaBrave - log('info', '🧠 Generando respuesta para @nucleo...'); - const respuestaObjMCP = await respuestaBrave(msg); - log('info', 'Respuesta de @nucleo (Brave):', respuestaObjMCP); - sendText(msg.chatId, respuestaObjMCP); - } else { - // Lógica para otros mensajes si no son comandos de UI ni @nucleo - log('debug', 'Mensaje no reconocido como comando UI o @nucleo:', text); - // Considerar si se debe enviar una respuesta por defecto o ninguna si no coincide con nada. - // Por ahora, no se envía nada si no es un comando específico. - } -} \ No newline at end of file diff --git a/agent/index.js b/agent/index.js deleted file mode 100644 index 093500e..0000000 --- a/agent/index.js +++ /dev/null @@ -1,35 +0,0 @@ -/* nucleo-bot ― index.js */ -import express from 'express'; -import morgan from 'morgan'; -import { config } from './config.js'; -import { log } from './logger.js'; -import { router } from './routes.js'; -import { - waitForGateway, - clearWebhooks, - registerWebhook -} from './whatsapp.js'; - - - -export let globalMemory = {}; - -/* 🌐 Express app */ -const app = express(); -app.use(express.json({ limit: '1mb' })); -app.use(morgan('[:date[iso]] :method :url :status - :response-time ms')); -app.use(router); - -/* 🚀 Bootstrap */ -async function bootstrap() { - await waitForGateway(); - await clearWebhooks(); - await registerWebhook(); -} - -/* 🏁 Arranque */ -app.listen(config.PORT, () => { - log('info', `🪪 Versión del bot: ${config.VERSION}`); - log('info', `🚀 nucleo-bot escuchando en :${config.PORT}`); - bootstrap().catch(e => log('error', 'Error en bootstrap:', e.message)); -}); diff --git a/agent/logger.js b/agent/logger.js deleted file mode 100644 index 4dbb0b6..0000000 --- a/agent/logger.js +++ /dev/null @@ -1,17 +0,0 @@ -/*───────────────────────────────────────────────────────────────*/ -/* 🖨️ Logger */ -/*───────────────────────────────────────────────────────────────*/ -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc.js'; -import { config } from './config.js'; - -dayjs.extend(utc); - -export function log(level, ...args) { - const levels = ['debug', 'info', 'warn', 'error']; - if (levels.indexOf(level) >= levels.indexOf(config.LOG_LEVEL)) { - console[level === 'debug' ? 'log' : level]( - `[${dayjs().utc().format()}]`, level.toUpperCase(), ...args - ); - } -} \ No newline at end of file diff --git a/agent/package.json b/agent/package.json deleted file mode 100644 index 010f853..0000000 --- a/agent/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "nucleo-bot", - "version": "0.4.0", - "type": "module", - "scripts": { - "start:mcp": "npx @philschmid/weather-mcp" - }, - "dependencies": { - "axios": "^1.8.4", - "dayjs": "^1.11.11", - "express": "^4.19.2", - "morgan": "^1.10.0", - "@google/generative-ai": "^0.4.0", - "@google/genai": "^0.9.0", - "@open-wa/wa-automate": "^4.34.3", - "mime-types": "^2.1.35", - "@modelcontextprotocol/sdk": "^1.0.0", - "@philschmid/weather-mcp": "^1.0.0" - } -} diff --git a/agent/processMessage/processVideoMessage.js b/agent/processMessage/processVideoMessage.js deleted file mode 100644 index e69de29..0000000 diff --git a/agent/respuestas/respuestaBrave.js b/agent/respuestas/respuestaBrave.js deleted file mode 100644 index 0096b9c..0000000 --- a/agent/respuestas/respuestaBrave.js +++ /dev/null @@ -1,128 +0,0 @@ -// /respuesta/respuestaBrave.js -import fs from 'fs/promises'; -import { log } from '../logger.js'; -import { - sendText, - fetchChatMessages, - setTypingStatus -} from '../whatsapp.js'; -import { processMessage } from '../utils/processMessage.js'; -import { saveMedia } from '../utils/saveMedia.js'; - -import { GoogleGenAI } from '@google/genai'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; - -function sanitizeSchema(obj) { - if (Array.isArray(obj)) return obj.map(sanitizeSchema); - if (obj && typeof obj === 'object') { - const out = {}; - for (const [k, v] of Object.entries(obj)) { - if (k === 'additionalProperties' || k === '$schema') continue; - out[k] = sanitizeSchema(v); - } - return out; - } - return obj; -} - -const cleanForGemini = ({ media, reactions, preview, ...rest }) => rest; - -export async function respuestaBrave(msg) { - const text = msg.text || ''; - - setTypingStatus(msg.chatId, true); - log('info', '🧠 Generando respuesta para @nucleo (Brave)…'); - - try { - const allraw = await fetchChatMessages(msg.chatId); - const allMsgs = allraw.map(processMessage); - - const context = allMsgs.map(cleanForGemini); - let json = JSON.stringify(context); - while (json.length > 100_000 && context.length) { - context.shift(); - json = JSON.stringify(context); - } - - const chatName = context.length > 0 ? context[0]?.chatName : 'chat'; - const prompt = [ - `Eres un asistente con acceso a Brave Search mediante herramientas externas.`, - `Pregunta del usuario: ${text}`, - ...context - ]; - - const medias = allraw.filter(m => m.type !== 'chat'); - const settled = await Promise.allSettled(medias.map(saveMedia)); - - const MAX = 20 * 1024 * 1024; - let total = 0; - const files = {}; - - for (const r of settled) { - if (r.status !== 'fulfilled' || !r.value) continue; - const entries = Object.entries(r.value); - if (entries.length === 0) continue; - const [msgId, obj] = entries[0]; - let size = Number(obj.sizeBytes || 0); - if (!size && obj.filePath) { - try { size = (await fs.stat(obj.filePath)).size; } catch { size = 0; } - } - if (total + size > MAX && total > 0) break; - if (size > MAX) continue; - - total += size; - files[msgId] = { uri: obj.uri || obj.filePath, mimeType: obj.mimeType }; - } - - // === MCP Brave Search === - const client = new Client({ name: 'brave-agent', version: '1.0.0' }); - const serverParams = new StdioClientTransport({ - command: 'uvx', - args: ['mcp-server-fetch'] - }); - - - await client.connect(serverParams); - - const mcp = await client.listTools(); - const tools = mcp.tools.map(t => ({ - name: t.name, - description: t.description, - parameters: sanitizeSchema({ - type: 'object', - ...t.inputSchema - }) - })); - - const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); - const response = await ai.models.generateContent({ - model: 'gemini-2.5-flash-preview-04-17', - contents: prompt, - config: { - tools: [{ functionDeclarations: tools }], - temperature: 0 - } - }); - - let respuesta; - if (response.functionCalls && response.functionCalls.length > 0) { - const call = response.functionCalls[0]; - const result = await client.callTool({ name: call.name, arguments: call.args }); - await client.close(); - respuesta = result.content?.[0]?.text ?? JSON.stringify(result); - } else { - await client.close(); - respuesta = response.text; - } - - await sendText(msg.chatId, respuesta); - log('info', '🧠 Respuesta enviada'); - - } catch (error) { - log('error', 'Error en respuestaBrave:', error); - await sendText(msg.chatId, 'Hubo un error al procesar tu solicitud.'); - } finally { - setTypingStatus(msg.chatId, false); - } -} diff --git a/agent/respuestas/respuestaMCP.js b/agent/respuestas/respuestaMCP.js deleted file mode 100644 index 5ccc774..0000000 --- a/agent/respuestas/respuestaMCP.js +++ /dev/null @@ -1,127 +0,0 @@ -// /respuesta/respuestaMCP.js -import fs from 'fs/promises'; -import { log } from '../logger.js'; -import { - sendText, - fetchChatMessages, - setTypingStatus -} from '../whatsapp.js'; -import { processMessage } from '../utils/processMessage.js'; -import { saveMedia } from '../utils/saveMedia.js'; - -import { GoogleGenAI } from '@google/genai'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; - -/* limpia el esquema JSON recursivamente */ -function sanitizeSchema(obj) { - if (Array.isArray(obj)) return obj.map(sanitizeSchema); - if (obj && typeof obj === 'object') { - const out = {}; - for (const [k, v] of Object.entries(obj)) { - if (k === 'additionalProperties' || k === '$schema') continue; - out[k] = sanitizeSchema(v); - } - return out; - } - return obj; -} - -const cleanForGemini = ({ media, reactions, preview, ...rest }) => rest; - -export async function respuestaMCP(msg) { - const text = msg.text || ''; - - setTypingStatus(msg.chatId, true); - log('info', '🧠 Generando respuesta para @nucleo…'); - - try { - const allraw = await fetchChatMessages(msg.chatId); - const allMsgs = allraw.map(processMessage); - - const context = allMsgs.map(cleanForGemini); - let json = JSON.stringify(context); - while (json.length > 100_000 && context.length) { - context.shift(); - json = JSON.stringify(context); - } - - const chatName = context.length > 0 ? context[0]?.chatName : 'chat'; - const prompt = [ - `Eres el asistente del grupo "${chatName}". tenes la capacidad de interactuar con las carpetas ubicacadas en el directorio "/media/mcp" y sus subcarpetas.`, - `Pregunta del usuario: ${text}`, - ...context - ]; - - const medias = allraw.filter(m => m.type !== 'chat'); - const settled = await Promise.allSettled(medias.map(saveMedia)); - - const MAX = 20 * 1024 * 1024; - let total = 0; - const files = {}; - - for (const r of settled) { - if (r.status !== 'fulfilled' || !r.value) continue; - const entries = Object.entries(r.value); - if (entries.length === 0) continue; - const [msgId, obj] = entries[0]; - let size = Number(obj.sizeBytes || 0); - if (!size && obj.filePath) { - try { size = (await fs.stat(obj.filePath)).size; } catch { size = 0; } - } - if (total + size > MAX && total > 0) break; - if (size > MAX) continue; - - total += size; - files[msgId] = { uri: obj.uri || obj.filePath, mimeType: obj.mimeType }; - } - - // === MCP setup === - const client = new Client({ name: 'mcp-agent', version: '1.0.0' }); - const serverParams = new StdioClientTransport({ - command: 'npx', - args: ['-y', '@modelcontextprotocol/server-filesystem', '/media/mcp'] - }); - await client.connect(serverParams); - - const mcp = await client.listTools(); - const tools = mcp.tools.map(t => ({ - name: t.name, - description: t.description, - parameters: sanitizeSchema({ - type: 'object', - ...t.inputSchema - }) - })); - - const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); - const response = await ai.models.generateContent({ - model: 'gemini-2.5-flash-preview-04-17', - contents: prompt, - config: { - tools: [{ googleSearch: {} }], - temperature: 0 - } - }); - - let respuesta; - if (response.functionCalls && response.functionCalls.length > 0) { - const call = response.functionCalls[0]; - const result = await client.callTool({ name: call.name, arguments: call.args }); - await client.close(); - respuesta = result.content?.[0]?.text ?? JSON.stringify(result); - } else { - await client.close(); - respuesta = response.text; - } - - await sendText(msg.chatId, respuesta); - log('info', '🧠 Respuesta enviada'); - - } catch (error) { - log('error', 'Error en respuestaMCP:', error); - await sendText(msg.chatId, 'Hubo un error al procesar tu solicitud.'); - } finally { - setTypingStatus(msg.chatId, false); - } -} diff --git a/agent/respuestas/respuestaNormal.js b/agent/respuestas/respuestaNormal.js deleted file mode 100644 index cef10ae..0000000 --- a/agent/respuestas/respuestaNormal.js +++ /dev/null @@ -1,100 +0,0 @@ -// /respuesta/respuestaNormal.js -import fs from 'fs/promises'; -import { log } from '../logger.js'; // <- Nota el '../' -import { - sendText, - fetchChatMessages, - setTypingStatus -} from '../whatsapp.js'; // <- Nota el '../' -import { askGemini } from '../gemini.js'; // <- Nota el '../' -import { processMessage } from '../utils/processMessage.js'; // <- Nota el '../' -import { saveMedia } from '../utils/saveMedia.js'; // <- Nota el '../' - -/* Quita campos pesados antes de mandar a Gemini */ -const cleanForGemini = ({ media, reactions, preview, ...rest }) => rest; - -/** - * Procesa la lógica específica para el comando @nucleo. - * Obtiene el historial, prepara el contexto, procesa media, - * llama a Gemini y envía la respuesta. - * @param {object} msg - El objeto de mensaje procesado. - */ -export async function respuestaNormal(msg) { - const text = msg.text || ''; // Necesitamos el texto original aquí también - - setTypingStatus(msg.chatId, true); - log('info', '🧠 Generando respuesta para @nucleo…'); - - try { - /* 1) historial completo del chat */ - const allraw = await fetchChatMessages(msg.chatId); - const allMsgs = allraw.map(processMessage); - - /* 2) recorta contexto a ≤100 kB */ - const context = allMsgs.map(cleanForGemini); - let json = JSON.stringify(context); - while (json.length > 100_000 && context.length) { - context.shift(); - json = JSON.stringify(context); - } - - // Asegurarse de que el contexto no esté vacío antes de acceder a context[0] - const chatName = context.length > 0 ? context[0]?.chatName : 'chat'; - - /* 3) prompt */ - const prompt = [ - `Eres el asistente del grupo "${chatName}".`, - `Pregunta del usuario: ${text}` // Usamos el 'text' del mensaje original que activó el comando - , ...context]; - - /* 4) procesa medias y respeta el límite de 20 MB */ - // Filtrar solo los mensajes que no son de tipo 'chat' del historial obtenido - const medias = allraw.filter(m => m.type !== 'chat'); - const settled = await Promise.allSettled(medias.map(saveMedia)); - - const MAX = 20 * 1024 * 1024; // 20 MB - let total = 0; - const files = {}; - - for (const r of settled) { - if (r.status !== 'fulfilled' || !r.value) continue; - - if (typeof r.value !== 'object' || r.value === null) continue; - const entries = Object.entries(r.value); - if (entries.length === 0) continue; - const [msgId, obj] = entries[0]; - if (typeof obj !== 'object' || obj === null) continue; - - let size = Number(obj.sizeBytes || 0); - - if (!size && obj.filePath) { - try { size = (await fs.stat(obj.filePath)).size; } - catch { size = 0; } - } - - // Comprobar si añadir este archivo excede el límite MÁXIMO TOTAL - // y si ya tenemos *algo* (total > 0) para evitar empezar con un archivo demasiado grande - if (total + size > MAX && total > 0) break; - // Si este archivo *por sí solo* excede el límite, saltarlo - if (size > MAX) continue; - - total += size; - files[msgId] = { uri: obj.uri || obj.filePath, mimeType: obj.mimeType }; - } - - // log('info', '🧠 Enviando a Gemini...', { files }); - - /* 5) llama a Gemini y responde */ - const respuesta = await askGemini(prompt, files); - await sendText(msg.chatId, respuesta); - - log('info', '🧠 Respuesta enviada'); - - } catch (error) { - log('error', 'Error en respuestaNormal:', error); - await sendText(msg.chatId, 'Hubo un error al procesar tu solicitud.'); - - } finally { - setTypingStatus(msg.chatId, false); - } -} \ No newline at end of file diff --git a/agent/routes.js b/agent/routes.js deleted file mode 100644 index 5a60d19..0000000 --- a/agent/routes.js +++ /dev/null @@ -1,200 +0,0 @@ -// routes.js -import express from 'express'; -import axios from 'axios'; -import dayjs from 'dayjs'; -import { config } from './config.js'; -import { log } from './logger.js'; -import { sendText } from './whatsapp.js'; -import { processIncoming } from './handlers.js'; -import { processMessage } from './utils/processMessage.js'; - -// chats a tomar en cuenta para el bot - -const relevantChats = [ - '50496210031@c.us', - '120363203056794284@g.us', - '120363398335375917@g.us', - '50498554225@c.us', - '50496934012@c.us', - '50497588328@c.us', - '50489701450@c.us' -] - - - - -export const router = express.Router(); - -// --- Manejo de eventos del webhook ------------------------------------------------ -router.post('/webhook', async (req, res) => { - const { event, data: raw } = req.body; - const data = processMessage(raw); - - // Si el evento no es relevante, ignorar - if(data.chatId && !relevantChats.includes(data.chatId)) { - log('info', `Mensaje de ${data.chatId} ignorado`); - return res.sendStatus(200); - } - // console.log('----------------------------------------------------------------'); - // log('debug', '↪︎ Mensaje IN →', raw); - // console.log('----------------------------------------------------------------'); - - // log('debug', `📩 Webhook event "${event}"`); - - switch (event) { - case 'onAck': - log('info', 'Ack:', data); - break; - - case 'onAddedToGroup': - log('info', 'Added to group:', data); - break; - - case 'onAnyMessage': - // log('info', 'onAnyMessage', data); - log('info', 'onAnyMessage', raw.chatId); - await processIncoming(raw); - break; - - case 'onBattery': - log('info', 'Battery status:', data); - break; - - case 'onBroadcast': - log('info', 'Broadcast:', data); - break; - - case 'onButton': - log('info', 'Button pressed:', data); - break; - - case 'onCallState': - log('info', 'Call state:', data); - break; - - case 'onChatDeleted': - log('info', 'Chat deleted:', data); - break; - - case 'onChatOpened': - log('info', 'Chat opened:', data); - break; - - case 'onChatState': - log('info', 'Chat state:', data); - break; - - case 'onContactAdded': - log('info', 'Contact added:', data); - break; - - case 'onGlobalParticipantsChanged': - log('info', 'Global participants changed:', data); - break; - - case 'onGroupApprovalRequest': - log('info', 'Group approval request:', data); - break; - - case 'onGroupChange': - log('info', 'Group change:', data); - break; - - case 'onIncomingCall': - log('info', 'Incoming call:', data); - break; - - case 'onLabel': - log('info', 'Label event:', data); - break; - - case 'onLogout': - log('info', 'Logout:', data); - break; - - case 'onMessage': - log('info', 'Message:', data); - break; - - case 'onMessageDeleted': - log('info', 'Message deleted:', data); - break; - - case 'onNewProduct': - log('info', 'New product:', data); - break; - - case 'onOrder': - log('info', 'Order:', data); - break; - - case 'onPlugged': - log('info', 'Plugged:', data); - break; - - case 'onPollVote': - log('info', 'Poll vote:', data); - break; - - case 'onReaction': - log('info', 'Reaction:', data); - break; - - case 'onRemovedFromGroup': - log('info', 'Removed from group:', data); - break; - - case 'onStateChanged': - log('info', 'State changed:', data); - break; - - case 'onStory': - log('info', 'Story:', data); - break; - - default: - log('warn', `Unhandled event type: "${event}"`, data); - break; - } - - res.sendStatus(200); -}); - - - - - - - - - - - - - - - - -// comentado el 4/26/2025 -/* Debug: escanear últimos mensajes */ -// router.get('/debug/scan', async (_req, res) => { -// const { data } = await axios.post(`${config.API_URL}/loadAndGetAllMessagesInChat`, { -// args: { chatId: config.GROUP_ID, includeMe: 'true', includeNotifications: 'false' } -// }); -// const msgs = (data?.response ?? []).slice(-20); -// log('info', `Escaneando ${msgs.length} mensajes recientes…`); -// for (const m of msgs) await processIncoming(m); -// res.json({ ok: true, scanned: msgs.length }); -// }); - -/* Debug: enviar mensaje */ -// router.get('/debug/send', async (req, res) => { -// const text = req.query.msg ?? config.REPLY_MSG; -// const resp = await sendText(config.GROUP_ID, text); -// res.json({ ok: true, resp }); -// }); - -/* Debug: versión */ -router.get('/debug/version', (_req, res) => { - res.json({ version: config.VERSION }); -}); diff --git a/agent/utils/decryptMediaContent.js b/agent/utils/decryptMediaContent.js deleted file mode 100644 index ee0a994..0000000 --- a/agent/utils/decryptMediaContent.js +++ /dev/null @@ -1,80 +0,0 @@ -// utils/decryptMediaContent.js (ES modules) -import fs from 'fs'; -import path from 'path'; -import axios from 'axios'; -import mime from 'mime-types'; -import { decryptMedia } from '@open-wa/wa-automate'; -import { log } from '../logger.js'; - -// 🔒 quita caracteres que rompen rutas -const safe = s => (s || '').replace(/[\\/:*?"<>|]/g, '_'); - -export async function decryptMediaContent( - mediaInfo, - outputDir = 'media/dec', - rawDir = 'media/raw', - filename = null -) { - const { clientUrl, t, filehash, msgId, type } = mediaInfo; - let { mimetype } = mediaInfo; - - if (!clientUrl) { - log('error', '❌ Sin clientUrl, no se puede bajar'); - return null; - } - - // deducir mimetype si falta - if (!mimetype) { - mimetype = - mime.lookup(clientUrl) || - (type?.startsWith('image') && 'image/jpeg') || - (type?.startsWith('video') && 'video/mp4') || - 'application/octet-stream'; - } - - const ext = mime.extension(mimetype) || 'bin'; - const baseName = safe(filename || msgId || filehash?.slice(0,16) || `file_${t||Date.now()}`); - const rawPath = path.join(rawDir , `${baseName}.enc`); - const decPath = path.join(outputDir, `${baseName}.${ext}`); - - if (fs.existsSync(decPath)) return decPath; - - fs.mkdirSync(rawDir , { recursive: true }); - fs.mkdirSync(outputDir, { recursive: true }); - - try { - /* ───── descarga RAW (solo si no existe) ───── */ - if (!fs.existsSync(rawPath)) { - const { data } = await axios - .get(clientUrl, { responseType: 'arraybuffer' }) - .catch(e => { - if (e.response?.status === 410) throw new Error('URL expirada (410)'); - throw e; - }); - fs.writeFileSync(rawPath, Buffer.from(data)); - } - - /* ───── inyectar RAW para que decryptMedia no lo vuelva a bajar ───── */ - const fake = { - ...mediaInfo, - mimetype, - mimeType: mimetype, - _data: { - ...mediaInfo, - mimetype, - mimeType: mimetype, - _raw: fs.readFileSync(rawPath) - } - }; - - const plain = await decryptMedia(fake); - if (!plain?.length) throw new Error('descifrado vacío'); - - fs.writeFileSync(decPath, plain); - log('info', `✔️ ${type || ext} → ${decPath}`); - return decPath; - } catch (e) { - log('error', `❌ decryptMedia falló → ${e.message}`); - return null; - } -} diff --git a/agent/utils/processMessage.js b/agent/utils/processMessage.js deleted file mode 100644 index 701ba97..0000000 --- a/agent/utils/processMessage.js +++ /dev/null @@ -1,80 +0,0 @@ -import dayjs from 'dayjs'; - -/** - * Convierte el raw de open-wa a un objeto compacto. - * Solo coloca `media` cuando hay TODO para desencriptar: mediaKey + clientUrl. - */ -export function processMessage(raw) { - const m = raw?.data ?? raw; // alias corto - - const base = { - msgId : m.id, - chatId : m.chatId, - chatName : m.chat?.name ?? m.chat?.formattedTitle ?? null, - senderId : m.sender?.id ?? m.author ?? null, - senderName: m.sender?.name ?? m.notifyName ?? null, - type : m.type, - text : m.text ?? m.caption ?? '', - fromMe : !!m.fromMe, - timestamp : m.timestamp ?? m.t, - date : m.timestamp - ? dayjs.unix(m.timestamp).utcOffset(-360).format('YYYY-MM-DD HH:mm:ss') - : null, - hasReactions: (m.reactions?.length ?? 0) > 0 || !!m.hasReaction, - reactions : (m.reactions ?? []).map(r => r.aggregateEmoji) - }; - - /* ---------- quoted ---------- */ - if (m.quotedMsg) { - base.replyTo = { - id : m.quotedMsg.id, - text: m.quotedMsg.text ?? m.quotedMsg.body ?? '', - from: m.quotedParticipant ?? null, - type: m.quotedMsg.type - }; - } - - /* ---------- preview de enlaces ---------- */ - if (m.matchedText) { - base.preview = { - url : m.matchedText, - title: m.title ?? null, - description: m.description ?? null - }; - } - - /* ---------- media listo para desencriptar ---------- */ - const isMediaType = ['image','video','sticker','ptt','audio','document'].includes(m.type); - const hasKeys = m.mediaKey || m.mediaData?.mediaKey; - const hasUrl = m.clientUrl || m.directPath; - - if (isMediaType && hasKeys && hasUrl) { - base.media = { - type : m.type, - clientUrl: m.clientUrl ?? m.directPath, - mimetype : m.mimetype, - size : m.size ?? m.mediaData?.size, - width : m.width, - height : m.height, - duration : Number(m.duration) || 0, - filename : m.filename ?? null, - caption : m.caption ?? null, - - /* claves para decryptMediaContent */ - mediaKey : m.mediaKey ?? m.mediaData?.mediaKey, - filehash : m.filehash ?? m.mediaData?.filehash, - t : m.t ?? m.timestamp - }; - - if (m.type === 'sticker') { - Object.assign(base.media, { - packId : m.stickerPackId ?? m.mediaData?.stickerPackId, - pack : m.stickerPackName ?? m.mediaData?.stickerPackName, - author : m.stickerPackPublisher ?? m.mediaData?.stickerPackPublisher, - animated: m.isLottie || m.mediaData?.isAnimated || false - }); - } - } - - return base; -} diff --git a/agent/utils/saveMedia.js b/agent/utils/saveMedia.js deleted file mode 100644 index 346418f..0000000 --- a/agent/utils/saveMedia.js +++ /dev/null @@ -1,80 +0,0 @@ -// utils/saveMedia.js -import fs from 'fs'; -import path from 'path'; -import axios from 'axios'; -import mime from 'mime-types'; -import { decryptMedia } from '@open-wa/wa-automate'; -import { GoogleGenAI } from '@google/genai'; -import { config } from '../config.js'; -import { log } from '../logger.js'; - -const BASE_DIR = '/media'; -const safe = s => (s || '').replace(/[\\/:*?"<>|]/g, '_'); -const ai = new GoogleGenAI({ apiKey: config.GEMINI_KEY }); - -function getShortId(msgId = '') { - return msgId.replace(/^true_|^false_|@c\.us_/g, '').split('_')[0]; -} - -export async function saveMedia(msg) { - if (msg.type === 'chat') return null; - if (!msg.clientUrl) { - log('warn', `Sin clientUrl: ${msg.id}`); - return null; - } - - const ext = mime.extension(msg.mimetype || 'application/octet-stream') || 'bin'; - const chatId = safe(msg.chatId); - const shortId = (`${msg.timestamp}${msg.type}`).toLocaleLowerCase(); - // const shortId = getShortId(msg.id); - const folder = path.join(BASE_DIR, chatId, safe(msg.type)); - const filePath = path.join(folder, `${shortId}.${ext}`); - const fileName = shortId // solo para Files API - - // Buscar primero en Files API - try { - const existing = await ai.files.get({ name: fileName }); - log('info', `📂 ya existe en Files API: ${fileName}`); - return { [msg.id]: existing }; - } catch (e) { - if (e.message?.includes('INVALID_ARGUMENT')) { - log('error', `files.get falló para ${fileName}: ${e.message}`); - } - } - - // Buscar en disco - if (fs.existsSync(filePath)) { - log('info', `📁 ya existe local: ${fileName}`, 'subiendo a Files API'); - const uploaded = await ai.files.upload({ - file: fileName, - config: { mimeType: msg.mimetype || 'application/octet-stream', name: fileName }, - }); - log('info', `📤 subido a Files API: ${uploaded.name}`); - return { [msg.id]: uploaded }; - } - - try { - const raw = await axios.get(msg.clientUrl, { responseType: 'arraybuffer' }); - msg._data = { ...msg, _raw: raw.data }; - const buf = await decryptMedia(msg); - - fs.mkdirSync(folder, { recursive: true }); - fs.writeFileSync(filePath, buf); - log('info', `✅ guardado: ${filePath}`); - - const uploaded = await ai.files.upload({ - file: filePath, - config: { mimeType: msg.mimetype || 'application/octet-stream', name: fileName }, - }); - log('info', `📤 subido a Files API: ${uploaded.name}`); - return { [msg.id]: uploaded }; - - } catch (err) { - if (err.response?.status === 410) { - log('warn', `URL expirada (410) para ${msg.id}`); - } else { - log('error', `Error al guardar media ${msg.id}: ${err.message}`); - } - return null; - } -} diff --git a/agent/whatsapp.js b/agent/whatsapp.js deleted file mode 100644 index 97cbc72..0000000 --- a/agent/whatsapp.js +++ /dev/null @@ -1,137 +0,0 @@ -// whatsapp.js -import axios from 'axios'; -import { config } from './config.js'; -import { log } from './logger.js'; - -/* ✉️ Enviar texto -------------------------------------------------------- */ -export async function sendText(to, content) { - log('info', `Enviando mensaje a ${to}: "${content}"`); - const { data } = await axios.post(`${config.API_URL}/sendText`, { - args: { to, content } - }); - log('debug', 'Respuesta de /sendText →', data); - return data; -} - -/* 🗨️ Traer todos los mensajes cargados en el chat ------------------------ */ -export async function fetchChatMessages(chatId) { - try { - const { data } = await axios.post(`${config.API_URL}/getAllMessagesInChat`, { - args: { - chatId, - includeMe: 'true', - includeNotifications: 'false' - } - }); - return data?.response ?? []; - } catch (e) { - log('error', 'Fallo /getAllMessagesInChat:', e.response?.data ?? e.message); - return []; - } -} - -/* ⏳ Esperar gateway ------------------------------------------------------ */ -export async function waitForGateway() { - for (let i = 1; i <= config.MAX_ATTEMPTS; i++) { - try { - await axios.get(`${config.API_URL}/api-docs`); - log('info', '🟢 whatsapp-gateway listo'); - return; - } catch { - log('warn', `Gateway no responde (intento ${i}/${config.MAX_ATTEMPTS})…`); - await new Promise(r => setTimeout(r, config.RETRY_MS)); - } - } - throw new Error('whatsapp-gateway no respondió a tiempo'); -} - -/* 🧹 Limpiar webhooks anteriores ----------------------------------------- */ -export async function clearWebhooks() { - try { - const { data } = await axios.post(`${config.API_URL}/listWebhooks`); - const hooks = data?.response ?? []; - if (!hooks.length) { - log('info', 'Sin webhooks previos que limpiar'); - return; - } - - log('info', `Eliminando ${hooks.length} webhooks…`); - const results = await Promise.allSettled( - hooks.map(h => axios.post(`${config.API_URL}/removeWebhook`, { args: { webhookId: h.id } })) - ); - - results.forEach((r, i) => { - const id = hooks[i].id; - if (r.status === 'fulfilled' && r.value?.data?.response === true) { - log('debug', `✔️ Eliminado webhook ${id}`); - } else { - log('warn', `⚠️ Falló eliminar webhook ${id}`); - } - }); - - const ok = results.filter(r => r.status === 'fulfilled' && r.value?.data?.response === true).length; - log('info', `Limpieza OK (${ok}/${hooks.length} eliminados)`); - } catch (e) { - log('error', 'Fallo limpiando webhooks:', e.response?.data ?? e.message); - } -} - -// --- Registro del webhook con todos los eventos disponibles ---------- -export async function registerWebhook() { - const url = `http://nucleo-bot:${config.PORT}/webhook`; - - const eventConfig = { - onAck: false, // ❌ - onAddedToGroup: true, - onAnyMessage: true, - onBattery: true, - onBroadcast: true, - onButton: true, - onCallState: false, // ❌ - onChatDeleted: true, - onChatOpened: true, - onChatState: true, - onContactAdded: true, - onGlobalParticipantsChanged: true, - onGroupApprovalRequest: true, - onGroupChange: true, - onIncomingCall: false, // ❌ - onLabel: true, - onLogout: true, - onMessage: false, // ❌ - onMessageDeleted: true, - onNewProduct: true, - onOrder: true, - onPlugged: false, // ❌ - onPollVote: true, - onReaction: true, - onRemovedFromGroup: false, // ❌ - onStateChanged: false, // ❌ - onStory: false, // ❌ - }; - - // usa el config de eventos para filtrar los habilitados (el config de arriba) - const allEvents = Object.entries(eventConfig) - .filter(([_, enabled]) => enabled) - .map(([event]) => event); - - - const { data } = await axios.post(`${config.API_URL}/registerWebhook`, { - args: { url, events: allEvents, id: 'nucleo-bot' } - }); - - log('info', '✔️ Webhook registrado:', data); -} - - -export async function setTypingStatus(chatId, status) { - try { - const { data } = await axios.post(`${config.API_URL}/simulateTyping`, { - args: { to: chatId, on: status } - }); - log('debug', 'Respuesta de /setChatState →', data); - } catch (e) { - log('error', 'Fallo /setChatState:', e.response?.data ?? e.message); - } -} - diff --git a/conversation-layer-agent/.dockerignore b/conversation-layer-agent/.dockerignore new file mode 100644 index 0000000..93f1361 --- /dev/null +++ b/conversation-layer-agent/.dockerignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/conversation-layer-agent/Dockerfile b/conversation-layer-agent/Dockerfile new file mode 100644 index 0000000..2f6ec7c --- /dev/null +++ b/conversation-layer-agent/Dockerfile @@ -0,0 +1,17 @@ +# ---------- Build stage ---------- +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# ---------- Production stage ---------- +FROM node:20-alpine +WORKDIR /app +COPY --from=builder /app/dist ./dist +COPY package*.json ./ +RUN npm install --production +ENV PORT=8001 +EXPOSE 8001 +CMD ["node", "dist/index.js"] diff --git a/conversation-layer-agent/package-lock.json b/conversation-layer-agent/package-lock.json new file mode 100644 index 0000000..62ae8fd --- /dev/null +++ b/conversation-layer-agent/package-lock.json @@ -0,0 +1,2426 @@ +{ + "name": "conversation-layer-agent", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "conversation-layer-agent", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@google/genai": "^1.4.0", + "@modelcontextprotocol/sdk": "^1.12.1", + "dotenv": "^16.5.0", + "express": "^4.18.2" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.11.19", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "typescript": "^5.4.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google/genai": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.4.0.tgz", + "integrity": "sha512-u9LQZbWBhqaaLelCcYsxMNDTeW12jzNwGkI/eqUeMG/iB1gJBu56LCxrFJ/hkHeZQgPg+j1pckBLZS/dnOh+Bw==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.14.2", + "ws": "^8.18.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.11.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", + "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", + "integrity": "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", + "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/zod": { + "version": "3.25.51", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.51.tgz", + "integrity": "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/conversation-layer-agent/package.json b/conversation-layer-agent/package.json new file mode 100644 index 0000000..4cb2078 --- /dev/null +++ b/conversation-layer-agent/package.json @@ -0,0 +1,24 @@ +{ + "name": "conversation-layer-agent", + "version": "1.0.0", + "main": "dist/index.js", + "license": "MIT", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "nodemon --watch src --ext ts --exec \"ts-node src/index.ts\"" + }, + "dependencies": { + "express": "^4.18.2", + "@google/genai": "^1.4.0", + "@modelcontextprotocol/sdk": "^1.12.1", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "typescript": "^5.4.5", + "@types/node": "^20.11.19", + "@types/express": "^4.17.21", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2" + } +} diff --git a/conversation-layer-agent/src/index.ts b/conversation-layer-agent/src/index.ts new file mode 100644 index 0000000..273b1ac --- /dev/null +++ b/conversation-layer-agent/src/index.ts @@ -0,0 +1,185 @@ +import express from 'express'; +import { GoogleGenAI, mcpToTool } from '@google/genai'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import dotenv from 'dotenv'; + +dotenv.config(); + +interface Participant { + id: string; + name: string; + isMe: boolean; + isAdmin?: boolean; +} + +interface Msg { + id: string; + from: string; + to: string; + ts: number; + type: 'chat' | 'image' | 'audio' | 'sticker' | 'doc'; + text?: string; + mediaUrl?: string; + mentions?: string[]; + meta: { + ack: number; + hasReaction: boolean; + isQuoted: boolean; + }; +} + +interface Conversation { + chatId: string; + title: string; + isGroup: boolean; + unreadCount: number; + participants: Participant[]; + messages: Msg[]; + createdAt: number; +} + +const PORT = Number(process.env.PORT) || 8001; +const API_KEY = process.env.GEMINI_API_KEY || ''; +console.log(`Using Gemini API key: ${API_KEY}`); + +const genAI = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null; + +const MCP_URL = process.env.MCP_URL || 'http://planilla.interno.com/mcp'; +let mcpClient: Client | undefined; +let mcpTransport: StreamableHTTPClientTransport | undefined; + +async function getMcpClient(): Promise { + if (!mcpClient) { + mcpClient = new Client({ name: 'planilla-client', version: '1.0.0' }); + mcpTransport = new StreamableHTTPClientTransport(new URL(MCP_URL)); + await mcpClient.connect(mcpTransport); + } + return mcpClient; +} +/** + * Descripción de alto nivel para que cualquier agente (humano o LLM) entienda y + * trabaje con el repositorio sin perder tiempo buscando contexto. + */ +const repoInfo = ` +📦 RESUMEN +Este repo orquesta tres servicios complementarios: + 1. whatsapp-router → Recibe webhooks de OpenWA y re-expide los mensajes al agente o la UI. + 2. conversation-layer-agent → LLM que responde dudas sobre el código y ejecuta acciones. + 3. chat-ui → Interfaz web mínima que conversa con el agente. + +🗂 ESTRUCTURA CLAVE +│ +├─ docker-compose.yml # Levanta todo el stack +├─ whatsapp-router/ +│ ├─ src/chatHandlers.ts # Mapeo chatId → handler; ¡editá aquí para nuevos agentes! +│ └─ … # Lógica de ruteo y validaciones +├─ conversation-layer-agent/ +│ ├─ src/index.ts # Entrada principal del agente +│ └─ prompts/system.ts # Prompt base; importa repoInfo +└─ chat-ui/ # Frontend Vite + React (TypeScript) + └─ … + +⚙️ VARIABLES DE ENTORNO (ejemplo .env) +OPEN_WA_URL=http://openwa:8080 +LLM_AGENT_URL=http://conversation-layer-agent:8000 +PORT=3001 # Puerto del router +NODE_ENV=development # Cambiá a production para desactivar logs verbosos + +🚀 CÓMO LEVANTAR +1) cp .env.example .env && edítalo según tu entorno +2) docker-compose up -d --build +3) Visita http://localhost:3000 (UI) o revisá logs con \`docker-compose logs -f\`. + +🔄 FLUJO DE MENSAJES +OpenWA → whatsapp-router (/webhook) → handler ↔ conversation-layer-agent ↔ chat-ui + +🤖 GUÍA RÁPIDA PARA AGENTES LLM +- Responde corto, en el tono del usuario (“vos”, español hondureño). +- Usa SOLO la info del objeto \`Conversation\`; no mantengas estado entre turnos. +- Si falta contexto, pedilo en una línea. +- No repitas instrucciones ni digas que sos IA. +- Devuelve \`{ reply: string, actions?: any[] }\` (JSON puro) para facilitar parsing. + +🔧 COMANDOS ÚTILES +npm run dev # Hot-reload del router +npm test # Ejecuta los tests +docker exec -it openwa sh # Shell dentro del contenedor OpenWA +git remote -v # Confirma remotos (origin: Gitea, github: mirror) + +🔐 SEGURIDAD +- Los tokens/API keys van en variables de entorno; nunca los subas al repo. +- Usa certificados válidos o \`NODE_TLS_REJECT_UNAUTHORIZED=0\` SOLO en dev. + +✍️ CONTRIBUCIONES +Push a rama feature/* → CI/CD en Gitea valida lint, tests y build. +Crea PR para revisión; no mezcles cambios de lógica y formato en el mismo commit. + +📜 LICENCIA +GPL-3.0 — libre de usar, modificar y redistribuir mientras mantengas la misma licencia. + +¡Listo! Con esto cualquier agente debería orientarse y empezar a trabajar sin drama. +`; + + +const app = express(); +app.use(express.json()); + +app.post('/', async (req, res) => { + const conversation = req.body?.conversation as Conversation | undefined; + if (!conversation) return res.status(400).json({ error: 'Missing conversation' }); + const lastMsg = conversation.messages[conversation.messages.length - 1]; + const message = lastMsg?.text || ''; + + const context = conversation.messages + .slice(-10) + .map((m) => { + const sender = + conversation.participants.find((p) => p.id === m.from)?.name || m.from; + const content = m.text || `[${m.type}]`; + return `${sender}: ${content}`; + }) + .join('\n'); + + if (!genAI) { + return res.json({ reply: repoInfo }); + } + + try { + const contents = `Repo information: ${repoInfo}\nConversation:\n${context}\n`; + const config: any = {}; + // if (message.toLowerCase().includes('/planilla')) { + if (true) { + console.log('Using Model Context Protocol tools ', MCP_URL); + const client = await getMcpClient(); + config.tools = [mcpToTool(client)]; + } + const result = await genAI.models.generateContent({ + model: 'gemini-2.0-flash', + contents, + config, + }); + const reply = (result.text || '').trim(); + res.json({ reply }); + } catch (err: any) { + console.error('Gemini error', err.message); + res.status(500).json({ error: 'Failed to generate reply' }); + } +}); + +app.get('/', (req, res) => { + res.send(` +

Conversation Layer Agent

+

This service answers questions about the repository.

+

Send a POST request to / with a JSON body containing {"conversation": {...}}

+

Example: {"conversation": {"chatId": "123@c.us", "title": "Chat", "isGroup": false, "unreadCount": 0, "participants": [{"id": "123@c.us", "name": "Alice", "isMe": false}], "messages": [{"id": "m1", "from": "123@c.us", "to": "me@c.us", "ts": 0, "type": "chat", "text": "hello", "meta": {"ack":0,"hasReaction":false,"isQuoted":false}}]}}

+

It will respond with a JSON object containing {"reply": "the answer"}

+ +

Repository info: ${repoInfo}

+ `); +} +); + +app.listen(PORT, () => { + console.log(`conversation-layer-agent listening on ${PORT}`); +}); \ No newline at end of file diff --git a/conversation-layer-agent/tsconfig.json b/conversation-layer-agent/tsconfig.json new file mode 100644 index 0000000..b2f1581 --- /dev/null +++ b/conversation-layer-agent/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "lib": ["es2020"], + "moduleResolution": "node" + }, + "include": ["src"] +} diff --git a/docker-compose.yml b/docker-compose.yml index 22633f6..e55beec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,10 +2,17 @@ version: "3.8" services: agent: + container_name: planilla-agent image: gitea.interno.com/nucleo000/planilla-agent:latest build: ./agent + + environment: + - PORT=8012 + - GEMINI_API_KEY= AIzaSyA9fI1mron-NVgghygu7B4sco7t6raXB8M + - MCP_URL= http:planilla-mcp:5000/mcp + restart: unless-stopped - networks: [planilla] + networks: [planilla, principal] api: container_name: planilla-api