From d91d73300a5ddaeda311ec741adacb000b4a0521 Mon Sep 17 00:00:00 2001 From: M Shehzen Date: Sat, 20 Jun 2026 14:50:44 +0530 Subject: [PATCH 1/8] feat(whatsapp): integrate Baileys WhatsApp channel with group-based messaging - Add WhatsAppChannel implementing Channel interface via @whiskeysockets/baileys - Group-based approach: Mercury listens to a WhatsApp group named 'Mercury' - QR code pairing via qrcode-terminal (device linking) - Admin pairing via /pair command in the group - Auto-detect group after connection using groupFetchAllParticipating() - Rate limiting (3s min, 60/hr per chat, 200/hr global, 1000/day) - Reconnection with exponential backoff; immediate reconnect on 515 - Suppress Baileys internal logs (pino level: fatal) - Config tracks: registered, paired, adminPaired, groupId, groupName - CLI: mercury whatsapp status/pair/reset/unregister/list/detect-group/groups/set-group - Onboarding flow resumes from incomplete steps (doctor) - send_file tool supports 'whatsapp' channel - Notification priority: Signal > Telegram > Discord > Slack > WhatsApp > CLI - .env.example: WHATSAPP_ENABLED, WHATSAPP_PHONE_NUMBER, WHATSAPP_MODE, WHATSAPP_GROUP_NAME - tsup externals: @whiskeysockets/baileys, protobufjs, qrcode-terminal, discord.js - protobufjs pinned to 7.4.0 for directory structure stability --- .env.example | 9 + package-lock.json | 2723 ++++++++++++++++++++-- package.json | 4 + src/capabilities/filesystem/send-file.ts | 4 +- src/channels/index.ts | 1 + src/channels/registry.ts | 7 + src/channels/whatsapp.ts | 736 ++++++ src/index.ts | 486 ++++ src/types/channel.ts | 21 + src/utils/config.ts | 90 +- src/whatsapp/auth.ts | 47 + src/whatsapp/rate-limiter.ts | 129 + tsup.config.ts | 4 + 13 files changed, 4123 insertions(+), 138 deletions(-) create mode 100644 src/channels/whatsapp.ts create mode 100644 src/whatsapp/auth.ts create mode 100644 src/whatsapp/rate-limiter.ts diff --git a/.env.example b/.env.example index b89e497f..84b5b612 100644 --- a/.env.example +++ b/.env.example @@ -110,6 +110,15 @@ SLACK_TEAM_ID= # Enable streaming responses (edit-in-place) SLACK_STREAMING=true +# ── WhatsApp ── +# Chat with Mercury through a WhatsApp group (self-chat not supported by WhatsApp protocol) +# Scan QR code during setup to link as a companion device +# Create a WhatsApp group named "Mercury" (just yourself) — Mercury auto-detects it +WHATSAPP_ENABLED=false +WHATSAPP_PHONE_NUMBER= +WHATSAPP_MODE=group +WHATSAPP_GROUP_NAME=Mercury + # ── Memory ── MEMORY_DIR=./memory diff --git a/package-lock.json b/package-lock.json index 2948a6ab..8510ca8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cosmicstack/mercury-agent", - "version": "1.1.12", + "version": "1.1.13", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cosmicstack/mercury-agent", - "version": "1.1.12", + "version": "1.1.13", "license": "MIT", "dependencies": { "@ai-sdk/anthropic": "^3.0.71", @@ -15,6 +15,7 @@ "@grammyjs/auto-retry": "^2.0.2", "@hono/node-server": "^2.0.0", "@slack/bolt": "^4.7.3", + "@whiskeysockets/baileys": "^7.0.0-rc13", "ai": "^6.0.168", "bcryptjs": "^3.0.3", "chalk": "^5.4.0", @@ -28,6 +29,8 @@ "node-cron": "^3.0.3", "ollama-ai-provider": "^1.2.0", "pino": "^10.3.1", + "protobufjs": "7.4.0", + "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", "react": "^18.3.1", "sql.js": "^1.14.1", @@ -42,6 +45,7 @@ "@types/better-sqlite3": "^7.6.13", "@types/node": "^22.13.0", "@types/node-cron": "^3.0.11", + "@types/qrcode": "^1.5.6", "@types/qrcode-terminal": "^0.12.2", "@types/react": "^18.3.28", "react-devtools-core": "^4.28.5", @@ -156,6 +160,52 @@ "node": ">=14.13.1" } }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.9.tgz", + "integrity": "sha512-HdMx6DoGywB30vacDbBsITbIX4pgFqj1zsrV58jZBUw3klzkNoXhj7qOqAgledhxG7YZI5rBSJg7Zp8/VG0DuA==", + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.4.1", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/node-cache": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@cacheable/node-cache/-/node-cache-1.7.6.tgz", + "integrity": "sha512-6Omk2SgNnjtxB5f/E6bTIWIt5xhdpx39fGNRQgU9lojvRxU68v+qY+SXXLsp3ZGukqoPjsK21wZ6XABFr/Ge3A==", + "license": "MIT", + "dependencies": { + "cacheable": "^2.3.1", + "hookified": "^1.14.0", + "keyv": "^5.5.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==", + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "node_modules/@discordjs/builders": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.1.tgz", @@ -296,6 +346,17 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -600,146 +661,688 @@ "node": ">=18" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@grammyjs/auto-retry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@grammyjs/auto-retry/-/auto-retry-2.0.2.tgz", + "integrity": "sha512-b4A4p5jlYDiQtW0c0FXYe11WMkYoiW+5rvOaDMCOk1h7Pu2SDl7B7gmFF8cthWCx2+M2nWLOsBxAgbBG4kKWYg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12.20.0 || >=14.13.1" + }, + "peerDependencies": { + "grammy": "^1.10.0" + } + }, + "node_modules/@grammyjs/types": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", + "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==" + }, + "node_modules/@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "9.x.x" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hono/node-server": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.3.tgz", + "integrity": "sha512-a0jV+/HRe3G5zjFID3zObAQFdkl6zpxTuqktdDDXS3MJKcrZIkB8OkLpNBlY/WXFqv2HF4a0takPej+aNFczWA==", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.1.tgz", + "integrity": "sha512-T15JRWOubQ3f5+GxnWeIvo47u5qV0M9HBgJhT+f2gE1e9e6OhR6K73Re52Hm80qWcu1DNb3GweKmpr/MnuP2Ow==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.1.tgz", + "integrity": "sha512-t1CPD0cr7XCHjwUj6tQ5MC0pCi866I+gUW6zbUX4aFPnKd1DFBtk0M+gWcjX8VeEzgfCNiSiNTVFZ6b7kvdbnQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.0" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.1.tgz", + "integrity": "sha512-MBSQXqNPThW9EcZ905H6N4sEdX5EwZEYzGx5EBq9ncDCGJALMiY1xPFJxNdzuB1iBjLOpIfxajM6YxdvwmQSLA==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.0.tgz", + "integrity": "sha512-EKbmBKtyTH+GPFDRw2TgK2oV6hyxxlJVIar4hoTYSNmIwipgMFdxPQqR392GmfdsPGWga0mCFN1cCKjRb9cljw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.0.tgz", + "integrity": "sha512-Pl2OmOvrJ42adUllESxBsG54PfXLo1OYg9i3c5/5Ln/qJ0gZuTM9YMhQJPIbXqwidLRc/c2zuHt4RsrymmNv7A==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.0.tgz", + "integrity": "sha512-A8UpHoUDW4DwnXoV6+q3C1s7QLRAHtPDEjWuNZjwHMyoCNZnm0GeNN8ls9f/bsEYTRQRW96C/n34XJQHJ2fT7A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.0.tgz", + "integrity": "sha512-C0SqjoFKnszqa44EQ7xoaT48nnO0lOyXEULfXMWi8krrjOPGYkeK30Okzla6ATbBYsyZ0ySinK0FVkpv3DwzfQ==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.0.tgz", + "integrity": "sha512-WOpkVxAjFd369iaIzEgNRreFD+gWdUMIGD5zplhNKNeqS6mm5dac3q2AFyCBmzYoAdouzZvRBgxy4z8QHZb4/A==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.0.tgz", + "integrity": "sha512-DRWw0mOHusrCCuw2rqP87oLg6PGlkomVDFqw2hIwsSfwWpu4k3XLcBPaKKl6ct/GtL/cwNkgwjV/tc0Mqht3VA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.0.tgz", + "integrity": "sha512-9APy+nFWhHS+kzLgWZfLcyrUd7YqnAQVa4BPOo4xkoHpdoktOAPG4cEr9+Jpl0TtqfVmcMJimNL5qNTyyOHZNA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.0.tgz", + "integrity": "sha512-y9RNUYDe2A1UAdhLyfeOodGRszQdaEoe4nfOpp/sNVPl2CWIcUyFaDoCh4vPLPxu19803j2naLqZup2WxDXCLA==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.0.tgz", + "integrity": "sha512-cC1wkC0Mlucd0KSiGrLkJnB/ZqPvZCntc/Lk7ZnYO5ZSbF2euNek4Xvxafojq+wN1q/W0eprdpUIjUr/EV2PBg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.0.tgz", + "integrity": "sha512-LiYMhUZicB1QG//+RvmYZpXJO8fYRENfp+MZUCnG9aw+AKvGAy9gPaCnuwsPcBFs8EV66M0NNxj9VHcNklE8zw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.1.tgz", + "integrity": "sha512-jygmR02PpCYypt7xB7nst1vqjZp/BpRA/Kf9nK7qRponJ/KrLPaZWEG4G15z1d2FZ6XqI+T0350ha3RSnKx24A==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.1.tgz", + "integrity": "sha512-ErCRyGU7LeoaFBZ0xW8hhLlXzhAg80sc4vxePB86qvtEvW1jEhhmbiNBP4oEzZfPMnu6HwHXfzD2W2kBU+RnCw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.0" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.1.tgz", + "integrity": "sha512-LUWZ2+r2UoLCd8j0RLCwQ4gL6w47+Y7igxtVnPIDXOOEjV86LpBkAHq5VpJeg+GHbw0KN/JWlPJOdZjyZnFqFQ==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.0" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.1.tgz", + "integrity": "sha512-i7x6J3mwF4JgT0sM4V4WlAWdJ0bucPtA9rzO1bTji1n5qgBq/W5nn87RvOQPleuuxahNoLdTngByD8/vDDLArw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.1.tgz", + "integrity": "sha512-0zSaTUjTF0kIWTSYxD4EG/nvCU4jez53+3RdURtoY3HvbXtIQ98W90JnrGz/oLRFuEnfIy9+7xeq883euc0ZWw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.1.tgz", + "integrity": "sha512-NbJD4mWdeyrNQKluO/tR/wBDOelcowSVGNBWxI0e3ZtlXc6F/UOVKDj1MLD4zl3oHTuvKW3s+MA9N54YTldAYw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.1.tgz", + "integrity": "sha512-VoW2sQCWI+0YIKQEmWJ8vzaQjTg9wIyfkFpvEfAS2h43X6iHu7GTk1hhOgB4IpSzCHe8UwQZIcx7b81VTaOrJA==", "cpu": [ "arm64" ], - "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ - "openbsd" + "linux" ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.1.tgz", + "integrity": "sha512-LjBoSd/c5JU0/K5MwzDMlgsSRP2bPn98JQGFFQAOLQ0bU/1z4ekxUdSKY9BmlwSh/cA+OrvpgsWqfZyYfVHBRw==", "cpu": [ "x64" ], - "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ - "openbsd" + "linux" ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.0" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.1.tgz", + "integrity": "sha512-PCQUoQdZyE8tp3HpbevuihfUmgSP4qWI0FGEPWoeXqaS+cUrFfemabHQiebUmUmlUhCuNnQMxGrQ+CPqK4hnxg==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, - "os": [ - "openharmony" - ], + "peer": true, + "dependencies": { + "@emnapi/runtime": "^1.11.0" + }, "engines": { - "node": ">=18" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.1.tgz", + "integrity": "sha512-xU2ml2bU2OPxYVvW2A6ae4M1g5QKyhKG06P4FAt+YEaFQQO0919Qx+XxIZEUuWTMoDViLpMws2/dQwoe/VcA6A==", "cpu": [ - "x64" + "wasm32" ], - "dev": true, + "license": "Apache-2.0", "optional": true, - "os": [ - "sunos" - ], + "peer": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.1" + }, "engines": { - "node": ">=18" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.1.tgz", + "integrity": "sha512-IkmHwuFhYpd3bTsN5SAahjwhiAcyXPooBt8vEUgxY3T0IP70sSJ0nU1xiPzZY8AH/OB1XpV3j8aZSVSOSfTbdA==", "cpu": [ "arm64" ], - "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.1.tgz", + "integrity": "sha512-wQahqCi9MD8Yxzg4gVM4fNrZxh+r6vD55PyIg+WJPaM5ZRUyF35iQpwJCuma3r6viU9/8Pxlc+XHV+woVa6nCQ==", "cpu": [ "ia32" ], - "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { - "node": ">=18" + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.1.tgz", + "integrity": "sha512-WzBtkYtZHATLPe8XRharxZXxQ9cdLrQWHiwxt+BJ5rBsisQrKeeV86ErxPSVhcG6xCEuNhs0SqLpWr7XDa2k6w==", "cpu": [ "x64" ], - "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { - "node": ">=18" - } - }, - "node_modules/@grammyjs/auto-retry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@grammyjs/auto-retry/-/auto-retry-2.0.2.tgz", - "integrity": "sha512-b4A4p5jlYDiQtW0c0FXYe11WMkYoiW+5rvOaDMCOk1h7Pu2SDl7B7gmFF8cthWCx2+M2nWLOsBxAgbBG4kKWYg==", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">=12.20.0 || >=14.13.1" - }, - "peerDependencies": { - "grammy": "^1.10.0" - } - }, - "node_modules/@grammyjs/types": { - "version": "3.27.3", - "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", - "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==" - }, - "node_modules/@hono/node-server": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.3.tgz", - "integrity": "sha512-a0jV+/HRe3G5zjFID3zObAQFdkl6zpxTuqktdDDXS3MJKcrZIkB8OkLpNBlY/WXFqv2HF4a0takPej+aNFczWA==", - "engines": { - "node": ">=20" + "node": ">=20.9.0" }, - "peerDependencies": { - "hono": "^4" + "funding": { + "url": "https://opencollective.com/libvips" } }, "node_modules/@jridgewell/gen-mapping": { @@ -777,6 +1380,28 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "license": "MIT" + }, "node_modules/@opentelemetry/api": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", @@ -790,6 +1415,69 @@ "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -1260,6 +1948,29 @@ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, "node_modules/@types/bcryptjs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-3.0.0.tgz", @@ -1390,6 +2101,16 @@ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "devOptional": true }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qrcode-terminal": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/qrcode-terminal/-/qrcode-terminal-0.12.2.tgz", @@ -1573,14 +2294,145 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", - "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@whiskeysockets/baileys": { + "version": "7.0.0-rc13", + "resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-7.0.0-rc13.tgz", + "integrity": "sha512-8JPc8gaaCRykkjW2jxLGQ7/RZGrc7awO7WU+QJocf58eSUI9jAdcuYLynzhAbyU4UWvJJsHImZ+5E/JaZj5ypA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@cacheable/node-cache": "^1.4.0", + "@hapi/boom": "^9.1.3", + "async-mutex": "^0.5.0", + "libsignal": "^6.0.0", + "lru-cache": "^11.1.0", + "music-metadata": "^11.12.3", + "p-queue": "^9.0.0", + "pino": "^9.6", + "protobufjs": "^7.5.6", + "whatsapp-rust-bridge": "0.5.4", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "audio-decode": "^2.1.3", + "jimp": "^1.6.1", + "link-preview-js": "^3.0.0", + "sharp": "*" + }, + "peerDependenciesMeta": { + "audio-decode": { + "optional": true + }, + "jimp": { + "optional": true + }, + "link-preview-js": { + "optional": true + } + } + }, + "node_modules/@whiskeysockets/baileys/node_modules/p-queue": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.0.tgz", + "integrity": "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@whiskeysockets/baileys/node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@whiskeysockets/baileys/node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/@whiskeysockets/baileys/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/@whiskeysockets/baileys/node_modules/protobufjs": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@whiskeysockets/baileys/node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "dependencies": { + "real-require": "^0.2.0" } }, "node_modules/abort-controller": { @@ -1699,6 +2551,15 @@ "node": ">=12" } }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1898,6 +2759,19 @@ "node": ">=8" } }, + "node_modules/cacheable": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.5.tgz", + "integrity": "sha512-EQfaKe09tl615iNvq/TBRWTFf1AKJNXYQSsMx0Z3EI0nA+pVsVPS8wJhnRlkbdacKPh1d0qVIhwTc2zsQNFEEg==", + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.0.8", + "@cacheable/utils": "^2.4.1", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "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", @@ -1927,6 +2801,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -2039,6 +2922,96 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/code-excerpt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", @@ -2050,6 +3023,24 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2139,6 +3130,12 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true }, + "node_modules/curve25519-js": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz", + "integrity": "sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2155,6 +3152,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -2210,11 +3216,16 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "optional": true, "engines": { "node": ">=8" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/discord-api-types": { "version": "0.38.48", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.48.tgz", @@ -2560,6 +3571,24 @@ } } }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -2587,6 +3616,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fix-dts-default-cjs-exports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", @@ -2702,6 +3744,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-east-asian-width": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", @@ -2809,6 +3860,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -2829,6 +3892,12 @@ "node": ">=16.9.0" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -2895,8 +3964,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "optional": true + ] }, "node_modules/indent-string": { "version": "5.0.0", @@ -3087,6 +4155,48 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/libsignal": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/libsignal/-/libsignal-6.0.0.tgz", + "integrity": "sha512-d/5V3YFtDljbFMufz4ncyUYGYhJl+vzAe+c2EFFBQ6bz1h8Q3IOMEGXYMzlibU60I+e8GagMMpji18iez3P1hA==", + "license": "GPL-3.0", + "dependencies": { + "curve25519-js": "^0.0.4", + "protobufjs": "^7.5.5" + } + }, + "node_modules/libsignal/node_modules/protobufjs": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -3114,6 +4224,18 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -3168,6 +4290,12 @@ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3185,6 +4313,15 @@ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-bytes.js": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz", @@ -3318,6 +4455,63 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/music-metadata": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.13.0.tgz", + "integrity": "sha512-uXRaov9dfjSpQufXIU7sMxVZnh+FilCQv2mXn+K5EJ/decP3dTWrgvPYa5r6MtRbieNSCE708Da4J0u1UGfQIw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.2", + "@tokenizer/token": "^0.3.0", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "file-type": "^21.3.4", + "media-typer": "^2.0.0", + "strtok3": "^10.3.5", + "token-types": "^6.1.2", + "uint8array-extras": "^1.5.0", + "win-guid": "^0.2.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/music-metadata/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/music-metadata/node_modules/media-typer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-2.0.0.tgz", + "integrity": "sha512-kOy3OxT2HH39N70UnKgu4NWDZjLOz8W/mfyvniHjRH/DrL3f2pOfvWQ4p60offbbtDAnXWp0v9LfMIqMec269Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -3523,6 +4717,33 @@ "node": ">=4" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-queue": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", @@ -3570,6 +4791,15 @@ "node": ">=8" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3592,6 +4822,15 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -3691,6 +4930,15 @@ "pathe": "^2.0.1" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", @@ -3803,6 +5051,30 @@ } ] }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3831,8 +5103,43 @@ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "optional": true, "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "license": "MIT" + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" } }, "node_modules/qrcode-terminal": { @@ -3995,6 +5302,21 @@ "node": ">= 12.13.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -4135,9 +5457,10 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4190,12 +5513,63 @@ "url": "https://opencollective.com/express" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "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/sharp": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.1.tgz", + "integrity": "sha512-lW979AMi+ESidzMv/Lnv+F9bknzLyxLqFI05Sm433vOeRcltgxQmXpnfOOFIAlKtwXU/ksupm2srQoFCkR214g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.1", + "@img/sharp-darwin-x64": "0.35.1", + "@img/sharp-freebsd-wasm32": "0.35.1", + "@img/sharp-libvips-darwin-arm64": "1.3.0", + "@img/sharp-libvips-darwin-x64": "1.3.0", + "@img/sharp-libvips-linux-arm": "1.3.0", + "@img/sharp-libvips-linux-arm64": "1.3.0", + "@img/sharp-libvips-linux-ppc64": "1.3.0", + "@img/sharp-libvips-linux-riscv64": "1.3.0", + "@img/sharp-libvips-linux-s390x": "1.3.0", + "@img/sharp-libvips-linux-x64": "1.3.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.0", + "@img/sharp-libvips-linuxmusl-x64": "1.3.0", + "@img/sharp-linux-arm": "0.35.1", + "@img/sharp-linux-arm64": "0.35.1", + "@img/sharp-linux-ppc64": "0.35.1", + "@img/sharp-linux-riscv64": "0.35.1", + "@img/sharp-linux-s390x": "0.35.1", + "@img/sharp-linux-x64": "0.35.1", + "@img/sharp-linuxmusl-arm64": "0.35.1", + "@img/sharp-linuxmusl-x64": "0.35.1", + "@img/sharp-webcontainers-wasm32": "0.35.1", + "@img/sharp-win32-arm64": "0.35.1", + "@img/sharp-win32-ia32": "0.35.1", + "@img/sharp-win32-x64": "0.35.1" + } + }, "node_modules/shell-quote": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", @@ -4503,6 +5877,22 @@ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -4665,6 +6055,24 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -4831,6 +6239,18 @@ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "dev": true }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/undici": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", @@ -5508,6 +6928,12 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, + "node_modules/whatsapp-rust-bridge": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/whatsapp-rust-bridge/-/whatsapp-rust-bridge-0.5.4.tgz", + "integrity": "sha512-yYO1qSs0Fe7tGtnxOFHomocUD6IZtoAgmA4oDFyGIRZ67D3QZk3w7swA6XXFXNQngiyrg2k7tul6IrM3eUFh7A==", + "license": "MIT" + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -5517,6 +6943,12 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -5547,6 +6979,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/win-guid": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", + "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==", + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -5588,6 +7026,12 @@ } } }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", @@ -5602,6 +7046,91 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yoga-layout": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", @@ -5681,6 +7210,41 @@ "is-fullwidth-code-point": "^4.0.0" } }, + "@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==" + }, + "@cacheable/memory": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.9.tgz", + "integrity": "sha512-HdMx6DoGywB30vacDbBsITbIX4pgFqj1zsrV58jZBUw3klzkNoXhj7qOqAgledhxG7YZI5rBSJg7Zp8/VG0DuA==", + "requires": { + "@cacheable/utils": "^2.4.1", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "@cacheable/node-cache": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@cacheable/node-cache/-/node-cache-1.7.6.tgz", + "integrity": "sha512-6Omk2SgNnjtxB5f/E6bTIWIt5xhdpx39fGNRQgU9lojvRxU68v+qY+SXXLsp3ZGukqoPjsK21wZ6XABFr/Ge3A==", + "requires": { + "cacheable": "^2.3.1", + "hookified": "^1.14.0", + "keyv": "^5.5.5" + } + }, + "@cacheable/utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==", + "requires": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "@discordjs/builders": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.1.tgz", @@ -5767,6 +7331,16 @@ } } }, + "@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "optional": true, + "peer": true, + "requires": { + "tslib": "^2.4.0" + } + }, "@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -5935,38 +7509,278 @@ "dev": true, "optional": true }, - "@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "dev": true, - "optional": true + "@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "dev": true, + "optional": true + }, + "@grammyjs/auto-retry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@grammyjs/auto-retry/-/auto-retry-2.0.2.tgz", + "integrity": "sha512-b4A4p5jlYDiQtW0c0FXYe11WMkYoiW+5rvOaDMCOk1h7Pu2SDl7B7gmFF8cthWCx2+M2nWLOsBxAgbBG4kKWYg==", + "requires": { + "debug": "^4.3.4" + } + }, + "@grammyjs/types": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", + "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==" + }, + "@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "requires": { + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "@hono/node-server": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.3.tgz", + "integrity": "sha512-a0jV+/HRe3G5zjFID3zObAQFdkl6zpxTuqktdDDXS3MJKcrZIkB8OkLpNBlY/WXFqv2HF4a0takPej+aNFczWA==", + "requires": {} + }, + "@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "peer": true + }, + "@img/sharp-darwin-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.1.tgz", + "integrity": "sha512-T15JRWOubQ3f5+GxnWeIvo47u5qV0M9HBgJhT+f2gE1e9e6OhR6K73Re52Hm80qWcu1DNb3GweKmpr/MnuP2Ow==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-darwin-arm64": "1.3.0" + } + }, + "@img/sharp-darwin-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.1.tgz", + "integrity": "sha512-t1CPD0cr7XCHjwUj6tQ5MC0pCi866I+gUW6zbUX4aFPnKd1DFBtk0M+gWcjX8VeEzgfCNiSiNTVFZ6b7kvdbnQ==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-darwin-x64": "1.3.0" + } + }, + "@img/sharp-freebsd-wasm32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.1.tgz", + "integrity": "sha512-MBSQXqNPThW9EcZ905H6N4sEdX5EwZEYzGx5EBq9ncDCGJALMiY1xPFJxNdzuB1iBjLOpIfxajM6YxdvwmQSLA==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-wasm32": "0.35.1" + } + }, + "@img/sharp-libvips-darwin-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.0.tgz", + "integrity": "sha512-EKbmBKtyTH+GPFDRw2TgK2oV6hyxxlJVIar4hoTYSNmIwipgMFdxPQqR392GmfdsPGWga0mCFN1cCKjRb9cljw==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-darwin-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.0.tgz", + "integrity": "sha512-Pl2OmOvrJ42adUllESxBsG54PfXLo1OYg9i3c5/5Ln/qJ0gZuTM9YMhQJPIbXqwidLRc/c2zuHt4RsrymmNv7A==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-linux-arm": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.0.tgz", + "integrity": "sha512-A8UpHoUDW4DwnXoV6+q3C1s7QLRAHtPDEjWuNZjwHMyoCNZnm0GeNN8ls9f/bsEYTRQRW96C/n34XJQHJ2fT7A==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-linux-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.0.tgz", + "integrity": "sha512-C0SqjoFKnszqa44EQ7xoaT48nnO0lOyXEULfXMWi8krrjOPGYkeK30Okzla6ATbBYsyZ0ySinK0FVkpv3DwzfQ==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-linux-ppc64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.0.tgz", + "integrity": "sha512-WOpkVxAjFd369iaIzEgNRreFD+gWdUMIGD5zplhNKNeqS6mm5dac3q2AFyCBmzYoAdouzZvRBgxy4z8QHZb4/A==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-linux-riscv64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.0.tgz", + "integrity": "sha512-DRWw0mOHusrCCuw2rqP87oLg6PGlkomVDFqw2hIwsSfwWpu4k3XLcBPaKKl6ct/GtL/cwNkgwjV/tc0Mqht3VA==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-linux-s390x": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.0.tgz", + "integrity": "sha512-9APy+nFWhHS+kzLgWZfLcyrUd7YqnAQVa4BPOo4xkoHpdoktOAPG4cEr9+Jpl0TtqfVmcMJimNL5qNTyyOHZNA==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-linux-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.0.tgz", + "integrity": "sha512-y9RNUYDe2A1UAdhLyfeOodGRszQdaEoe4nfOpp/sNVPl2CWIcUyFaDoCh4vPLPxu19803j2naLqZup2WxDXCLA==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.0.tgz", + "integrity": "sha512-cC1wkC0Mlucd0KSiGrLkJnB/ZqPvZCntc/Lk7ZnYO5ZSbF2euNek4Xvxafojq+wN1q/W0eprdpUIjUr/EV2PBg==", + "optional": true, + "peer": true + }, + "@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.0.tgz", + "integrity": "sha512-LiYMhUZicB1QG//+RvmYZpXJO8fYRENfp+MZUCnG9aw+AKvGAy9gPaCnuwsPcBFs8EV66M0NNxj9VHcNklE8zw==", + "optional": true, + "peer": true + }, + "@img/sharp-linux-arm": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.1.tgz", + "integrity": "sha512-jygmR02PpCYypt7xB7nst1vqjZp/BpRA/Kf9nK7qRponJ/KrLPaZWEG4G15z1d2FZ6XqI+T0350ha3RSnKx24A==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-linux-arm": "1.3.0" + } + }, + "@img/sharp-linux-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.1.tgz", + "integrity": "sha512-ErCRyGU7LeoaFBZ0xW8hhLlXzhAg80sc4vxePB86qvtEvW1jEhhmbiNBP4oEzZfPMnu6HwHXfzD2W2kBU+RnCw==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-linux-arm64": "1.3.0" + } + }, + "@img/sharp-linux-ppc64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.1.tgz", + "integrity": "sha512-LUWZ2+r2UoLCd8j0RLCwQ4gL6w47+Y7igxtVnPIDXOOEjV86LpBkAHq5VpJeg+GHbw0KN/JWlPJOdZjyZnFqFQ==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-linux-ppc64": "1.3.0" + } + }, + "@img/sharp-linux-riscv64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.1.tgz", + "integrity": "sha512-i7x6J3mwF4JgT0sM4V4WlAWdJ0bucPtA9rzO1bTji1n5qgBq/W5nn87RvOQPleuuxahNoLdTngByD8/vDDLArw==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-linux-riscv64": "1.3.0" + } + }, + "@img/sharp-linux-s390x": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.1.tgz", + "integrity": "sha512-0zSaTUjTF0kIWTSYxD4EG/nvCU4jez53+3RdURtoY3HvbXtIQ98W90JnrGz/oLRFuEnfIy9+7xeq883euc0ZWw==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-linux-s390x": "1.3.0" + } + }, + "@img/sharp-linux-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.1.tgz", + "integrity": "sha512-NbJD4mWdeyrNQKluO/tR/wBDOelcowSVGNBWxI0e3ZtlXc6F/UOVKDj1MLD4zl3oHTuvKW3s+MA9N54YTldAYw==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-linux-x64": "1.3.0" + } + }, + "@img/sharp-linuxmusl-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.1.tgz", + "integrity": "sha512-VoW2sQCWI+0YIKQEmWJ8vzaQjTg9wIyfkFpvEfAS2h43X6iHu7GTk1hhOgB4IpSzCHe8UwQZIcx7b81VTaOrJA==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.0" + } + }, + "@img/sharp-linuxmusl-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.1.tgz", + "integrity": "sha512-LjBoSd/c5JU0/K5MwzDMlgsSRP2bPn98JQGFFQAOLQ0bU/1z4ekxUdSKY9BmlwSh/cA+OrvpgsWqfZyYfVHBRw==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.0" + } }, - "@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "dev": true, - "optional": true + "@img/sharp-wasm32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.1.tgz", + "integrity": "sha512-PCQUoQdZyE8tp3HpbevuihfUmgSP4qWI0FGEPWoeXqaS+cUrFfemabHQiebUmUmlUhCuNnQMxGrQ+CPqK4hnxg==", + "optional": true, + "peer": true, + "requires": { + "@emnapi/runtime": "^1.11.0" + } }, - "@grammyjs/auto-retry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@grammyjs/auto-retry/-/auto-retry-2.0.2.tgz", - "integrity": "sha512-b4A4p5jlYDiQtW0c0FXYe11WMkYoiW+5rvOaDMCOk1h7Pu2SDl7B7gmFF8cthWCx2+M2nWLOsBxAgbBG4kKWYg==", + "@img/sharp-webcontainers-wasm32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.1.tgz", + "integrity": "sha512-xU2ml2bU2OPxYVvW2A6ae4M1g5QKyhKG06P4FAt+YEaFQQO0919Qx+XxIZEUuWTMoDViLpMws2/dQwoe/VcA6A==", + "optional": true, + "peer": true, "requires": { - "debug": "^4.3.4" + "@img/sharp-wasm32": "0.35.1" } }, - "@grammyjs/types": { - "version": "3.27.3", - "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", - "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==" + "@img/sharp-win32-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.1.tgz", + "integrity": "sha512-IkmHwuFhYpd3bTsN5SAahjwhiAcyXPooBt8vEUgxY3T0IP70sSJ0nU1xiPzZY8AH/OB1XpV3j8aZSVSOSfTbdA==", + "optional": true, + "peer": true }, - "@hono/node-server": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.3.tgz", - "integrity": "sha512-a0jV+/HRe3G5zjFID3zObAQFdkl6zpxTuqktdDDXS3MJKcrZIkB8OkLpNBlY/WXFqv2HF4a0takPej+aNFczWA==", - "requires": {} + "@img/sharp-win32-ia32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.1.tgz", + "integrity": "sha512-wQahqCi9MD8Yxzg4gVM4fNrZxh+r6vD55PyIg+WJPaM5ZRUyF35iQpwJCuma3r6viU9/8Pxlc+XHV+woVa6nCQ==", + "optional": true, + "peer": true + }, + "@img/sharp-win32-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.1.tgz", + "integrity": "sha512-WzBtkYtZHATLPe8XRharxZXxQ9cdLrQWHiwxt+BJ5rBsisQrKeeV86ErxPSVhcG6xCEuNhs0SqLpWr7XDa2k6w==", + "optional": true, + "peer": true }, "@jridgewell/gen-mapping": { "version": "0.3.13", @@ -6000,6 +7814,20 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "requires": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + } + }, + "@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==" + }, "@opentelemetry/api": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", @@ -6010,6 +7838,59 @@ "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==" + }, + "@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "requires": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==" + }, "@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -6283,6 +8164,20 @@ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" }, + "@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "requires": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + } + }, + "@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "@types/bcryptjs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-3.0.0.tgz", @@ -6405,6 +8300,15 @@ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "devOptional": true }, + "@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/qrcode-terminal": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/qrcode-terminal/-/qrcode-terminal-0.12.2.tgz", @@ -6550,6 +8454,92 @@ "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==" }, + "@whiskeysockets/baileys": { + "version": "7.0.0-rc13", + "resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-7.0.0-rc13.tgz", + "integrity": "sha512-8JPc8gaaCRykkjW2jxLGQ7/RZGrc7awO7WU+QJocf58eSUI9jAdcuYLynzhAbyU4UWvJJsHImZ+5E/JaZj5ypA==", + "requires": { + "@cacheable/node-cache": "^1.4.0", + "@hapi/boom": "^9.1.3", + "async-mutex": "^0.5.0", + "libsignal": "^6.0.0", + "lru-cache": "^11.1.0", + "music-metadata": "^11.12.3", + "p-queue": "^9.0.0", + "pino": "^9.6", + "protobufjs": "^7.5.6", + "whatsapp-rust-bridge": "0.5.4", + "ws": "^8.13.0" + }, + "dependencies": { + "p-queue": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.0.tgz", + "integrity": "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==", + "requires": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + } + }, + "p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==" + }, + "pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "requires": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + } + }, + "pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "requires": { + "split2": "^4.0.0" + } + }, + "protobufjs": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + } + }, + "thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "requires": { + "real-require": "^0.2.0" + } + } + } + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -6622,6 +8612,14 @@ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true }, + "async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "requires": { + "tslib": "^2.4.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -6747,6 +8745,18 @@ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true }, + "cacheable": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.5.tgz", + "integrity": "sha512-EQfaKe09tl615iNvq/TBRWTFf1AKJNXYQSsMx0Z3EI0nA+pVsVPS8wJhnRlkbdacKPh1d0qVIhwTc2zsQNFEEg==", + "requires": { + "@cacheable/memory": "^2.0.8", + "@cacheable/utils": "^2.4.1", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "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", @@ -6765,6 +8775,11 @@ "get-intrinsic": "^1.3.0" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, "chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -6837,6 +8852,69 @@ } } }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "code-excerpt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", @@ -6845,6 +8923,19 @@ "convert-to-spaces": "^2.0.1" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6901,6 +8992,11 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true }, + "curve25519-js": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz", + "integrity": "sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==" + }, "debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -6909,6 +9005,11 @@ "ms": "^2.1.3" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -6943,8 +9044,12 @@ "detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "optional": true + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" + }, + "dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" }, "discord-api-types": { "version": "0.38.48", @@ -7195,6 +9300,17 @@ "dev": true, "requires": {} }, + "file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "requires": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -7214,6 +9330,15 @@ "statuses": "^2.0.1" } }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, "fix-dts-default-cjs-exports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", @@ -7285,6 +9410,11 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, "get-east-asian-width": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", @@ -7351,6 +9481,14 @@ "has-symbols": "^1.0.3" } }, + "hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "requires": { + "hookified": "^1.15.0" + } + }, "hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -7364,6 +9502,11 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.21.tgz", "integrity": "sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==" }, + "hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==" + }, "http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -7396,8 +9539,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "optional": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "indent-string": { "version": "5.0.0", @@ -7528,6 +9670,43 @@ "safe-buffer": "^5.0.1" } }, + "keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "requires": { + "@keyv/serialize": "^1.1.1" + } + }, + "libsignal": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/libsignal/-/libsignal-6.0.0.tgz", + "integrity": "sha512-d/5V3YFtDljbFMufz4ncyUYGYhJl+vzAe+c2EFFBQ6bz1h8Q3IOMEGXYMzlibU60I+e8GagMMpji18iez3P1hA==", + "requires": { + "curve25519-js": "^0.0.4", + "protobufjs": "^7.5.5" + }, + "dependencies": { + "protobufjs": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + } + } + } + }, "lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -7546,6 +9725,14 @@ "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", "dev": true }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, "lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -7591,6 +9778,11 @@ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" }, + "long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7605,6 +9797,11 @@ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true }, + "lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==" + }, "magic-bytes.js": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz", @@ -7692,6 +9889,35 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "music-metadata": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.13.0.tgz", + "integrity": "sha512-uXRaov9dfjSpQufXIU7sMxVZnh+FilCQv2mXn+K5EJ/decP3dTWrgvPYa5r6MtRbieNSCE708Da4J0u1UGfQIw==", + "requires": { + "@borewit/text-codec": "^0.2.2", + "@tokenizer/token": "^0.3.0", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "file-type": "^21.3.4", + "media-typer": "^2.0.0", + "strtok3": "^10.3.5", + "token-types": "^6.1.2", + "uint8array-extras": "^1.5.0", + "win-guid": "^0.2.1" + }, + "dependencies": { + "content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==" + }, + "media-typer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-2.0.0.tgz", + "integrity": "sha512-kOy3OxT2HH39N70UnKgu4NWDZjLOz8W/mfyvniHjRH/DrL3f2pOfvWQ4p60offbbtDAnXWp0v9LfMIqMec269Q==" + } + } + }, "mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -7819,6 +10045,22 @@ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, "p-queue": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", @@ -7852,6 +10094,11 @@ "p-finally": "^1.0.0" } }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -7867,6 +10114,11 @@ "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==" }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, "path-to-regexp": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -7944,6 +10196,11 @@ "pathe": "^2.0.1" } }, + "pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" + }, "postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", @@ -7989,6 +10246,25 @@ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==" }, + "protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -8013,6 +10289,31 @@ "once": "^1.3.1" } }, + "qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "requires": { + "hookified": "^2.1.1" + }, + "dependencies": { + "hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==" + } + } + }, + "qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "requires": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + } + }, "qrcode-terminal": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", @@ -8117,6 +10418,16 @@ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -8213,9 +10524,9 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" }, "semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==" + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==" }, "send": { "version": "1.2.1", @@ -8246,11 +10557,52 @@ "send": "^1.2.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "sharp": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.1.tgz", + "integrity": "sha512-lW979AMi+ESidzMv/Lnv+F9bknzLyxLqFI05Sm433vOeRcltgxQmXpnfOOFIAlKtwXU/ksupm2srQoFCkR214g==", + "peer": true, + "requires": { + "@img/colour": "^1.1.0", + "@img/sharp-darwin-arm64": "0.35.1", + "@img/sharp-darwin-x64": "0.35.1", + "@img/sharp-freebsd-wasm32": "0.35.1", + "@img/sharp-libvips-darwin-arm64": "1.3.0", + "@img/sharp-libvips-darwin-x64": "1.3.0", + "@img/sharp-libvips-linux-arm": "1.3.0", + "@img/sharp-libvips-linux-arm64": "1.3.0", + "@img/sharp-libvips-linux-ppc64": "1.3.0", + "@img/sharp-libvips-linux-riscv64": "1.3.0", + "@img/sharp-libvips-linux-s390x": "1.3.0", + "@img/sharp-libvips-linux-x64": "1.3.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.0", + "@img/sharp-libvips-linuxmusl-x64": "1.3.0", + "@img/sharp-linux-arm": "0.35.1", + "@img/sharp-linux-arm64": "0.35.1", + "@img/sharp-linux-ppc64": "0.35.1", + "@img/sharp-linux-riscv64": "0.35.1", + "@img/sharp-linux-s390x": "0.35.1", + "@img/sharp-linux-x64": "0.35.1", + "@img/sharp-linuxmusl-arm64": "0.35.1", + "@img/sharp-linuxmusl-x64": "0.35.1", + "@img/sharp-webcontainers-wasm32": "0.35.1", + "@img/sharp-win32-arm64": "0.35.1", + "@img/sharp-win32-ia32": "0.35.1", + "@img/sharp-win32-x64": "0.35.1", + "detect-libc": "^2.1.2", + "semver": "^7.8.4" + } + }, "shell-quote": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", @@ -8453,6 +10805,14 @@ } } }, + "strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "requires": { + "@tokenizer/token": "^0.3.0" + } + }, "sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -8579,6 +10939,16 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "requires": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + } + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -8679,6 +11049,11 @@ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "dev": true }, + "uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==" + }, "undici": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", @@ -8992,6 +11367,11 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, + "whatsapp-rust-bridge": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/whatsapp-rust-bridge/-/whatsapp-rust-bridge-0.5.4.tgz", + "integrity": "sha512-yYO1qSs0Fe7tGtnxOFHomocUD6IZtoAgmA4oDFyGIRZ67D3QZk3w7swA6XXFXNQngiyrg2k7tul6IrM3eUFh7A==" + }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -9001,6 +11381,11 @@ "webidl-conversions": "^3.0.0" } }, + "which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, "why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -9019,6 +11404,11 @@ "string-width": "^7.0.0" } }, + "win-guid": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", + "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==" + }, "wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -9040,11 +11430,78 @@ "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "requires": {} }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, "yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==" }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "yoga-layout": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", diff --git a/package.json b/package.json index 6a02951b..5ae65faf 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@grammyjs/auto-retry": "^2.0.2", "@hono/node-server": "^2.0.0", "@slack/bolt": "^4.7.3", + "@whiskeysockets/baileys": "^7.0.0-rc13", "ai": "^6.0.168", "bcryptjs": "^3.0.3", "chalk": "^5.4.0", @@ -79,6 +80,8 @@ "node-cron": "^3.0.3", "ollama-ai-provider": "^1.2.0", "pino": "^10.3.1", + "protobufjs": "7.4.0", + "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", "react": "^18.3.1", "sql.js": "^1.14.1", @@ -93,6 +96,7 @@ "@types/better-sqlite3": "^7.6.13", "@types/node": "^22.13.0", "@types/node-cron": "^3.0.11", + "@types/qrcode": "^1.5.6", "@types/qrcode-terminal": "^0.12.2", "@types/react": "^18.3.28", "react-devtools-core": "^4.28.5", diff --git a/src/capabilities/filesystem/send-file.ts b/src/capabilities/filesystem/send-file.ts index f863948a..1bdf4581 100644 --- a/src/capabilities/filesystem/send-file.ts +++ b/src/capabilities/filesystem/send-file.ts @@ -11,10 +11,10 @@ export function createSendFileTool( ) { return tool({ description: - 'Send a file to the user. Use the channel parameter to send via a specific channel (signal, telegram). If omitted, sends via the current channel or the best available channel.', + 'Send a file to the user. Use the channel parameter to send via a specific channel (signal, telegram, whatsapp). If omitted, sends via the current channel or the best available channel.', inputSchema: zodSchema(z.object({ path: z.string().describe('Absolute or relative path to the file to send'), - channel: z.enum(['signal', 'telegram']).optional().describe('The channel to send the file via. Use this when the user explicitly requests a specific channel, e.g. "send via Signal" or "send to Telegram".'), + channel: z.enum(['signal', 'telegram', 'whatsapp']).optional().describe('The channel to send the file via. Use this when the user explicitly requests a specific channel, e.g. "send via Signal" or "send to Telegram" or "send to WhatsApp".'), })), execute: async ({ path, channel }) => { const resolved = isAbsolute(path) ? resolve(path) : resolve(getCwd(), path); diff --git a/src/channels/index.ts b/src/channels/index.ts index 7fff8772..d0b6b5b4 100644 --- a/src/channels/index.ts +++ b/src/channels/index.ts @@ -6,4 +6,5 @@ export { SignalChannel } from './signal.js'; export { WebChannel } from './web.js'; export { DiscordChannel } from './discord.js'; export { SlackChannel } from './slack.js'; +export { WhatsAppChannel } from './whatsapp.js'; export { ChannelRegistry } from './registry.js'; \ No newline at end of file diff --git a/src/channels/registry.ts b/src/channels/registry.ts index 674da6b4..6d54e30f 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -5,6 +5,7 @@ import { TelegramChannel } from './telegram.js'; import { SignalChannel } from './signal.js'; import { DiscordChannel } from './discord.js'; import { SlackChannel } from './slack.js'; +import { WhatsAppChannel } from './whatsapp.js'; import type { MercuryConfig } from '../utils/config.js'; import { logger } from '../utils/logger.js'; @@ -29,6 +30,10 @@ export class ChannelRegistry { if (config.channels.slack.enabled && config.channels.slack.botToken) { this.register('slack', new SlackChannel(config)); } + + if (config.channels.whatsapp.enabled && config.channels.whatsapp.phoneNumber) { + this.register('whatsapp', new WhatsAppChannel(config)); + } } getCliChannel(): CLIChannel | undefined { @@ -90,6 +95,8 @@ export class ChannelRegistry { if (discord?.isReady()) return discord; const slack = this.channels.get('slack'); if (slack?.isReady()) return slack; + const whatsapp = this.channels.get('whatsapp'); + if (whatsapp?.isReady()) return whatsapp; const cli = this.channels.get('cli'); if (cli?.isReady()) return cli; return this.channels.values().next().value; diff --git a/src/channels/whatsapp.ts b/src/channels/whatsapp.ts new file mode 100644 index 00000000..39136c06 --- /dev/null +++ b/src/channels/whatsapp.ts @@ -0,0 +1,736 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import pino from 'pino'; +import type { AnyMessageContent, WAMessage, ConnectionState, WAVersion, GroupMetadata } from '@whiskeysockets/baileys'; +import makeWASocket, { + useMultiFileAuthState, + DisconnectReason, + fetchLatestBaileysVersion, + Browsers, + makeCacheableSignalKeyStore, + isJidGroup, +} from '@whiskeysockets/baileys'; +import { Boom } from '@hapi/boom'; +import qrcode from 'qrcode-terminal'; +import type { ChannelMessage } from '../types/channel.js'; +import { BaseChannel, type PermissionMode } from './base.js'; +import type { MercuryConfig } from '../utils/config.js'; +import { loadConfig, saveConfig } from '../utils/config.js'; +import { logger } from '../utils/logger.js'; +import { WhatsAppRateLimiter } from '../whatsapp/rate-limiter.js'; +import { authDirExists, ensureAuthDir as ensureAuthDirFn, deleteAuthDir, validateAuthState } from '../whatsapp/auth.js'; + +function getAuthDir(): string { + return ensureAuthDirFn(); +} + +const MAX_MESSAGE_LENGTH = 4000; +const INTER_MESSAGE_DELAY_MS = 350; + +const AFFIRMATIVE = new Set(['yes', 'y', 'ok', 'okay', 'sure', 'allow', 'approve', 'yeah', 'yep', 'yup']); +const NEGATIVE = new Set(['no', 'n', 'nope', 'deny', 'stop', 'cancel', 'never']); +const ALWAYS_WORDS = new Set(['always', 'all', 'allow-all', 'allow all', 'forever']); + +function normalizeReply(raw: string): string { + const cleaned = raw.trim().toLowerCase().replace(/[.!]/g, ''); + if (ALWAYS_WORDS.has(cleaned)) return 'always'; + if (AFFIRMATIVE.has(cleaned)) return 'yes'; + if (NEGATIVE.has(cleaned)) return 'no'; + if (/^\d+$/.test(cleaned)) return cleaned; + if (AFFIRMATIVE.has(cleaned.split(/\s+/)[0])) return 'yes'; + return cleaned || 'no'; +} + +type PendingReply = { + resolve: (value: string) => void; + timeout: NodeJS.Timeout; +}; + +export class WhatsAppChannel extends BaseChannel { + readonly type = 'whatsapp' as const; + private sock: ReturnType | null = null; + private rateLimiter = new WhatsAppRateLimiter(); + private reconnectAttempts = 0; + private maxReconnectAttempts = 10; + private pairingComplete = false; + private qrDisplayed = false; + private pairingResolve: ((success: boolean) => void) | null = null; + private sentMessageIds = new Set(); + private pendingReplies = new Map(); + private permissionModes = new Map(); + private onPermissionMode?: (mode: PermissionMode, source: string) => void; + private configReloadInterval: NodeJS.Timeout | null = null; + + private stepCounters = new Map(); + private stepHistory = new Map(); + private taskActive = new Map(); + private deferredResponses = new Map(); + private statusNotices = new Map(); + private static readonly MAX_STATUS_NOTICES = 3; + + private chatCommandContext?: import('../capabilities/registry.js').ChatCommandContext; + + private boundConnectionUpdate: (update: Partial) => Promise; + private boundIncomingMessages: (data: { messages: WAMessage[]; type: string }) => void; + + private connecting = false; + + constructor(private config: MercuryConfig) { + super(); + this.boundConnectionUpdate = this.handleConnectionUpdate.bind(this); + this.boundIncomingMessages = this.handleIncomingMessages.bind(this); + } + + setChatCommandContext(ctx: import('../capabilities/registry.js').ChatCommandContext): void { + this.chatCommandContext = ctx; + } + + setOnPermissionMode(fn: (mode: PermissionMode, source: string) => void): void { + this.onPermissionMode = fn; + } + + beginTask(targetId?: string): void { + const key = targetId || 'default'; + this.taskActive.set(key, true); + this.stepCounters.set(key, 0); + this.stepHistory.set(key, []); + this.deferredResponses.delete(key); + this.statusNotices.delete(key); + } + + endTask(targetId?: string): void { + const key = targetId || 'default'; + this.taskActive.delete(key); + this.stepCounters.delete(key); + this.stepHistory.delete(key); + this.deferredResponses.delete(key); + this.statusNotices.delete(key); + } + + /** + * Resolve the JID to send messages to. + * In group mode: the configured WhatsApp group JID. + * In self mode: the admin's own JID (self-chat). + */ + private getTargetJid(): string | null { + const wa = this.config.channels.whatsapp; + if (wa.mode === 'group') { + return wa.groupId || null; + } + return wa.admin?.jid || null; + } + + /** + * Auto-detect a WhatsApp group named "Mercury" (or configured groupName). + * Called after successful connection. Stores groupId in config if found. + */ + async detectGroup(): Promise<{ groupId: string; groupName: string } | null> { + if (!this.sock) return null; + const wa = this.config.channels.whatsapp; + const targetName = (wa.groupName || 'Mercury').toLowerCase().trim(); + try { + const groups = await this.sock.groupFetchAllParticipating(); + for (const [gid, meta] of Object.entries(groups)) { + const name = (meta.subject || '').toLowerCase().trim(); + if (name === targetName) { + const freshConfig = loadConfig(); + freshConfig.channels.whatsapp.groupId = gid; + freshConfig.channels.whatsapp.groupName = meta.subject || 'Mercury'; + saveConfig(freshConfig); + this.config = freshConfig; + logger.info({ groupId: gid, groupName: meta.subject }, 'WhatsApp: group detected'); + return { groupId: gid, groupName: meta.subject || 'Mercury' }; + } + } + logger.info({ targetName, groupCount: Object.keys(groups).length }, 'WhatsApp: group not found'); + } catch (err) { + logger.warn({ err }, 'WhatsApp: failed to fetch groups'); + } + return null; + } + + /** + * List all groups the account participates in (for CLI display). + */ + async listGroups(): Promise<{ groupId: string; groupName: string }[]> { + if (!this.sock) return []; + try { + const groups = await this.sock.groupFetchAllParticipating(); + return Object.entries(groups).map(([gid, meta]) => ({ + groupId: gid, + groupName: meta.subject || '(unnamed)', + })); + } catch { + return []; + } + } + + private allowPairing = false; + + async start(): Promise { + if (this.ready || this.connecting) { + logger.info({ ready: this.ready, connecting: this.connecting }, 'WhatsApp: start() called but already running/connecting, skipping'); + return; + } + this.connecting = true; + const freshConfig = loadConfig(); + this.config = freshConfig; + + if (!freshConfig.channels.whatsapp.enabled) { + logger.info('WhatsApp: channel disabled, not starting'); + return; + } + + if (!freshConfig.channels.whatsapp.phoneNumber) { + logger.warn('WhatsApp: no phone number configured, not starting'); + return; + } + + // If paired but auth data is missing/invalid, mark as unpaired + if (freshConfig.channels.whatsapp.paired && !validateAuthState()) { + logger.warn('WhatsApp: config says paired but auth data missing/invalid, marking as unpaired'); + freshConfig.channels.whatsapp.paired = false; + freshConfig.channels.whatsapp.admin = null; + saveConfig(freshConfig); + this.config = freshConfig; + } + + // If not paired and not explicitly pairing, skip — don't show QR on daemon start + if (!freshConfig.channels.whatsapp.paired && !this.allowPairing) { + logger.info('WhatsApp: not paired, skipping channel start (run "mercury whatsapp pair" to pair)'); + return; + } + + // Ensure auth directory exists (needed for both fresh pairing and reconnection) + ensureAuthDirFn(); + this.qrDisplayed = false; + this.pairingComplete = false; + await this.connectToWhatsApp(); + this.startConfigReload(); + this.connecting = false; + logger.info('WhatsApp: channel started'); + } + + /** + * Start in pairing mode — will show QR code even if not paired. + * Used by `mercury whatsapp pair` and the onboarding flow. + */ + async startForPairing(): Promise { + this.allowPairing = true; + await this.start(); + } + + private async connectToWhatsApp(): Promise { + try { + // Clean up old socket before creating a new one + if (this.sock) { + try { + this.sock.ev.off('connection.update', this.boundConnectionUpdate); + this.sock.ev.off('messages.upsert', this.boundIncomingMessages); + await this.sock.end(new Error('Reconnecting')); + } catch { + // ignore cleanup errors + } + this.sock = null; + } + + const { state, saveCreds } = await useMultiFileAuthState(getAuthDir()); + let version: WAVersion | undefined; + try { + const versionResult = await fetchLatestBaileysVersion(); + version = versionResult.version; + logger.info({ version: version.join('.') }, 'WhatsApp: fetched latest version'); + } catch (err) { + logger.warn({ err }, 'WhatsApp: could not fetch latest version, using default'); + } + + const baileysLogger = pino({ level: 'fatal' }, pino.destination(1)); + + this.sock = makeWASocket({ + version, + auth: { + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, baileysLogger), + }, + browser: Browsers.macOS('Desktop'), + connectTimeoutMs: 60_000, + keepAliveIntervalMs: 25_000, + qrTimeout: 60_000, + markOnlineOnConnect: true, + syncFullHistory: true, + printQRInTerminal: false, + getMessage: async () => undefined, + logger: baileysLogger, + }); + + this.sock.ev.on('connection.update', this.boundConnectionUpdate); + this.sock.ev.on('creds.update', saveCreds); + this.sock.ev.on('messages.upsert', this.boundIncomingMessages); + } catch (err) { + logger.error({ err }, 'WhatsApp: failed to connect'); + this.ready = false; + } + } + + private async handleConnectionUpdate(update: Partial): Promise { + const { connection, lastDisconnect, qr } = update; + + if (qr && !this.pairingComplete && !this.qrDisplayed) { + this.qrDisplayed = true; + logger.info('WhatsApp: QR code received, displaying...'); + qrcode.generate(qr, { small: true }); + console.log('\n Scan this QR code with WhatsApp > Linked Devices > Link a device\n'); + } + + if (connection === 'open') { + logger.info('WhatsApp: connected successfully'); + this.pairingComplete = true; + this.qrDisplayed = false; + this.reconnectAttempts = 0; + this.ready = true; + + // Mark as paired (device linked) — admin is set via /pair command in the group + if (this.sock?.user) { + const freshConfig = loadConfig(); + freshConfig.channels.whatsapp.paired = true; + freshConfig.channels.whatsapp.registered = true; + saveConfig(freshConfig); + this.config = freshConfig; + logger.info({ jid: this.sock.user.id }, 'WhatsApp: device linked'); + } + + // Auto-detect group in group mode (delayed to allow group metadata sync) + if (this.config.channels.whatsapp.mode === 'group' && !this.config.channels.whatsapp.groupId) { + setTimeout(() => this.detectGroup().then((detected) => { + if (detected) { + logger.info({ groupId: detected.groupId, groupName: detected.groupName }, 'WhatsApp: group auto-detected'); + } else { + // Retry once more after a longer delay (group sync can be slow) + setTimeout(() => this.detectGroup().then((d) => { + if (d) { + logger.info({ groupId: d.groupId, groupName: d.groupName }, 'WhatsApp: group auto-detected (retry)'); + } else { + logger.info('WhatsApp: no group named "Mercury" found. Create one and restart Mercury.'); + } + }), 10000); + } + }), 5000); + } + + if (this.pairingResolve) { + this.pairingResolve(true); + this.pairingResolve = null; + } + } + + if (connection === 'close') { + this.ready = false; + const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode; + + if (statusCode === DisconnectReason.loggedOut || statusCode === DisconnectReason.forbidden) { + logger.error('WhatsApp: permanent disconnect (logged out or forbidden), not reconnecting'); + const freshConfig = loadConfig(); + freshConfig.channels.whatsapp.paired = false; + freshConfig.channels.whatsapp.admin = null; + saveConfig(freshConfig); + this.config = freshConfig; + + if (this.pairingResolve) { + this.pairingResolve(false); + this.pairingResolve = null; + } + return; + } + + if (statusCode === DisconnectReason.restartRequired) { + logger.info('WhatsApp: restart required (515), reconnecting...'); + this.reconnectAttempts = 0; + setTimeout(() => this.connectToWhatsApp(), 1000); + return; + } + + const shouldReconnect = statusCode !== DisconnectReason.loggedOut + && statusCode !== DisconnectReason.forbidden; + + if (shouldReconnect && this.reconnectAttempts < this.maxReconnectAttempts) { + this.reconnectAttempts++; + const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30_000) + Math.random() * 5000; + logger.info({ attempt: this.reconnectAttempts, delay: Math.round(delay), statusCode }, 'WhatsApp: reconnecting...'); + setTimeout(() => this.connectToWhatsApp(), delay); + } else if (this.reconnectAttempts >= this.maxReconnectAttempts) { + logger.error('WhatsApp: max reconnect attempts reached, giving up'); + if (this.pairingResolve) { + this.pairingResolve(false); + this.pairingResolve = null; + } + } + } + } + + private async handleIncomingMessages({ messages, type }: { messages: WAMessage[]; type: string }): Promise { + if (type !== 'notify' && type !== 'append') return; + + const wa = this.config.channels.whatsapp; + const targetJid = this.getTargetJid(); + if (!targetJid) return; + + for (const msg of messages) { + const remoteJid = msg.key.remoteJid || ''; + const msgId = msg.key.id || ''; + + // Skip messages we sent (Mercury's own replies) + if (this.sentMessageIds.has(msgId)) continue; + + if (wa.mode === 'group') { + // Group mode: only accept messages from the configured group + if (remoteJid !== targetJid) continue; + } else { + // Self mode: accept fromMe messages (not groups, not broadcast) + if (isJidGroup(remoteJid)) continue; + if (remoteJid === 'status@broadcast') continue; + if (!msg.key.fromMe) continue; + } + + const text = this.extractText(msg); + if (!text) continue; + + // Handle /pair command — record sender as admin + const trimmed = text.trim().toLowerCase(); + if (trimmed === '/pair' || trimmed === '/start') { + await this.handlePairCommand(msg, targetJid); + continue; + } + + // If admin hasn't paired yet, tell user to pair + if (!wa.adminPaired || !wa.admin) { + await this.send('⚠️ Not paired yet. Send /pair to register as admin.', targetJid); + continue; + } + + const channelMsg: ChannelMessage = { + id: msg.key.id || String(Date.now()), + channelId: `whatsapp:${targetJid}`, + channelType: 'whatsapp', + senderId: targetJid, + senderName: this.config.channels.whatsapp.admin?.name || 'You', + content: text, + timestamp: typeof msg.messageTimestamp === 'number' ? msg.messageTimestamp : Date.now() / 1000, + metadata: { key: msg.key }, + }; + + this.emit(channelMsg); + } + } + + private async handlePairCommand(msg: WAMessage, targetJid: string): Promise { + // Get the sender's JID (the participant in the group) + const senderJid = msg.key.participant || msg.key.remoteJid || ''; + if (!senderJid) { + await this.send('❌ Could not determine your WhatsApp ID. Please try again.', targetJid); + return; + } + + // Get sender name from push name if available + const senderName = (msg.pushName || undefined) as string | undefined; + + const freshConfig = loadConfig(); + const phoneNumber = freshConfig.channels.whatsapp.phoneNumber; + freshConfig.channels.whatsapp.admin = { + jid: senderJid, + phoneNumber, + name: senderName, + pairedAt: new Date().toISOString(), + }; + freshConfig.channels.whatsapp.paired = true; + freshConfig.channels.whatsapp.registered = true; + freshConfig.channels.whatsapp.adminPaired = true; + saveConfig(freshConfig); + freshConfig.channels.whatsapp.registered = true; + saveConfig(freshConfig); + this.config = freshConfig; + + logger.info({ senderJid, senderName }, 'WhatsApp: admin paired via /pair command'); + await this.send('✅ You are now the admin. Mercury is ready — send any message to chat.', targetJid); + } + + private extractText(msg: WAMessage): string { + if (!msg.message) return ''; + const m = msg.message; + return m.conversation + || m.extendedTextMessage?.text + || m.imageMessage?.caption + || m.videoMessage?.caption + || m.documentMessage?.caption + || m.buttonsResponseMessage?.selectedDisplayText + || m.templateButtonReplyMessage?.selectedDisplayText + || m.listResponseMessage?.title + || ''; + } + + async waitForPairing(timeoutMs = 180_000): Promise { + if (this.pairingComplete) return true; + return new Promise((resolve) => { + this.pairingResolve = resolve; + setTimeout(() => { + if (this.pairingResolve === resolve) { + this.pairingResolve = null; + resolve(false); + } + }, timeoutMs); + }); + } + + /** + * Wait for the user to send /pair in the WhatsApp group. + * Polls config every 3 seconds for adminPaired to become true. + * Returns true if admin paired, false if timed out. + */ + async waitForAdminPairing(timeoutMs = 180_000): Promise { + const wa = this.config.channels.whatsapp; + if (wa.adminPaired && wa.admin) return true; + + const interval = 3000; + const maxChecks = Math.floor(timeoutMs / interval); + + for (let i = 0; i < maxChecks; i++) { + await this.sleep(interval); + const freshConfig = loadConfig(); + if (freshConfig.channels.whatsapp.adminPaired && freshConfig.channels.whatsapp.admin) { + this.config = freshConfig; + return true; + } + } + return false; + } + + async stop(): Promise { + this.configReloadInterval && clearInterval(this.configReloadInterval); + this.rateLimiter.reset(); + if (this.sock) { + try { + await this.sock.end(new Error('Graceful shutdown')); + } catch { + // ignore + } + this.sock = null; + } + this.ready = false; + logger.info('WhatsApp: channel stopped'); + } + + async send(content: string, targetId?: string, elapsedMs?: number): Promise { + const targetJid = this.getTargetJid(); + if (!targetJid) { + logger.warn('WhatsApp: no target JID, cannot send'); + return; + } + + const jid: string = targetId?.replace('whatsapp:', '') || targetJid; + const chunks = this.splitMessage(content); + + for (const chunk of chunks) { + const check = this.rateLimiter.checkRateLimit(jid); + if (!check.allowed) { + logger.warn({ jid, reason: check.reason }, 'WhatsApp: rate limited'); + continue; + } + + await this.rateLimiter.enqueue(async () => { + const sent = await this.sock!.sendMessage(jid, { text: chunk }); + const msgId = sent?.key?.id; + if (msgId) { + this.sentMessageIds.add(msgId); + setTimeout(() => this.sentMessageIds.delete(msgId), 300_000); + } + this.rateLimiter.recordSend(jid); + }); + + if (chunks.length > 1) { + await this.sleep(INTER_MESSAGE_DELAY_MS); + } + } + } + + async sendFile(filePath: string, targetId?: string): Promise { + const targetJid = this.getTargetJid(); + if (!targetJid) { + logger.warn('WhatsApp: no target JID, cannot send file'); + return; + } + + const jid: string = targetId?.replace('whatsapp:', '') || targetJid; + if (!fs.existsSync(filePath)) { + logger.error({ filePath }, 'WhatsApp: file not found'); + return; + } + + const ext = path.extname(filePath).toLowerCase(); + const check = this.rateLimiter.checkRateLimit(jid); + if (!check.allowed) { + logger.warn({ jid, reason: check.reason }, 'WhatsApp: rate limited, cannot send file'); + await this.send(`⚠️ Rate limited, could not send file: ${path.basename(filePath)}`, targetId); + return; + } + + await this.rateLimiter.enqueue(async () => { + let message: AnyMessageContent; + const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']; + const videoExts = ['.mp4', '.mov', '.avi', '.mkv', '.webm']; + const audioExts = ['.mp3', '.ogg', '.wav', '.m4a', '.flac']; + + if (imageExts.includes(ext)) { + message = { image: { url: filePath }, caption: path.basename(filePath) }; + } else if (videoExts.includes(ext)) { + message = { video: { url: filePath }, caption: path.basename(filePath) }; + } else if (audioExts.includes(ext)) { + message = { audio: { url: filePath }, mimetype: 'audio/ogg' }; + } else { + message = { document: { url: filePath }, fileName: path.basename(filePath), mimetype: 'application/octet-stream' }; + } + + const sent = await this.sock!.sendMessage(jid, message); + const msgId = sent?.key?.id; + if (msgId) { + this.sentMessageIds.add(msgId); + setTimeout(() => this.sentMessageIds.delete(msgId), 300_000); + } + this.rateLimiter.recordSend(jid); + }); + } + + async stream(content: AsyncIterable, targetId?: string): Promise { + const chunks: string[] = []; + let currentMessageId: string | null = null; + const targetJid = this.getTargetJid(); + if (!targetJid) return ''; + const jid: string = targetId?.replace('whatsapp:', '') || targetJid; + + let buffer = ''; + let lastEditTime = 0; + const EDIT_INTERVAL_MS = 2500; + const MAX_EDIT_LENGTH = 4096; + + for await (const token of content) { + buffer += token; + chunks.push(token); + const now = Date.now(); + + if (!currentMessageId && buffer.length > 20) { + const check = this.rateLimiter.checkRateLimit(jid); + if (check.allowed) { + await this.rateLimiter.enqueue(async () => { + const sent = await this.sock!.sendMessage(jid, { text: buffer.slice(0, MAX_EDIT_LENGTH) }); + const msgId = sent?.key?.id; + if (msgId) { + currentMessageId = msgId; + this.sentMessageIds.add(msgId); + setTimeout(() => this.sentMessageIds.delete(msgId), 300_000); + } + this.rateLimiter.recordSend(jid); + }); + } + lastEditTime = now; + } else if (currentMessageId && now - lastEditTime > EDIT_INTERVAL_MS && buffer.length <= MAX_EDIT_LENGTH) { + await this.sock!.sendMessage(jid, { text: buffer, edit: currentMessageId }); + lastEditTime = now; + } + } + + if (currentMessageId && buffer.length <= MAX_EDIT_LENGTH) { + await this.sock!.sendMessage(jid, { text: buffer, edit: currentMessageId }); + } else if (!currentMessageId) { + await this.send(buffer, targetId); + } else { + const remainder = buffer.slice(MAX_EDIT_LENGTH); + if (remainder) await this.send(remainder, targetId); + } + + return chunks.join(''); + } + + async typing(targetId?: string): Promise { + const targetJid = this.getTargetJid(); + if (!targetJid || !this.sock) return; + const jid = targetId?.replace('whatsapp:', '') || targetJid; + try { + await this.sock.sendPresenceUpdate('composing', jid); + } catch { + // ignore presence errors + } + } + + async askToContinue(question: string, targetId?: string): Promise { + const targetJid = this.getTargetJid(); + if (!targetJid) return false; + const jid = targetId?.replace('whatsapp:', '') || targetJid; + + await this.send(`❓ ${question}\n_Reply **yes** to continue or **no** to stop._`, targetId); + + return new Promise((resolve) => { + const timeout = setTimeout(() => resolve(false), 120_000); + this.pendingReplies.set(jid, { + resolve: (reply: string) => { + clearTimeout(timeout); + this.pendingReplies.delete(jid); + const normalized = normalizeReply(reply); + resolve(normalized === 'yes' || normalized === 'always'); + }, + timeout, + }); + }); + } + + async askPermissionMode(): Promise { + const targetJid = this.getTargetJid(); + if (!targetJid) return 'ask-me'; + return this.permissionModes.get(targetJid) || 'ask-me'; + } + + private splitMessage(text: string): string[] { + if (text.length <= MAX_MESSAGE_LENGTH) return [text]; + const chunks: string[] = []; + let remaining = text; + while (remaining.length > 0) { + if (remaining.length <= MAX_MESSAGE_LENGTH) { + chunks.push(remaining); + break; + } + let splitAt = remaining.lastIndexOf('\n', MAX_MESSAGE_LENGTH); + if (splitAt < MAX_MESSAGE_LENGTH * 0.5) splitAt = remaining.lastIndexOf(' ', MAX_MESSAGE_LENGTH); + if (splitAt < MAX_MESSAGE_LENGTH * 0.5) splitAt = MAX_MESSAGE_LENGTH; + chunks.push(remaining.slice(0, splitAt)); + remaining = remaining.slice(splitAt).trimStart(); + } + return chunks; + } + + private startConfigReload(): void { + this.configReloadInterval = setInterval(() => { + try { + this.config = loadConfig(); + } catch { + // ignore reload errors + } + }, 30_000); + } + + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + getStatus(): { enabled: boolean; phoneNumber: string; paired: boolean; connected: boolean; admin: string | null; mode: string; groupId: string | null; groupName: string | null; rateLimit: { hourlyGlobal: number; dailyGlobal: number } } { + const stats = this.rateLimiter.getStats(); + const wa = this.config.channels.whatsapp; + return { + enabled: wa.enabled, + phoneNumber: wa.phoneNumber, + paired: wa.paired, + connected: this.ready, + admin: wa.admin?.phoneNumber || null, + mode: wa.mode, + groupId: wa.groupId || null, + groupName: wa.groupName || null, + rateLimit: { hourlyGlobal: stats.hourlyGlobal, dailyGlobal: stats.dailyGlobal }, + }; + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index fc7d63f3..baff4284 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,11 +42,18 @@ import { rejectSlackPendingRequest, removeSlackUser, clearSlackAccess, + getWhatsAppAdmin, + hasWhatsAppAdmin, + getWhatsAppAccessSummary, + setWhatsAppAdmin, + clearWhatsAppAccess, + isWhatsAppPaired, } from './utils/config.js'; import type { MercuryConfig } from './utils/config.js'; import type { ProviderName } from './utils/config.js'; import { logger } from './utils/logger.js'; import { redactPhone } from './utils/redact.js'; +import { authDirExists, ensureAuthDir, deleteAuthDir, validateAuthState } from './whatsapp/auth.js'; import { Identity } from './soul/identity.js'; import { ShortTermMemory, LongTermMemory, EpisodicMemory, migrateLegacyMemory } from './memory/store.js'; import { UserMemoryStore } from './memory/user-memory.js'; @@ -62,6 +69,7 @@ import { CLIChannel } from './channels/cli.js'; import { TelegramChannel } from './channels/telegram.js'; import { SignalChannel } from './channels/signal.js'; import { DiscordChannel } from './channels/discord.js'; +import { WhatsAppChannel } from './channels/whatsapp.js'; import { WebChannel } from './channels/web.js'; import { TokenBudget } from './utils/tokens.js'; import { CapabilityRegistry } from './capabilities/registry.js'; @@ -1050,6 +1058,133 @@ async function completeInitialDiscordPairing(config: MercuryConfig): Promise { + if (!config.channels.whatsapp.enabled || !config.channels.whatsapp.phoneNumber) { + return; + } + + // If device linked, group detected, and admin paired — fully set up + if (config.channels.whatsapp.paired && validateAuthState() && config.channels.whatsapp.groupId && config.channels.whatsapp.adminPaired) { + console.log(chalk.green(' ✓ WhatsApp fully set up.')); + console.log(''); + return; + } + + console.log(''); + console.log(chalk.bold.white(' WhatsApp Linking')); + console.log(chalk.dim(' A QR code will appear below. Scan it with WhatsApp:')); + console.log(chalk.dim(' Phone > Settings > Linked Devices > Link a device')); + console.log(chalk.dim(' Scan the QR code to link Mercury as a companion device.')); + console.log(''); + + const waChannel = new WhatsAppChannel(config); + + try { + await waChannel.startForPairing(); + } catch (err: any) { + console.log(chalk.red(`\n ✗ Failed to start WhatsApp: ${err.message || err}`)); + console.log(chalk.dim(' Run "mercury whatsapp pair" later to try again.')); + console.log(''); + return; + } + + const success = await waChannel.waitForPairing(180_000); + + if (success) { + const freshConfig = loadConfig(); + console.log(''); + console.log(chalk.green(` ✓ WhatsApp linked! Phone: ${freshConfig.channels.whatsapp.admin?.phoneNumber || freshConfig.channels.whatsapp.phoneNumber}`)); + await runGroupDetectionFlow(waChannel); + } else { + console.log(''); + console.log(chalk.yellow(' ⏱ WhatsApp pairing timed out or failed.')); + console.log(chalk.dim(' Run "mercury whatsapp pair" to try again.')); + console.log(''); + } + + await waChannel.stop(); +} + +/** + * Shared group detection flow used after pairing succeeds. + * 1. Show group instructions + * 2. Wait up to 20 seconds for auto-detection (checking every 5s) + * 3. If detected → done + * 4. If not detected → ask user to create group and rescan (Y/n) + * 5. If Y → rescan; if n → exit + */ +async function runGroupDetectionFlow(waChannel: WhatsAppChannel): Promise { + console.log(''); + console.log(chalk.bold.white(' WhatsApp Group Setup')); + console.log(chalk.dim(' Mercury listens for messages in a WhatsApp group named "Mercury".')); + console.log(chalk.dim(' Create a group on your phone:')); + console.log(chalk.dim(' 1. Open WhatsApp → New Group')); + console.log(chalk.dim(' 2. Name it "Mercury" (case-insensitive)')); + console.log(chalk.dim(' 3. Add any contact, then remove them (or keep them)')); + console.log(chalk.dim(' Looking for the group...')); + console.log(''); + + // Try detecting the group for up to 20 seconds (4 attempts, 5s apart) + for (let attempt = 1; attempt <= 4; attempt++) { + await new Promise(resolve => setTimeout(resolve, 5000)); + const detected = await waChannel.detectGroup(); + if (detected) { + console.log(chalk.green(` ✓ Group detected: ${detected.groupName}`)); + await runAdminPairingFlow(waChannel); + return; + } + } + + // Not found — ask user to create and rescan + while (true) { + console.log(chalk.yellow(' ✗ No group named "Mercury" found.')); + const answer = await ask(chalk.white(' Create a WhatsApp group named "Mercury" and rescan? (Y/n): ')); + const choice = answer.trim().toLowerCase(); + if (choice === 'n' || choice === 'no') { + console.log(chalk.dim(' You can run "mercury whatsapp detect-group" later after creating the group.')); + console.log(''); + return; + } + + // Rescan — wait 20 seconds again + console.log(chalk.dim(' Rescanning for groups...')); + for (let attempt = 1; attempt <= 4; attempt++) { + await new Promise(resolve => setTimeout(resolve, 5000)); + const detected = await waChannel.detectGroup(); + if (detected) { + console.log(chalk.green(` ✓ Group detected: ${detected.groupName}`)); + await runAdminPairingFlow(waChannel); + return; + } + } + } +} + +/** + * Wait for the user to send /pair in the WhatsApp group. + * Shows instructions, then polls for adminPaired for up to 3 minutes. + */ +async function runAdminPairingFlow(waChannel: WhatsAppChannel): Promise { + console.log(''); + console.log(chalk.bold.white(' Final step — Pair as admin')); + console.log(chalk.dim(' Open the "Mercury" group on your phone and send:')); + console.log(chalk.cyan(' /pair')); + console.log(chalk.dim(' Waiting for /pair...')); + console.log(''); + + const adminPaired = await waChannel.waitForAdminPairing(180_000); + if (adminPaired) { + const config = loadConfig(); + console.log(chalk.green(` ✓ Admin paired: ${config.channels.whatsapp.admin?.phoneNumber || 'OK'}`)); + console.log(chalk.dim(' Mercury is ready. Send any message in the group to chat.')); + console.log(''); + } else { + console.log(chalk.yellow(' ⏱ Timed out waiting for /pair.')); + console.log(chalk.dim(' Send /pair in the "Mercury" group later to register as admin.')); + console.log(''); + } +} + async function configure(existingConfig?: MercuryConfig): Promise { const isReconfig = !!existingConfig; const config = existingConfig ?? loadConfig(); @@ -1746,6 +1881,114 @@ async function configure(existingConfig?: MercuryConfig): Promise { saveConfig(config); } + // ── WhatsApp ───────────────────────────────────────────────────── + hr(); + console.log(''); + console.log(chalk.bold.white(' WhatsApp (optional)')); + if (isReconfig && config.channels.whatsapp.phoneNumber) { + console.log(chalk.dim(' Leave empty to keep current. Enter "none" to disable.')); + console.log(chalk.dim(' Enter "reset" to clear session and re-pair with a QR code.')); + console.log(chalk.dim(' Enter "unregister" to unlink device from WhatsApp and delete all data.')); + } else { + console.log(chalk.dim(' Leave empty to skip. You can add it later with mercury doctor.')); + console.log(chalk.dim(' WhatsApp lets you chat with Mercury through a WhatsApp group.')); + console.log(chalk.dim(' You scan a QR code to link Mercury as a companion device.')); + console.log(chalk.dim(' Then create a WhatsApp group named "Mercury" (just yourself).')); + console.log(chalk.dim(' Mercury will auto-detect the group and listen for messages there.')); + } + console.log(''); + + if (isReconfig && config.channels.whatsapp.phoneNumber) { + const waPhoneMask = ` [${redactPhone(config.channels.whatsapp.phoneNumber)}]`; + const waPhone = await ask(chalk.white(` WhatsApp Phone Number${waPhoneMask}: `)); + if (waPhone.toLowerCase() === 'none') { + config.channels.whatsapp.enabled = false; + config.channels.whatsapp.phoneNumber = ''; + config.channels.whatsapp.registered = false; + config.channels.whatsapp.paired = false; + config.channels.whatsapp.adminPaired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + deleteAuthDir(); + saveConfig(config); + } else if (waPhone.toLowerCase() === 'reset') { + deleteAuthDir(); + config.channels.whatsapp.paired = false; + config.channels.whatsapp.adminPaired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + config.channels.whatsapp.enabled = true; + saveConfig(config); + console.log(''); + console.log(chalk.yellow(' WhatsApp session cleared. Starting QR pairing...')); + console.log(''); + await completeInitialWhatsAppPairing(config); + } else if (waPhone.toLowerCase() === 'unregister') { + deleteAuthDir(); + config.channels.whatsapp.enabled = false; + config.channels.whatsapp.phoneNumber = ''; + config.channels.whatsapp.registered = false; + config.channels.whatsapp.paired = false; + config.channels.whatsapp.adminPaired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + saveConfig(config); + console.log(''); + console.log(chalk.green(' WhatsApp unregistered. Device removed and data deleted.')); + console.log(''); + } else if (waPhone) { + config.channels.whatsapp.phoneNumber = waPhone; + config.channels.whatsapp.enabled = true; + config.channels.whatsapp.registered = true; + if (!config.channels.whatsapp.paired) { + config.channels.whatsapp.paired = false; + config.channels.whatsapp.adminPaired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + deleteAuthDir(); + } + saveConfig(config); + if (!config.channels.whatsapp.paired || !config.channels.whatsapp.adminPaired) { + console.log(''); + console.log(chalk.yellow(' WhatsApp setup incomplete. Starting pairing flow...')); + console.log(''); + await completeInitialWhatsAppPairing(config); + } + } else { + // User pressed Enter to keep existing number — check if setup is incomplete + const wa = config.channels.whatsapp; + if (wa.registered && (!wa.paired || !wa.adminPaired || !wa.groupId)) { + console.log(''); + if (!wa.paired) { + console.log(chalk.yellow(' WhatsApp device not linked. Starting QR pairing...')); + } else if (!wa.groupId) { + console.log(chalk.yellow(' WhatsApp group not detected. Starting group detection...')); + } else if (!wa.adminPaired) { + console.log(chalk.yellow(' WhatsApp admin not paired. Waiting for /pair in the group...')); + } + console.log(''); + await completeInitialWhatsAppPairing(config); + } + } + } else { + const waPhone = await ask(chalk.white(' WhatsApp Phone Number (with country code, e.g., +1234567890): ')); + if (waPhone) { + config.channels.whatsapp.phoneNumber = waPhone; + config.channels.whatsapp.enabled = true; + config.channels.whatsapp.registered = true; + config.channels.whatsapp.paired = false; + saveConfig(config); + console.log(''); + console.log(chalk.yellow(' Starting WhatsApp linking...')); + console.log(chalk.dim(' Scan the QR code below with WhatsApp > Linked Devices > Link a device')); + console.log(''); + await completeInitialWhatsAppPairing(config); + } else { + config.channels.whatsapp.enabled = false; + saveConfig(config); + } + } + hr(); console.log(''); console.log(chalk.bold.white(' GitHub Integration (optional)')); @@ -2132,6 +2375,7 @@ async function runAgent(isDaemon: boolean = false): Promise { const { channelId, channelType } = capabilities.getChannelContext(); const telegram = channels.get('telegram'); const signal = channels.get('signal'); + const whatsapp = channels.get('whatsapp'); // Explicit channel override from the user if (channel === 'signal' && signal) { @@ -2142,6 +2386,10 @@ async function runAgent(isDaemon: boolean = false): Promise { await telegram.sendFile(filePath, channelType === 'telegram' ? channelId : undefined); return; } + if (channel === 'whatsapp' && whatsapp) { + await whatsapp.sendFile(filePath, channelType === 'whatsapp' ? channelId : undefined); + return; + } if (channelType === 'telegram' && telegram) { await telegram.sendFile(filePath, channelId); @@ -2153,6 +2401,11 @@ async function runAgent(isDaemon: boolean = false): Promise { return; } + if (channelType === 'whatsapp' && whatsapp) { + await whatsapp.sendFile(filePath, channelId); + return; + } + if (config.channels.telegram.enabled && telegram && getTelegramApprovedUsers(config).length > 0) { await telegram.sendFile(filePath); return; @@ -2163,6 +2416,11 @@ async function runAgent(isDaemon: boolean = false): Promise { return; } + if (config.channels.whatsapp.enabled && whatsapp && hasWhatsAppAdmin(config)) { + await whatsapp.sendFile(filePath); + return; + } + const cli = channels.get('cli'); if (cli) { await cli.sendFile(filePath); @@ -2269,6 +2527,7 @@ async function runAgent(isDaemon: boolean = false): Promise { const cliChannel = channels.get('cli') as CLIChannel | undefined; const tgChannel = channels.get('telegram') as TelegramChannel | undefined; const sigChannel = channels.get('signal') as SignalChannel | undefined; + const waChannel = channels.get('whatsapp') as WhatsAppChannel | undefined; if (tgChannel) { tgChannel.setChatCommandContext(capabilities.getChatCommandContext()!); @@ -2278,6 +2537,10 @@ async function runAgent(isDaemon: boolean = false): Promise { sigChannel.setChatCommandContext(capabilities.getChatCommandContext()!); } + if (waChannel) { + waChannel.setChatCommandContext(capabilities.getChatCommandContext()!); + } + setWebWebChannel(webChannel); setWebProgrammingMode(agent.programmingMode); setWebBgTasks(agent.backgroundTasks); @@ -2747,6 +3010,10 @@ program if (config.channels.slack.botToken) { console.log(` Slack Access: ${chalk.white(getSlackAccessSummary(config))}`); } + console.log(` WhatsApp: ${config.channels.whatsapp.enabled ? chalk.green('enabled') : chalk.dim('disabled')}`); + if (config.channels.whatsapp.phoneNumber) { + console.log(` WhatsApp Access: ${chalk.white(getWhatsAppAccessSummary(config))}`); + } console.log(` Web: ${config.web.enabled ? chalk.green(`enabled (http://localhost:${config.web.port})`) : chalk.dim('disabled')}`); console.log(` Skills: ${skills.length > 0 ? chalk.green(skills.map(s => s.name).join(', ')) : chalk.dim('none')}`); console.log(` Budget: ${chalk.white(config.tokens.dailyBudget.toLocaleString())} tokens/day`); @@ -3495,6 +3762,225 @@ slackCmd console.log(''); }); +// ── WhatsApp commands ─────────────────────────────────────────────── + +const whatsappCmd = program + .command('whatsapp') + .description('Manage WhatsApp channel setup and status'); + +whatsappCmd + .command('status') + .description('Show WhatsApp configuration and connection status') + .action(() => { + const config = loadConfig(); + console.log(` Enabled: ${config.channels.whatsapp.enabled ? chalk.green('yes') : chalk.dim('no')}`); + console.log(` Phone: ${config.channels.whatsapp.phoneNumber || chalk.dim('not set')}`); + console.log(` Registered: ${config.channels.whatsapp.registered ? chalk.green('yes') : chalk.dim('no')}`); + console.log(` Paired: ${config.channels.whatsapp.paired ? chalk.green('yes') : chalk.dim('no')}`); + console.log(` Admin Paired: ${config.channels.whatsapp.adminPaired ? chalk.green('yes') : chalk.dim('no (send /pair in the group)')}`); + console.log(` Mode: ${chalk.white(config.channels.whatsapp.mode || 'group')}`); + if (config.channels.whatsapp.groupId) { + console.log(` Group: ${chalk.white(config.channels.whatsapp.groupName || 'Mercury')}`); + console.log(` Group ID: ${chalk.dim(config.channels.whatsapp.groupId)}`); + } else if ((config.channels.whatsapp.mode || 'group') === 'group') { + console.log(` Group: ${chalk.yellow('not detected — run "mercury whatsapp detect-group"')}`); + } + if (config.channels.whatsapp.admin) { + console.log(` Admin: ${chalk.white(config.channels.whatsapp.admin.phoneNumber)}`); + console.log(` Paired at: ${chalk.dim(config.channels.whatsapp.admin.pairedAt)}`); + } + console.log(` Auth data: ${authDirExists() ? chalk.green('present') : chalk.dim('missing')}`); + console.log(''); + }); + +whatsappCmd + .command('pair') + .description('Start WhatsApp QR pairing flow') + .action(async () => { + const config = loadConfig(); + if (!config.channels.whatsapp.phoneNumber) { + console.log(chalk.red(' WhatsApp phone number not set. Run mercury doctor first.')); + process.exit(1); + } + + deleteAuthDir(); + config.channels.whatsapp.paired = false; + config.channels.whatsapp.admin = null; + saveConfig(config); + + console.log(''); + console.log(chalk.bold.white(' WhatsApp Pairing')); + console.log(chalk.dim(' A QR code will appear below. Scan it with WhatsApp:')); + console.log(chalk.dim(' Phone > Settings > Linked Devices > Link a device')); + console.log(''); + + const waChannel = new WhatsAppChannel(config); + try { + await waChannel.startForPairing(); + } catch (err: any) { + console.log(chalk.red(` ✗ Failed to start WhatsApp: ${err.message || err}`)); + process.exit(1); + } + + const success = await waChannel.waitForPairing(180_000); + if (success) { + const freshConfig = loadConfig(); + console.log(''); + console.log(chalk.green(` ✓ WhatsApp linked! Phone: ${freshConfig.channels.whatsapp.admin?.phoneNumber || freshConfig.channels.whatsapp.phoneNumber}`)); + await runGroupDetectionFlow(waChannel); + } else { + console.log(''); + console.log(chalk.yellow(' ⏱ WhatsApp pairing timed out or failed. Try again.')); + } + await waChannel.stop(); + }); + +whatsappCmd + .command('reset') + .description('Clear WhatsApp session data and disable channel') + .action(() => { + const config = loadConfig(); + deleteAuthDir(); + config.channels.whatsapp.paired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.registered = false; + config.channels.whatsapp.enabled = false; + config.channels.whatsapp.groupId = ''; + saveConfig(config); + console.log(chalk.yellow(' WhatsApp session cleared. Channel disabled.')); + console.log(chalk.dim(' Run "mercury whatsapp pair" to re-link, or "mercury doctor" to reconfigure.')); + }); + +whatsappCmd + .command('unregister') + .description('Unlink device from WhatsApp and delete all session data') + .action(async () => { + const config = loadConfig(); + console.log(chalk.yellow(' This will unlink Mercury from your WhatsApp and delete all session data.')); + console.log(chalk.dim(' You will need to re-scan a QR code to use WhatsApp again.')); + const answer = await ask(chalk.white(' Continue? (y/N): ')); + if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') { + console.log(chalk.dim(' Cancelled.')); + return; + } + deleteAuthDir(); + config.channels.whatsapp.enabled = false; + config.channels.whatsapp.phoneNumber = ''; + config.channels.whatsapp.registered = false; + config.channels.whatsapp.paired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + saveConfig(config); + console.log(chalk.green(' WhatsApp unregistered. Device removed and data deleted.')); + }); + +whatsappCmd + .command('list') + .description('Show WhatsApp admin info') + .action(() => { + const config = loadConfig(); + if (!config.channels.whatsapp.admin) { + console.log(chalk.dim(' No WhatsApp admin paired yet.')); + } else { + const admin = config.channels.whatsapp.admin; + console.log(` Admin Phone: ${chalk.white(admin.phoneNumber)}`); + console.log(` Admin JID: ${chalk.dim(admin.jid)}`); + console.log(` Paired at: ${chalk.dim(admin.pairedAt)}`); + } + console.log(''); + }); + +whatsappCmd + .command('detect-group') + .description('Auto-detect a WhatsApp group named "Mercury" and configure it') + .action(async () => { + const config = loadConfig(); + if (!config.channels.whatsapp.paired || !authDirExists()) { + console.log(chalk.red(' WhatsApp is not paired. Run "mercury whatsapp pair" first.')); + process.exit(1); + } + + console.log(''); + console.log(chalk.bold.white(' Detecting WhatsApp group...')); + console.log(chalk.dim(' Make sure you have created a WhatsApp group named "Mercury".')); + console.log(''); + + const waChannel = new WhatsAppChannel(config); + try { + await waChannel.startForPairing(); + } catch (err: any) { + console.log(chalk.red(` ✗ Failed to start WhatsApp: ${err.message || err}`)); + process.exit(1); + } + + // Wait for connection + await new Promise(resolve => setTimeout(resolve, 5000)); + + const detected = await waChannel.detectGroup(); + if (detected) { + console.log(chalk.green(` ✓ Group detected: ${detected.groupName} (${detected.groupId})`)); + console.log(chalk.dim(' Mercury will now listen for messages in this group.')); + } else { + console.log(chalk.yellow(' ✗ No group named "Mercury" found.')); + console.log(chalk.dim(' Create a WhatsApp group named "Mercury" and try again.')); + console.log(chalk.dim(' Tip: You can add any contact to create the group, then remove them.')); + } + + await waChannel.stop(); + }); + +whatsappCmd + .command('groups') + .description('List all WhatsApp groups the account participates in') + .action(async () => { + const config = loadConfig(); + if (!config.channels.whatsapp.paired || !authDirExists()) { + console.log(chalk.red(' WhatsApp is not paired. Run "mercury whatsapp pair" first.')); + process.exit(1); + } + + console.log(''); + console.log(chalk.bold.white(' WhatsApp Groups')); + console.log(''); + + const waChannel = new WhatsAppChannel(config); + try { + await waChannel.startForPairing(); + } catch (err: any) { + console.log(chalk.red(` ✗ Failed to start WhatsApp: ${err.message || err}`)); + process.exit(1); + } + + // Wait for connection + await new Promise(resolve => setTimeout(resolve, 5000)); + + const groups = await waChannel.listGroups(); + if (groups.length === 0) { + console.log(chalk.dim(' No groups found.')); + } else { + const configuredGroupId = config.channels.whatsapp.groupId; + for (const g of groups) { + const marker = g.groupId === configuredGroupId ? chalk.green(' ← active') : ''; + console.log(` ${chalk.white(g.groupName)} ${chalk.dim(g.groupId)}${marker}`); + } + } + console.log(''); + + await waChannel.stop(); + }); + +whatsappCmd + .command('set-group ') + .description('Manually set the WhatsApp group ID to listen for messages in') + .action((groupId: string) => { + const config = loadConfig(); + config.channels.whatsapp.groupId = groupId; + config.channels.whatsapp.mode = 'group'; + saveConfig(config); + console.log(chalk.green(` ✓ Group ID set to: ${groupId}`)); + console.log(chalk.dim(' Restart Mercury for changes to take effect.')); + }); + const serviceCmd = program .command('service') .description('Manage Mercury as a system service (auto-start, crash recovery)'); diff --git a/src/types/channel.ts b/src/types/channel.ts index 4ee477e0..4b8cc192 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -137,3 +137,24 @@ export interface SlackChannelConfig extends ChannelConfig { members: SlackAccessUser[]; pending: SlackPendingRequest[]; } + +export interface WhatsAppAdmin { + jid: string; + phoneNumber: string; + name?: string; + pairedAt: string; +} + +export type WhatsAppMode = 'self' | 'group'; + +export interface WhatsAppChannelConfig extends ChannelConfig { + type: 'whatsapp'; + phoneNumber: string; + registered: boolean; + paired: boolean; + admin: WhatsAppAdmin | null; + adminPaired: boolean; + mode: WhatsAppMode; + groupId?: string; + groupName?: string; +} diff --git a/src/utils/config.ts b/src/utils/config.ts index f7ce2b49..45401ba6 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -3,7 +3,7 @@ import { join, resolve } from 'node:path'; import { homedir } from 'node:os'; import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; import { config as loadDotenv } from 'dotenv'; -import type { SignalAccessUser, SignalPendingRequest, DiscordAccessUser, DiscordPendingRequest, SlackAccessUser, SlackPendingRequest } from '../types/channel.js'; +import type { SignalAccessUser, SignalPendingRequest, DiscordAccessUser, DiscordPendingRequest, SlackAccessUser, SlackPendingRequest, WhatsAppAdmin } from '../types/channel.js'; const MERCURY_HOME = join(homedir(), '.mercury'); @@ -47,6 +47,13 @@ export interface TelegramPendingRequest { pairingCode?: string; } +export interface WhatsAppAdminConfig { + jid: string; + phoneNumber: string; + name?: string; + pairedAt: string; +} + export type ProviderName = | 'openai' | 'anthropic' @@ -126,6 +133,17 @@ export interface MercuryConfig { members: SlackAccessUser[]; pending: SlackPendingRequest[]; }; + whatsapp: { + enabled: boolean; + phoneNumber: string; + registered: boolean; + paired: boolean; + admin: WhatsAppAdminConfig | null; + adminPaired: boolean; + mode: string; + groupId?: string; + groupName?: string; + }; }; github: { username: string; @@ -328,6 +346,17 @@ export function getDefaultConfig(): MercuryConfig { members: [], pending: [], }, + whatsapp: { + enabled: getEnvBool('WHATSAPP_ENABLED', false), + phoneNumber: getEnv('WHATSAPP_PHONE_NUMBER', ''), + registered: false, + paired: false, + admin: null, + adminPaired: false, + mode: getEnv('WHATSAPP_MODE', 'group'), + groupId: getEnv('WHATSAPP_GROUP_ID', ''), + groupName: getEnv('WHATSAPP_GROUP_NAME', 'Mercury'), + }, }, github: { username: getEnv('GITHUB_USERNAME', ''), @@ -384,14 +413,18 @@ export function loadConfig(): MercuryConfig { migrateLegacyOllamaLocalBaseUrl( migrateLegacyOllamaCloudBaseUrl( migrateLegacySignalAccess( - migrateLegacyTelegramAccess(deepMerge(defaults, fileConfig)), + migrateLegacyTelegramAccess( + migrateLegacyWhatsAppAccess(deepMerge(defaults, fileConfig)), + ), ), ), ), ); } return migrateLegacyDiscordAccess( - migrateLegacyTelegramAccess(getDefaultConfig()), + migrateLegacyWhatsAppAccess( + migrateLegacyTelegramAccess(getDefaultConfig()), + ), ); } @@ -1142,3 +1175,54 @@ export function migrateLegacySlackAccess(config: MercuryConfig): MercuryConfig { slack.pending = slack.pending || []; return config; } + +// ── WhatsApp access helpers ───────────────────────────────────── + +export function getWhatsAppAdmin(config: MercuryConfig): WhatsAppAdminConfig | null { + return config.channels.whatsapp.admin; +} + +export function hasWhatsAppAdmin(config: MercuryConfig): boolean { + return config.channels.whatsapp.admin !== null; +} + +export function getWhatsAppAccessSummary(config: MercuryConfig): string { + if (!config.channels.whatsapp.admin) return 'not paired'; + return `admin: ${config.channels.whatsapp.admin.phoneNumber} (paired ${config.channels.whatsapp.admin.pairedAt})`; +} + +export function setWhatsAppAdmin(config: MercuryConfig, admin: WhatsAppAdminConfig): MercuryConfig { + config.channels.whatsapp.admin = admin; + config.channels.whatsapp.paired = true; + config.channels.whatsapp.registered = true; + return config; +} + +export function clearWhatsAppAccess(config: MercuryConfig): MercuryConfig { + config.channels.whatsapp.admin = null; + config.channels.whatsapp.paired = false; + config.channels.whatsapp.registered = false; + return config; +} + +export function isWhatsAppPaired(config: MercuryConfig): boolean { + return config.channels.whatsapp.paired && config.channels.whatsapp.admin !== null; +} + +export function migrateLegacyWhatsAppAccess(config: MercuryConfig): MercuryConfig { + const wa = config.channels.whatsapp as any; + if (!wa) return config; + if (wa.admin === undefined) wa.admin = null; + if (wa.registered === undefined) wa.registered = false; + if (wa.paired === undefined) wa.paired = false; + if (wa.adminPaired === undefined) wa.adminPaired = !!wa.admin; + if (wa.phoneNumber === undefined) wa.phoneNumber = ''; + if (wa.mode === undefined) wa.mode = 'group'; + if (wa.groupName === undefined) wa.groupName = 'Mercury'; + // Clean up stale fields from previous implementation + delete wa.sessionDir; + delete wa.admins; + delete wa.members; + delete wa.pending; + return config; +} diff --git a/src/whatsapp/auth.ts b/src/whatsapp/auth.ts new file mode 100644 index 00000000..36f236ff --- /dev/null +++ b/src/whatsapp/auth.ts @@ -0,0 +1,47 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; +import { logger } from '../utils/logger.js'; +import { getMercuryHome } from '../utils/config.js'; + +export const WHATSAPP_AUTH_DIR = () => join(getMercuryHome(), 'whatsapp-auth'); + +export function authDirExists(): boolean { + const dir = WHATSAPP_AUTH_DIR(); + return fs.existsSync(dir) && fs.existsSync(path.join(dir, 'creds.json')); +} + +export function ensureAuthDir(): string { + const dir = WHATSAPP_AUTH_DIR(); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + try { + fs.chmodSync(dir, 0o700); + } catch { + // chmod may fail on some platforms, continue anyway + } + return dir; +} + +export function deleteAuthDir(): void { + const dir = WHATSAPP_AUTH_DIR(); + if (fs.existsSync(dir)) { + fs.rmSync(dir, { recursive: true, force: true }); + logger.info('WhatsApp auth data deleted'); + } +} + +export function validateAuthState(): boolean { + const dir = WHATSAPP_AUTH_DIR(); + if (!fs.existsSync(dir)) return false; + const credsPath = path.join(dir, 'creds.json'); + if (!fs.existsSync(credsPath)) return false; + try { + const data = JSON.parse(fs.readFileSync(credsPath, 'utf-8')); + return !!(data.me && data.me.id); + } catch { + return false; + } +} \ No newline at end of file diff --git a/src/whatsapp/rate-limiter.ts b/src/whatsapp/rate-limiter.ts new file mode 100644 index 00000000..9d20fa3e --- /dev/null +++ b/src/whatsapp/rate-limiter.ts @@ -0,0 +1,129 @@ +import { logger } from '../utils/logger.js'; + +interface RateLimitEntry { + timestamps: number[]; +} + +export class WhatsAppRateLimiter { + private perChatHourly = new Map(); + private globalHourly: number[] = []; + private globalDaily: number[] = []; + private lastSendTime = 0; + + private readonly MIN_SEND_INTERVAL_MS = 3000; + private readonly PER_CHAT_HOURLY_LIMIT = 60; + private readonly GLOBAL_HOURLY_LIMIT = 200; + private readonly GLOBAL_DAILY_LIMIT = 1000; + private readonly HOUR_MS = 3_600_000; + private readonly DAY_MS = 86_400_000; + + private queue: Array<{ fn: () => Promise; resolve: (v: unknown) => void; reject: (e: unknown) => void }> = []; + private processing = false; + + async enqueue(fn: () => Promise): Promise { + return new Promise((resolve, reject) => { + this.queue.push({ fn, resolve: resolve as (v: unknown) => void, reject }); + this.processQueue(); + }); + } + + private async processQueue(): Promise { + if (this.processing) return; + this.processing = true; + + while (this.queue.length > 0) { + const item = this.queue.shift()!; + try { + await this.waitIfRateLimited(); + const result = await item.fn(); + item.resolve(result); + } catch (err) { + item.reject(err); + } + } + + this.processing = false; + } + + private async waitIfRateLimited(): Promise { + const now = Date.now(); + const minWait = this.MIN_SEND_INTERVAL_MS - (now - this.lastSendTime); + if (minWait > 0) { + const jitter = Math.random() * 2000; + await this.sleep(minWait + jitter); + } + + this.pruneTimestamps(now); + this.lastSendTime = Date.now(); + this.globalHourly.push(this.lastSendTime); + this.globalDaily.push(this.lastSendTime); + } + + checkRateLimit(jid: string): { allowed: boolean; reason?: string } { + const now = Date.now(); + this.pruneTimestamps(now); + + if (this.globalDaily.length >= this.GLOBAL_DAILY_LIMIT) { + logger.warn({ dailyCount: this.globalDaily.length }, 'WhatsApp: daily rate limit reached'); + return { allowed: false, reason: 'Daily message limit reached (1000). Try again tomorrow.' }; + } + + if (this.globalHourly.length >= this.GLOBAL_HOURLY_LIMIT) { + logger.warn({ hourlyCount: this.globalHourly.length }, 'WhatsApp: hourly rate limit reached'); + return { allowed: false, reason: 'Hourly message limit reached (200). Try again later.' }; + } + + const chatTimestamps = this.perChatHourly.get(jid) || []; + if (chatTimestamps.length >= this.PER_CHAT_HOURLY_LIMIT) { + logger.warn({ jid, chatCount: chatTimestamps.length }, 'WhatsApp: per-chat hourly rate limit reached'); + return { allowed: false, reason: 'Per-chat hourly limit reached (60). Try again later.' }; + } + + return { allowed: true }; + } + + recordSend(jid: string): void { + const now = Date.now(); + const chatTimestamps = this.perChatHourly.get(jid) || []; + chatTimestamps.push(now); + this.perChatHourly.set(jid, chatTimestamps); + } + + private pruneTimestamps(now: number): void { + const hourAgo = now - this.HOUR_MS; + const dayAgo = now - this.DAY_MS; + + this.globalHourly = this.globalHourly.filter(t => t > hourAgo); + this.globalDaily = this.globalDaily.filter(t => t > dayAgo); + + for (const [jid, timestamps] of this.perChatHourly.entries()) { + const pruned = timestamps.filter(t => t > hourAgo); + if (pruned.length === 0) { + this.perChatHourly.delete(jid); + } else { + this.perChatHourly.set(jid, pruned); + } + } + } + + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + reset(): void { + this.perChatHourly.clear(); + this.globalHourly = []; + this.globalDaily = []; + this.lastSendTime = 0; + } + + getStats(): { hourlyGlobal: number; dailyGlobal: number; chatCount: number } { + const now = Date.now(); + this.pruneTimestamps(now); + return { + hourlyGlobal: this.globalHourly.length, + dailyGlobal: this.globalDaily.length, + chatCount: this.perChatHourly.size, + }; + } +} \ No newline at end of file diff --git a/tsup.config.ts b/tsup.config.ts index d8666b61..6e5bc7be 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -25,8 +25,10 @@ export default defineConfig({ "@ai-sdk/deepseek", "@ai-sdk/openai", "@grammyjs/auto-retry", + "@whiskeysockets/baileys", "chalk", "commander", + "discord.js", "dotenv", "grammy", "ink", @@ -35,6 +37,8 @@ export default defineConfig({ "node-cron", "ollama-ai-provider", "pino", + "protobufjs", + "qrcode-terminal", "react", "react-dom", "yaml", From 1d8ded6fdb5b6ebcf85de42d8f56264983c88c51 Mon Sep 17 00:00:00 2001 From: M Shehzen Date: Sat, 20 Jun 2026 15:11:48 +0530 Subject: [PATCH 2/8] refactor(onboarding): streamline doctor flow for already-paired channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Discord: if already paired (token + admins), show 'Reconfigure? (y/N)' instead of all setup instructions. Default No skips to next section. - Slack: same pattern — 'Reconfigure? (y/N)' when already paired. Full setup extracted to runSlackSetup() helper. - WhatsApp: if fully paired (device + group + admin), show 'WhatsApp linked to [phone] — group detected, admin paired. Reconfigure? (y/N)'. If partially paired, resume from incomplete step (unchanged). If not configured, show first-time setup instructions (unchanged). --- src/index.ts | 425 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 289 insertions(+), 136 deletions(-) diff --git a/src/index.ts b/src/index.ts index baff4284..6f507b55 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,7 @@ import { removeDiscordUser, clearDiscordAccess, getSlackAccessSummary, + hasSlackAdmins, findSlackPendingRequest, approveSlackPendingRequest, rejectSlackPendingRequest, @@ -1185,6 +1186,85 @@ async function runAdminPairingFlow(waChannel: WhatsAppChannel): Promise { } } +/** + * Full Slack setup flow — shows instructions, prompts for tokens, saves config. + * Used both for first-time setup and reconfiguration. + */ +async function runSlackSetup(config: MercuryConfig, isReconfig: boolean): Promise { + if (isReconfig) { + console.log(chalk.dim(' Leave empty to keep current value. Enter "none" to disable.')); + } else { + console.log(chalk.dim(' Leave empty to skip. You can add it later.')); + } + console.log(chalk.dim(' To create a Slack app:')); + console.log(chalk.dim(' 1. Go to https://api.slack.com/apps → Create New App → From scratch')); + console.log(chalk.dim(' 2. Under "Socket Mode", enable it and generate an App-Level Token')); + console.log(chalk.dim(' with connections:write scope → copy the xapp- token')); + console.log(chalk.dim(' 3. Under "OAuth & Permissions", add Bot Token Scopes:')); + console.log(chalk.dim(' chat:write, chat:write.public, chat:write.customize,')); + console.log(chalk.dim(' channels:history, groups:history, im:history, im:write,')); + console.log(chalk.dim(' files:write, commands, app_mentions:read')); + console.log(chalk.dim(' 4. Install app to workspace → copy Bot User OAuth Token (xoxb-)')); + console.log(chalk.dim(' 5. Under "Event Subscriptions", enable and subscribe to:')); + console.log(chalk.dim(' message.channels, message.groups, message.im, app_mention')); + console.log(chalk.dim(' 6. Under "Interactivity & Shortcuts", enable interactivity')); + console.log(chalk.dim(' 7. Under "Slash Commands", create /mercury command')); + console.log(chalk.dim(' 8. Under "App Home", check "Allow users to send Slash commands')); + console.log(chalk.dim(' and messages from the messages tab"')); + console.log(chalk.dim(' 9. Invite the bot to your channel: /invite @Mercury')); + console.log(chalk.dim(' 10. DM the bot /mercury start to become the first admin.')); + console.log(chalk.dim(' Channel members can chat openly. DMs require admin approval.')); + + const slMask = isReconfig && config.channels.slack.botToken ? ` [${maskKey(config.channels.slack.botToken)}]` : ''; + const slackBotToken = await ask(chalk.white(` Slack Bot Token${slMask} (starts with xoxb-): `)); + if (isReconfig && slackBotToken.toLowerCase() === 'none') { + config.channels.slack.enabled = false; + config.channels.slack.botToken = ''; + clearSlackAccess(config); + } else if (slackBotToken) { + if (slackBotToken !== config.channels.slack.botToken) { + clearSlackAccess(config); + } + config.channels.slack.botToken = slackBotToken; + appendToEnv('SLACK_BOT_TOKEN', slackBotToken); + } + + if (config.channels.slack.enabled || config.channels.slack.botToken) { + const slAppMask = isReconfig && config.channels.slack.appToken ? ` [${maskKey(config.channels.slack.appToken)}]` : ''; + const slackAppToken = await ask(chalk.white(` Slack App-Level Token${slAppMask} (starts with xapp-): `)); + if (slackAppToken && slackAppToken.toLowerCase() !== 'none') { + config.channels.slack.appToken = slackAppToken; + appendToEnv('SLACK_APP_TOKEN', slackAppToken); + } else if (slackAppToken.toLowerCase() === 'none') { + config.channels.slack.appToken = ''; + } + + if (config.channels.slack.botToken) { + config.channels.slack.enabled = true; + + if (!config.channels.slack.channelId) { + console.log(chalk.dim(' To find a Channel ID: right-click the channel name → Copy Channel ID.')); + const slChannelId = await ask(chalk.white(' Slack Channel ID (optional — leave empty for all channels): ')); + if (slChannelId.trim()) { + config.channels.slack.channelId = slChannelId.trim(); + } + } + + if (!config.channels.slack.teamId) { + const slTeamId = await ask(chalk.white(' Slack Team/Workspace ID (optional): ')); + if (slTeamId.trim()) { + config.channels.slack.teamId = slTeamId.trim(); + } + } + + saveConfig(config); + } + } else if (!config.channels.slack.botToken) { + config.channels.slack.enabled = false; + saveConfig(config); + } +} + async function configure(existingConfig?: MercuryConfig): Promise { const isReconfig = !!existingConfig; const config = existingConfig ?? loadConfig(); @@ -1734,172 +1814,238 @@ async function configure(existingConfig?: MercuryConfig): Promise { hr(); console.log(''); console.log(chalk.bold.white(' Discord (optional)')); - if (isReconfig) { - console.log(chalk.dim(' Leave empty to keep current value. Enter "none" to disable.')); - } else { - console.log(chalk.dim(' Leave empty to skip. You can add it later.')); - } - console.log(chalk.dim(' To create a Discord bot:')); - console.log(chalk.dim(' 1. Go to https://discord.com/developers/applications')); - console.log(chalk.dim(' 2. Click "New Application" → give it a name')); - console.log(chalk.dim(' 3. Navigate to Bot → Click "Reset Token" → Copy the token')); - console.log(chalk.dim(' 4. Enable Privileged Gateway Intents:')); - console.log(chalk.dim(' - Message Content Intent')); - console.log(chalk.dim(' 5. Go to OAuth2 → URL Generator:')); - console.log(chalk.dim(' Scopes: bot, applications.commands')); - console.log(chalk.dim(' Bot Permissions: Send Messages, Read Message History,')); - console.log(chalk.dim(' Use Slash Commands, Attach Files, Embed Links')); - console.log(chalk.dim(' 6. Open the generated URL to invite the bot to your server')); - console.log(chalk.dim(' 7. Optionally create a "Mercury Admin" role in your server')); - console.log(chalk.dim(' Guild members can chat openly. DMs require pairing (like Telegram).')); - console.log(''); - - const dcMask = isReconfig && config.channels.discord.botToken ? ` [${maskKey(config.channels.discord.botToken)}]` : ''; - const discordToken = await ask(chalk.white(` Discord Bot Token${dcMask}: `)); - if (isReconfig && discordToken.toLowerCase() === 'none') { - config.channels.discord.enabled = false; - config.channels.discord.botToken = ''; - clearDiscordAccess(config); - } else if (discordToken) { - if (discordToken !== config.channels.discord.botToken) { - clearDiscordAccess(config); - } - config.channels.discord.botToken = discordToken; - appendToEnv('DISCORD_BOT_TOKEN', discordToken); - config.channels.discord.enabled = true; - } - if (config.channels.discord.enabled && config.channels.discord.botToken) { - if (!config.channels.discord.guildId) { + // If already fully paired, offer simple reconfigure prompt + if (isReconfig && config.channels.discord.enabled && config.channels.discord.botToken && hasDiscordAdminsFn(config)) { + console.log(chalk.green(` ✓ Discord paired: ${getDiscordAccessSummary(config)}`)); + const reconfig = await ask(chalk.white(' Reconfigure? (y/N): ')); + if (reconfig.trim().toLowerCase() !== 'y' && reconfig.trim().toLowerCase() !== 'yes') { + // Skip — keep existing config, fall through to Slack section below + } else { + // Fall through to full setup below + console.log(chalk.dim(' Leave empty to keep current value. Enter "none" to disable.')); + console.log(chalk.dim(' To create a Discord bot:')); + console.log(chalk.dim(' 1. Go to https://discord.com/developers/applications')); + console.log(chalk.dim(' 2. Click "New Application" → give it a name')); + console.log(chalk.dim(' 3. Navigate to Bot → Click "Reset Token" → Copy the token')); + console.log(chalk.dim(' 4. Enable Privileged Gateway Intents:')); + console.log(chalk.dim(' - Message Content Intent')); + console.log(chalk.dim(' 5. Go to OAuth2 → URL Generator:')); + console.log(chalk.dim(' Scopes: bot, applications.commands')); + console.log(chalk.dim(' Bot Permissions: Send Messages, Read Message History,')); + console.log(chalk.dim(' Use Slash Commands, Attach Files, Embed Links')); + console.log(chalk.dim(' 6. Open the generated URL to invite the bot to your server')); + console.log(chalk.dim(' 7. Optionally create a "Mercury Admin" role in your server')); + console.log(chalk.dim(' Guild members can chat openly. DMs require pairing (like Telegram).')); console.log(''); - console.log(chalk.dim(' To find your Server ID: in Discord, go to Settings → App Settings →')); - console.log(chalk.dim(' Advanced → toggle Developer Mode ON. Then right-click your server')); - console.log(chalk.dim(' name in the sidebar → Copy Server ID.')); - const guildId = await ask(chalk.white(' Discord Guild/Server ID (optional — leave empty for all servers): ')); - if (guildId.trim()) { - config.channels.discord.guildId = guildId.trim(); - } - } - if (!config.channels.discord.channelId) { - console.log(chalk.dim(' To find a Channel ID: right-click the channel name in the sidebar → Copy Channel ID.')); - const channelId = await ask(chalk.white(' Discord Channel ID (optional — leave empty for all channels): ')); - if (channelId.trim()) { - config.channels.discord.channelId = channelId.trim(); + const dcMask = config.channels.discord.botToken ? ` [${maskKey(config.channels.discord.botToken)}]` : ''; + const discordToken = await ask(chalk.white(` Discord Bot Token${dcMask}: `)); + if (discordToken.toLowerCase() === 'none') { + config.channels.discord.enabled = false; + config.channels.discord.botToken = ''; + clearDiscordAccess(config); + } else if (discordToken) { + if (discordToken !== config.channels.discord.botToken) { + clearDiscordAccess(config); + } + config.channels.discord.botToken = discordToken; + appendToEnv('DISCORD_BOT_TOKEN', discordToken); + config.channels.discord.enabled = true; } - } - const adminRoleCurrent = isReconfig && config.channels.discord.adminRoleName ? ` [${config.channels.discord.adminRoleName}]` : ''; - const adminRoleName = await ask(chalk.white(` Admin role name${adminRoleCurrent} [Mercury Admin]: `)); - if (adminRoleName.trim()) { - config.channels.discord.adminRoleName = adminRoleName.trim(); - } else if (!config.channels.discord.adminRoleName) { - config.channels.discord.adminRoleName = 'Mercury Admin'; - } + if (config.channels.discord.enabled && config.channels.discord.botToken) { + const guildIdCurrent = config.channels.discord.guildId ? ` [${config.channels.discord.guildId}]` : ''; + const guildId = await ask(chalk.white(` Discord Guild/Server ID${guildIdCurrent} (optional — leave empty for all): `)); + if (guildId.trim()) { + config.channels.discord.guildId = guildId.trim(); + } - saveConfig(config); - } else if (!config.channels.discord.botToken) { - config.channels.discord.enabled = false; - saveConfig(config); - } + const channelIdCurrent = config.channels.discord.channelId ? ` [${config.channels.discord.channelId}]` : ''; + const channelId = await ask(chalk.white(` Discord Channel ID${channelIdCurrent} (optional — leave empty for all): `)); + if (channelId.trim()) { + config.channels.discord.channelId = channelId.trim(); + } - await completeInitialDiscordPairing(config); + const adminRoleCurrent = config.channels.discord.adminRoleName ? ` [${config.channels.discord.adminRoleName}]` : ''; + const adminRoleName = await ask(chalk.white(` Admin role name${adminRoleCurrent} [Mercury Admin]: `)); + if (adminRoleName.trim()) { + config.channels.discord.adminRoleName = adminRoleName.trim(); + } else if (!config.channels.discord.adminRoleName) { + config.channels.discord.adminRoleName = 'Mercury Admin'; + } - hr(); - console.log(''); - console.log(chalk.bold.white(' Slack (optional)')); - if (isReconfig) { - console.log(chalk.dim(' Leave empty to keep current value. Enter "none" to disable.')); - } else { - console.log(chalk.dim(' Leave empty to skip. You can add it later.')); - } - console.log(chalk.dim(' To create a Slack app:')); - console.log(chalk.dim(' 1. Go to https://api.slack.com/apps → Create New App → From scratch')); - console.log(chalk.dim(' 2. Under "Socket Mode", enable it and generate an App-Level Token')); - console.log(chalk.dim(' with connections:write scope → copy the xapp- token')); - console.log(chalk.dim(' 3. Under "OAuth & Permissions", add Bot Token Scopes:')); - console.log(chalk.dim(' chat:write, chat:write.public, chat:write.customize,')); - console.log(chalk.dim(' channels:history, groups:history, im:history, im:write,')); - console.log(chalk.dim(' files:write, commands, app_mentions:read')); - console.log(chalk.dim(' 4. Install app to workspace → copy Bot User OAuth Token (xoxb-)')); - console.log(chalk.dim(' 5. Under "Event Subscriptions", enable and subscribe to:')); - console.log(chalk.dim(' message.channels, message.groups, message.im, app_mention')); - console.log(chalk.dim(' 6. Under "Interactivity & Shortcuts", enable interactivity')); - console.log(chalk.dim(' 7. Under "Slash Commands", create /mercury command')); - console.log(chalk.dim(' 8. Under "App Home", check "Allow users to send Slash commands')); - console.log(chalk.dim(' and messages from the messages tab"')); - console.log(chalk.dim(' 9. Invite the bot to your channel: /invite @Mercury')); - console.log(chalk.dim(' 10. DM the bot /mercury start to become the first admin.')); - console.log(chalk.dim(' Channel members can chat openly. DMs require admin approval.')); + saveConfig(config); + } - const slMask = isReconfig && config.channels.slack.botToken ? ` [${maskKey(config.channels.slack.botToken)}]` : ''; - const slackBotToken = await ask(chalk.white(` Slack Bot Token${slMask} (starts with xoxb-): `)); - if (isReconfig && slackBotToken.toLowerCase() === 'none') { - config.channels.slack.enabled = false; - config.channels.slack.botToken = ''; - clearSlackAccess(config); - } else if (slackBotToken) { - if (slackBotToken !== config.channels.slack.botToken) { - clearSlackAccess(config); + await completeInitialDiscordPairing(config); } - config.channels.slack.botToken = slackBotToken; - appendToEnv('SLACK_BOT_TOKEN', slackBotToken); - } + } else { + // Not yet paired — show full setup + if (isReconfig) { + console.log(chalk.dim(' Leave empty to keep current value. Enter "none" to disable.')); + } else { + console.log(chalk.dim(' Leave empty to skip. You can add it later.')); + } + console.log(chalk.dim(' To create a Discord bot:')); + console.log(chalk.dim(' 1. Go to https://discord.com/developers/applications')); + console.log(chalk.dim(' 2. Click "New Application" → give it a name')); + console.log(chalk.dim(' 3. Navigate to Bot → Click "Reset Token" → Copy the token')); + console.log(chalk.dim(' 4. Enable Privileged Gateway Intents:')); + console.log(chalk.dim(' - Message Content Intent')); + console.log(chalk.dim(' 5. Go to OAuth2 → URL Generator:')); + console.log(chalk.dim(' Scopes: bot, applications.commands')); + console.log(chalk.dim(' Bot Permissions: Send Messages, Read Message History,')); + console.log(chalk.dim(' Use Slash Commands, Attach Files, Embed Links')); + console.log(chalk.dim(' 6. Open the generated URL to invite the bot to your server')); + console.log(chalk.dim(' 7. Optionally create a "Mercury Admin" role in your server')); + console.log(chalk.dim(' Guild members can chat openly. DMs require pairing (like Telegram).')); + console.log(''); - if (config.channels.slack.enabled || config.channels.slack.botToken) { - const slAppMask = isReconfig && config.channels.slack.appToken ? ` [${maskKey(config.channels.slack.appToken)}]` : ''; - const slackAppToken = await ask(chalk.white(` Slack App-Level Token${slAppMask} (starts with xapp-): `)); - if (slackAppToken && slackAppToken.toLowerCase() !== 'none') { - config.channels.slack.appToken = slackAppToken; - appendToEnv('SLACK_APP_TOKEN', slackAppToken); - } else if (slackAppToken.toLowerCase() === 'none') { - config.channels.slack.appToken = ''; + const dcMask = isReconfig && config.channels.discord.botToken ? ` [${maskKey(config.channels.discord.botToken)}]` : ''; + const discordToken = await ask(chalk.white(` Discord Bot Token${dcMask}: `)); + if (isReconfig && discordToken.toLowerCase() === 'none') { + config.channels.discord.enabled = false; + config.channels.discord.botToken = ''; + clearDiscordAccess(config); + } else if (discordToken) { + if (discordToken !== config.channels.discord.botToken) { + clearDiscordAccess(config); + } + config.channels.discord.botToken = discordToken; + appendToEnv('DISCORD_BOT_TOKEN', discordToken); + config.channels.discord.enabled = true; } - if (config.channels.slack.botToken) { - config.channels.slack.enabled = true; - - if (!config.channels.slack.channelId) { - console.log(chalk.dim(' To find a Channel ID: right-click the channel name → Copy Channel ID.')); - const slChannelId = await ask(chalk.white(' Slack Channel ID (optional — leave empty for all channels): ')); - if (slChannelId.trim()) { - config.channels.slack.channelId = slChannelId.trim(); + if (config.channels.discord.enabled && config.channels.discord.botToken) { + if (!config.channels.discord.guildId) { + console.log(''); + console.log(chalk.dim(' To find your Server ID: in Discord, go to Settings → App Settings →')); + console.log(chalk.dim(' Advanced → toggle Developer Mode ON. Then right-click your server')); + console.log(chalk.dim(' name in the sidebar → Copy Server ID.')); + const guildId = await ask(chalk.white(' Discord Guild/Server ID (optional — leave empty for all servers): ')); + if (guildId.trim()) { + config.channels.discord.guildId = guildId.trim(); } } - if (!config.channels.slack.teamId) { - const slTeamId = await ask(chalk.white(' Slack Team/Workspace ID (optional): ')); - if (slTeamId.trim()) { - config.channels.slack.teamId = slTeamId.trim(); + if (!config.channels.discord.channelId) { + console.log(chalk.dim(' To find a Channel ID: right-click the channel name in the sidebar → Copy Channel ID.')); + const channelId = await ask(chalk.white(' Discord Channel ID (optional — leave empty for all channels): ')); + if (channelId.trim()) { + config.channels.discord.channelId = channelId.trim(); } } + const adminRoleCurrent = isReconfig && config.channels.discord.adminRoleName ? ` [${config.channels.discord.adminRoleName}]` : ''; + const adminRoleName = await ask(chalk.white(` Admin role name${adminRoleCurrent} [Mercury Admin]: `)); + if (adminRoleName.trim()) { + config.channels.discord.adminRoleName = adminRoleName.trim(); + } else if (!config.channels.discord.adminRoleName) { + config.channels.discord.adminRoleName = 'Mercury Admin'; + } + + saveConfig(config); + } else if (!config.channels.discord.botToken) { + config.channels.discord.enabled = false; saveConfig(config); } - } else if (!config.channels.slack.botToken) { - config.channels.slack.enabled = false; - saveConfig(config); + + await completeInitialDiscordPairing(config); + } + + hr(); + console.log(''); + console.log(chalk.bold.white(' Slack (optional)')); + + // If already fully paired, offer simple reconfigure prompt + if (isReconfig && config.channels.slack.enabled && config.channels.slack.botToken && hasSlackAdmins(config)) { + console.log(chalk.green(` ✓ Slack paired: ${getSlackAccessSummary(config)}`)); + const reconfig = await ask(chalk.white(' Reconfigure? (y/N): ')); + if (reconfig.trim().toLowerCase() !== 'y' && reconfig.trim().toLowerCase() !== 'yes') { + // Skip — keep existing config + } else { + await runSlackSetup(config, isReconfig); + } + } else { + await runSlackSetup(config, isReconfig); } // ── WhatsApp ───────────────────────────────────────────────────── hr(); console.log(''); console.log(chalk.bold.white(' WhatsApp (optional)')); - if (isReconfig && config.channels.whatsapp.phoneNumber) { + + const wa = config.channels.whatsapp; + const waFullyPaired = wa.paired && wa.adminPaired && !!wa.groupId && wa.enabled && !!wa.phoneNumber; + + if (isReconfig && waFullyPaired) { + // Already fully paired — show simple reconfigure prompt + const groupLabel = wa.groupName ? `group "${wa.groupName}"` : 'group detected'; + console.log(chalk.green(` ✓ WhatsApp linked to ${redactPhone(wa.phoneNumber)} — ${groupLabel}, admin paired`)); + const reconfig = await ask(chalk.white(' Reconfigure? (y/N): ')); + if (reconfig.trim().toLowerCase() === 'y' || reconfig.trim().toLowerCase() === 'yes') { + // Show the advanced menu + console.log(chalk.dim(' Enter "none" to disable. "reset" to clear session and re-pair. "unregister" to unlink device.')); + console.log(''); + const waPhoneMask = ` [${redactPhone(wa.phoneNumber)}]`; + const waPhone = await ask(chalk.white(` WhatsApp Phone Number${waPhoneMask}: `)); + if (waPhone.toLowerCase() === 'none') { + config.channels.whatsapp.enabled = false; + config.channels.whatsapp.phoneNumber = ''; + config.channels.whatsapp.registered = false; + config.channels.whatsapp.paired = false; + config.channels.whatsapp.adminPaired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + deleteAuthDir(); + saveConfig(config); + } else if (waPhone.toLowerCase() === 'reset') { + deleteAuthDir(); + config.channels.whatsapp.paired = false; + config.channels.whatsapp.adminPaired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + config.channels.whatsapp.enabled = true; + saveConfig(config); + console.log(''); + console.log(chalk.yellow(' WhatsApp session cleared. Starting QR pairing...')); + console.log(''); + await completeInitialWhatsAppPairing(config); + } else if (waPhone.toLowerCase() === 'unregister') { + deleteAuthDir(); + config.channels.whatsapp.enabled = false; + config.channels.whatsapp.phoneNumber = ''; + config.channels.whatsapp.registered = false; + config.channels.whatsapp.paired = false; + config.channels.whatsapp.adminPaired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + saveConfig(config); + console.log(''); + console.log(chalk.green(' WhatsApp unregistered. Device removed and data deleted.')); + console.log(''); + } else if (waPhone) { + config.channels.whatsapp.phoneNumber = waPhone; + config.channels.whatsapp.enabled = true; + config.channels.whatsapp.registered = true; + config.channels.whatsapp.paired = false; + config.channels.whatsapp.adminPaired = false; + config.channels.whatsapp.admin = null; + config.channels.whatsapp.groupId = ''; + deleteAuthDir(); + saveConfig(config); + await completeInitialWhatsAppPairing(config); + } + } + } else if (isReconfig && wa.phoneNumber && (wa.paired || wa.registered)) { + // Partially set up — resume from incomplete step console.log(chalk.dim(' Leave empty to keep current. Enter "none" to disable.')); console.log(chalk.dim(' Enter "reset" to clear session and re-pair with a QR code.')); console.log(chalk.dim(' Enter "unregister" to unlink device from WhatsApp and delete all data.')); - } else { - console.log(chalk.dim(' Leave empty to skip. You can add it later with mercury doctor.')); - console.log(chalk.dim(' WhatsApp lets you chat with Mercury through a WhatsApp group.')); - console.log(chalk.dim(' You scan a QR code to link Mercury as a companion device.')); - console.log(chalk.dim(' Then create a WhatsApp group named "Mercury" (just yourself).')); - console.log(chalk.dim(' Mercury will auto-detect the group and listen for messages there.')); - } - console.log(''); + console.log(''); - if (isReconfig && config.channels.whatsapp.phoneNumber) { - const waPhoneMask = ` [${redactPhone(config.channels.whatsapp.phoneNumber)}]`; + const waPhoneMask = ` [${redactPhone(wa.phoneNumber)}]`; const waPhone = await ask(chalk.white(` WhatsApp Phone Number${waPhoneMask}: `)); if (waPhone.toLowerCase() === 'none') { config.channels.whatsapp.enabled = false; @@ -1956,7 +2102,6 @@ async function configure(existingConfig?: MercuryConfig): Promise { } } else { // User pressed Enter to keep existing number — check if setup is incomplete - const wa = config.channels.whatsapp; if (wa.registered && (!wa.paired || !wa.adminPaired || !wa.groupId)) { console.log(''); if (!wa.paired) { @@ -1971,6 +2116,14 @@ async function configure(existingConfig?: MercuryConfig): Promise { } } } else { + // Not configured at all — first-time setup + console.log(chalk.dim(' Leave empty to skip. You can add it later with mercury doctor.')); + console.log(chalk.dim(' WhatsApp lets you chat with Mercury through a WhatsApp group.')); + console.log(chalk.dim(' You scan a QR code to link Mercury as a companion device.')); + console.log(chalk.dim(' Then create a WhatsApp group named "Mercury" (just yourself).')); + console.log(chalk.dim(' Mercury will auto-detect the group and listen for messages there.')); + console.log(''); + const waPhone = await ask(chalk.white(' WhatsApp Phone Number (with country code, e.g., +1234567890): ')); if (waPhone) { config.channels.whatsapp.phoneNumber = waPhone; From ab735f929ba56505b9810e43d6eacfb1728bb866 Mon Sep 17 00:00:00 2001 From: M Shehzen Date: Sat, 20 Jun 2026 15:23:12 +0530 Subject: [PATCH 3/8] refactor(onboarding): streamline doctor flow for Telegram and Signal channels - Telegram: if already paired (token + admins), show 'Reconfigure? (y/N)' with status summary instead of full setup instructions. Default No skips. - Signal: if already paired (phone + admins), show 'Reconfigure? (y/N)' with phone number and group name instead of 'Keep existing? (Y/n)'. Full setup extracted to runSignalSetup() and runTelegramSetup() helpers. - completeInitialSignalSetup: removed redundant 'already paired' prompt since the reconfigure decision is now handled by the caller. --- src/index.ts | 387 ++++++++++++++++++++++++++------------------------- 1 file changed, 201 insertions(+), 186 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6f507b55..3b190218 100644 --- a/src/index.ts +++ b/src/index.ts @@ -776,36 +776,9 @@ async function completeInitialSignalSetup(config: MercuryConfig): Promise return; } - // Already paired + // Already paired — the reconfigure decision is handled by the caller if (hasSignalAdminsFn(config)) { - console.log(chalk.green(' ✓ Signal already paired.')); - if (config.channels.signal.mode === 'group' && config.channels.signal.groupName) { - console.log(chalk.dim(` Group: ${config.channels.signal.groupName}`)); - } - console.log(chalk.dim(' To unregister, run: mercury signal unregister')); - console.log(''); - const keepConfig = await ask(chalk.white(' Keep the existing configuration? (Y/n): ')); - if (keepConfig.toLowerCase() !== 'n' && keepConfig.toLowerCase() !== 'no') { - return; - } - // Clear pairing data but keep phone number — fall through to fresh pairing - config.channels.signal.admins = []; - config.channels.signal.members = []; - config.channels.signal.pending = []; - config.channels.signal.groupId = undefined; - config.channels.signal.groupName = undefined; - - // Re-ask mode since we're starting fresh - const modeAnswer = await ask(chalk.white(' Mode — group or private? [group]: ')); - config.channels.signal.mode = modeAnswer.toLowerCase().startsWith('private') ? 'private' : 'group'; - saveConfig(config); - console.log(chalk.dim(' Configuration cleared. Starting fresh pairing...')); - - // Kill any running signal-cli daemon so group list is fresh - try { - const { killStaleSignalCliProcesses } = await import('./signal/jsonrpc.js'); - killStaleSignalCliProcesses(); - } catch { /* ignore */ } + return; } console.log(''); @@ -1186,6 +1159,40 @@ async function runAdminPairingFlow(waChannel: WhatsAppChannel): Promise { } } +/** + * Full Telegram setup flow — shows instructions, prompts for bot token, saves config. + * Used for first-time setup and reconfiguration. + */ +async function runTelegramSetup(config: MercuryConfig, isReconfig: boolean): Promise { + if (isReconfig) { + console.log(chalk.dim(' Leave empty to keep current value. Enter "none" to disable.')); + } else { + console.log(chalk.dim(' Leave empty to skip. You can add it later.')); + console.log(chalk.dim(' To create a bot token:')); + console.log(chalk.dim(' 1. Open Telegram and message @BotFather')); + console.log(chalk.dim(' 2. Run /newbot and follow the prompts')); + console.log(chalk.dim(' 3. Copy the bot token BotFather gives you')); + console.log(chalk.dim(' 4. Paste that token here')); + console.log(chalk.dim(' After setup, users send /start to request access.')); + console.log(chalk.dim(' The first Telegram user gets a pairing code, and you approve that code from the CLI.')); + } + console.log(''); + + const tgMask = isReconfig && config.channels.telegram.botToken ? ` [${maskKey(config.channels.telegram.botToken)}]` : ''; + const telegramToken = await ask(chalk.white(` Telegram Bot Token${tgMask}: `)); + if (isReconfig && telegramToken.toLowerCase() === 'none') { + config.channels.telegram.enabled = false; + config.channels.telegram.botToken = ''; + clearTelegramAccess(config); + } else if (telegramToken) { + if (telegramToken !== config.channels.telegram.botToken) { + clearTelegramAccess(config); + } + config.channels.telegram.botToken = telegramToken; + config.channels.telegram.enabled = true; + } +} + /** * Full Slack setup flow — shows instructions, prompts for tokens, saves config. * Used both for first-time setup and reconfiguration. @@ -1265,6 +1272,144 @@ async function runSlackSetup(config: MercuryConfig, isReconfig: boolean): Promis } } +/** + * Full Signal setup flow — shows instructions, prompts for phone number, handles + * none/reset/unregister commands. Used for first-time setup and reconfiguration. + */ +async function runSignalSetup(config: MercuryConfig, isReconfig: boolean): Promise { + if (isReconfig && config.channels.signal.phoneNumber) { + console.log(chalk.dim(' Leave empty to keep current number. Enter "none" to disable Signal.')); + console.log(chalk.dim(' Enter "reset" to start fresh (clear config, optionally delete binary).')); + console.log(chalk.dim(' Enter "unregister" to unlink this device from Signal and clear all data.')); + } else { + console.log(chalk.dim(' Leave empty to skip. You can add it later with mercury doctor.')); + console.log(chalk.dim(' Include country code, e.g. +1 for US, +44 for UK, +91 for India.')); + console.log(chalk.dim(' Signal lets you chat with Mercury through a Signal group or private chat.')); + } + console.log(''); + + const signalPhoneCurrent = isReconfig && config.channels.signal.phoneNumber ? ` [${redactPhone(config.channels.signal.phoneNumber)}]` : ''; + const signalPhoneInput = await ask(chalk.white(` Signal phone number${signalPhoneCurrent}: `)); + + if (isReconfig && signalPhoneInput.toLowerCase() === 'none') { + config.channels.signal.enabled = false; + config.channels.signal.phoneNumber = ''; + clearSignalAccess(config); + saveConfig(config); + } else if (isReconfig && signalPhoneInput.toLowerCase() === 'unregister') { + console.log(''); + console.log(chalk.yellow(' ⚠️ This will unlink this device from Signal and clear all Mercury Signal data.')); + console.log(chalk.yellow(' Mercury will no longer be able to send or receive Signal messages.')); + console.log(''); + const confirmUnregister = await ask(chalk.white(' Continue? (y/N): ')); + if (confirmUnregister.toLowerCase() !== 'y' && confirmUnregister.toLowerCase() !== 'yes') { + console.log(chalk.dim(' Cancelled.')); + } else { + const phoneNumberToDelete = config.channels.signal.phoneNumber; + + const { findSignalCli, sendSignalMessage } = await import('./signal/setup.js'); + if (findSignalCli()) { + const target: { groupId?: string; recipient?: string } = {}; + if (config.channels.signal.mode === 'group' && config.channels.signal.groupId) { + target.groupId = config.channels.signal.groupId; + } else if (config.channels.signal.admins.length > 0) { + target.recipient = config.channels.signal.admins[0].phoneNumber; + } + if (target.groupId || target.recipient) { + try { + await sendSignalMessage(config.channels.signal.phoneNumber, 'Mercury has been unregistered from this conversation. It will no longer respond here. To reconnect, the admin needs to set up Signal again with: mercury doctor', target); + } catch { /* best effort */ } + } + } + + const { killStaleSignalCliProcesses } = await import('./signal/jsonrpc.js'); + killStaleSignalCliProcesses(); + + console.log(chalk.dim(' Unlinking device from Signal...')); + const { unregisterSignalNumber } = await import('./signal/setup.js'); + const unregResult = await unregisterSignalNumber(config.channels.signal.phoneNumber); + if (unregResult.success) { + console.log(chalk.green(' ✓ Device unlinked from Signal server.')); + } else { + console.log(chalk.yellow(' ⚠ Could not reach Signal server to unlink device.')); + console.log(chalk.dim(' Local data has been cleared. The device will be unlinked automatically.')); + } + + clearSignalAccess(config); + config.channels.signal.enabled = false; + config.channels.signal.phoneNumber = ''; + config.channels.signal.groupId = undefined; + config.channels.signal.groupName = undefined; + saveConfig(config); + + const { deleteSignalCliAccountData } = await import('./signal/setup.js'); + if (phoneNumberToDelete) { + deleteSignalCliAccountData(phoneNumberToDelete); + } + + console.log(chalk.green(' ✓ Signal data cleared.')); + + const setupNew = await ask(chalk.white(' Set up Signal with a new number now? (y/N): ')); + if (setupNew.toLowerCase() === 'y' || setupNew.toLowerCase() === 'yes') { + const newPhone = await ask(chalk.white(' New Signal phone number (e.g. +1234567890): ')); + if (newPhone) { + config.channels.signal.phoneNumber = newPhone.trim(); + config.channels.signal.enabled = true; + const modeAnswer = await ask(chalk.white(' Mode — group or private? [group]: ')); + config.channels.signal.mode = modeAnswer.toLowerCase().startsWith('private') ? 'private' : 'group'; + saveConfig(config); + } + } + } + } else if (isReconfig && signalPhoneInput.toLowerCase() === 'reset') { + clearSignalAccess(config); + config.channels.signal.enabled = false; + config.channels.signal.phoneNumber = ''; + saveConfig(config); + + console.log(''); + console.log(chalk.yellow(' ⚠️ This will clear all Signal access and group connection.')); + const deleteBinary = await ask(chalk.white(' Also delete the signal-cli binary? (y/N): ')); + if (deleteBinary.toLowerCase() === 'y' || deleteBinary.toLowerCase() === 'yes') { + const { removeSignalCli, getSignalCliDir } = await import('./signal/setup.js'); + const { existsSync } = await import('node:fs'); + if (existsSync(getSignalCliDir())) { + removeSignalCli(); + console.log(chalk.green(' ✓ Signal binary removed.')); + } + } + + console.log(chalk.green(' ✓ Signal config reset.')); + console.log(chalk.dim(' Continue below to set up Signal with a new number.')); + console.log(''); + + const newPhone = await ask(chalk.white(' New Signal phone number (e.g. +1234567890): ')); + if (newPhone) { + config.channels.signal.phoneNumber = newPhone.trim(); + config.channels.signal.enabled = true; + + const modeAnswer = await ask(chalk.white(' Mode — group or private? [group]: ')); + config.channels.signal.mode = modeAnswer.toLowerCase().startsWith('private') ? 'private' : 'group'; + + saveConfig(config); + } + } else if (signalPhoneInput) { + if (signalPhoneInput !== config.channels.signal.phoneNumber) { + clearSignalAccess(config); + } + config.channels.signal.phoneNumber = signalPhoneInput.trim(); + config.channels.signal.enabled = true; + + const modeAnswer = await ask(chalk.white(' Mode — group or private? [group]: ')); + config.channels.signal.mode = modeAnswer.toLowerCase().startsWith('private') ? 'private' : 'group'; + + saveConfig(config); + } else if (!config.channels.signal.phoneNumber) { + config.channels.signal.enabled = false; + saveConfig(config); + } +} + async function configure(existingConfig?: MercuryConfig): Promise { const isReconfig = !!existingConfig; const config = existingConfig ?? loadConfig(); @@ -1642,175 +1787,45 @@ async function configure(existingConfig?: MercuryConfig): Promise { hr(); console.log(''); console.log(chalk.bold.white(' Telegram (optional)')); - if (isReconfig) { - console.log(chalk.dim(' Leave empty to keep current value. Enter "none" to disable.')); - } else { - console.log(chalk.dim(' Leave empty to skip. You can add it later.')); - console.log(chalk.dim(' To create a bot token:')); - console.log(chalk.dim(' 1. Open Telegram and message @BotFather')); - console.log(chalk.dim(' 2. Run /newbot and follow the prompts')); - console.log(chalk.dim(' 3. Copy the bot token BotFather gives you')); - console.log(chalk.dim(' 4. Paste that token here')); - console.log(chalk.dim(' After setup, users send /start to request access.')); - console.log(chalk.dim(' The first Telegram user gets a pairing code, and you approve that code from the CLI.')); - } - console.log(''); - const tgMask = isReconfig && config.channels.telegram.botToken ? ` [${maskKey(config.channels.telegram.botToken)}]` : ''; - const telegramToken = await ask(chalk.white(` Telegram Bot Token${tgMask}: `)); - if (isReconfig && telegramToken.toLowerCase() === 'none') { - config.channels.telegram.enabled = false; - config.channels.telegram.botToken = ''; - clearTelegramAccess(config); - } else if (telegramToken) { - if (telegramToken !== config.channels.telegram.botToken) { - clearTelegramAccess(config); + // If already fully paired, offer simple reconfigure prompt + if (isReconfig && config.channels.telegram.enabled && config.channels.telegram.botToken && hasTelegramAdmins(config)) { + console.log(chalk.green(` ✓ Telegram paired: ${getTelegramAccessSummary(config)}`)); + const reconfig = await ask(chalk.white(' Reconfigure? (y/N): ')); + if (reconfig.trim().toLowerCase() !== 'y' && reconfig.trim().toLowerCase() !== 'yes') { + // Skip — keep existing config + } else { + await runTelegramSetup(config, isReconfig); + await completeInitialTelegramPairing(config); } - config.channels.telegram.botToken = telegramToken; - config.channels.telegram.enabled = true; + } else { + await runTelegramSetup(config, isReconfig); + await completeInitialTelegramPairing(config); } - await completeInitialTelegramPairing(config); - hr(); console.log(''); console.log(chalk.bold.white(' Signal (optional)')); - if (isReconfig && config.channels.signal.phoneNumber) { - console.log(chalk.dim(' Leave empty to keep current number. Enter "none" to disable Signal.')); - console.log(chalk.dim(' Enter "reset" to start fresh (clear config, optionally delete binary).')); - console.log(chalk.dim(' Enter "unregister" to unlink this device from Signal and clear all data.')); - } else { - console.log(chalk.dim(' Leave empty to skip. You can add it later with mercury doctor.')); - console.log(chalk.dim(' Include country code, e.g. +1 for US, +44 for UK, +91 for India.')); - console.log(chalk.dim(' Signal lets you chat with Mercury through a Signal group or private chat.')); - } - console.log(''); - const signalPhoneCurrent = isReconfig && config.channels.signal.phoneNumber ? ` [${redactPhone(config.channels.signal.phoneNumber)}]` : ''; - const signalPhoneInput = await ask(chalk.white(` Signal phone number${signalPhoneCurrent}: `)); - - if (isReconfig && signalPhoneInput.toLowerCase() === 'none') { - config.channels.signal.enabled = false; - config.channels.signal.phoneNumber = ''; - clearSignalAccess(config); - saveConfig(config); - } else if (isReconfig && signalPhoneInput.toLowerCase() === 'unregister') { - console.log(''); - console.log(chalk.yellow(' ⚠️ This will unlink this device from Signal and clear all Mercury Signal data.')); - console.log(chalk.yellow(' Mercury will no longer be able to send or receive Signal messages.')); - console.log(''); - const confirmUnregister = await ask(chalk.white(' Continue? (y/N): ')); - if (confirmUnregister.toLowerCase() !== 'y' && confirmUnregister.toLowerCase() !== 'yes') { - console.log(chalk.dim(' Cancelled.')); + // If already fully paired, offer simple reconfigure prompt + if (isReconfig && config.channels.signal.enabled && config.channels.signal.phoneNumber && hasSignalAdminsFn(config)) { + const groupLabel = config.channels.signal.mode === 'group' && config.channels.signal.groupName + ? ` — group "${config.channels.signal.groupName}"` + : ''; + console.log(chalk.green(` ✓ Signal paired: ${redactPhone(config.channels.signal.phoneNumber)}${groupLabel}`)); + console.log(chalk.dim(' To unregister, run: mercury signal unregister')); + const reconfig = await ask(chalk.white(' Reconfigure? (y/N): ')); + if (reconfig.trim().toLowerCase() !== 'y' && reconfig.trim().toLowerCase() !== 'yes') { + // Skip — keep existing config } else { - const phoneNumberToDelete = config.channels.signal.phoneNumber; - - // Send goodbye message (best effort, skip if signal-cli not installed or target gone) - const { findSignalCli, sendSignalMessage } = await import('./signal/setup.js'); - if (findSignalCli()) { - const target: { groupId?: string; recipient?: string } = {}; - if (config.channels.signal.mode === 'group' && config.channels.signal.groupId) { - target.groupId = config.channels.signal.groupId; - } else if (config.channels.signal.admins.length > 0) { - target.recipient = config.channels.signal.admins[0].phoneNumber; - } - if (target.groupId || target.recipient) { - try { - await sendSignalMessage(config.channels.signal.phoneNumber, 'Mercury has been unregistered from this conversation. It will no longer respond here. To reconnect, the admin needs to set up Signal again with: mercury doctor', target); - } catch { /* best effort */ } - } - } - - const { killStaleSignalCliProcesses } = await import('./signal/jsonrpc.js'); - killStaleSignalCliProcesses(); - - // Unregister from Signal server - console.log(chalk.dim(' Unlinking device from Signal...')); - const { unregisterSignalNumber } = await import('./signal/setup.js'); - const unregResult = await unregisterSignalNumber(config.channels.signal.phoneNumber); - if (unregResult.success) { - console.log(chalk.green(' ✓ Device unlinked from Signal server.')); - } else { - console.log(chalk.yellow(' ⚠ Could not reach Signal server to unlink device.')); - console.log(chalk.dim(' Local data has been cleared. The device will be unlinked automatically.')); - } - - clearSignalAccess(config); - config.channels.signal.enabled = false; - config.channels.signal.phoneNumber = ''; - config.channels.signal.groupId = undefined; - config.channels.signal.groupName = undefined; - saveConfig(config); - - const { deleteSignalCliAccountData } = await import('./signal/setup.js'); - if (phoneNumberToDelete) { - deleteSignalCliAccountData(phoneNumberToDelete); - } - - console.log(chalk.green(' ✓ Signal data cleared.')); - - const setupNew = await ask(chalk.white(' Set up Signal with a new number now? (y/N): ')); - if (setupNew.toLowerCase() === 'y' || setupNew.toLowerCase() === 'yes') { - const newPhone = await ask(chalk.white(' New Signal phone number (e.g. +1234567890): ')); - if (newPhone) { - config.channels.signal.phoneNumber = newPhone.trim(); - config.channels.signal.enabled = true; - const modeAnswer = await ask(chalk.white(' Mode — group or private? [group]: ')); - config.channels.signal.mode = modeAnswer.toLowerCase().startsWith('private') ? 'private' : 'group'; - saveConfig(config); - } - } - } - } else if (isReconfig && signalPhoneInput.toLowerCase() === 'reset') { - clearSignalAccess(config); - config.channels.signal.enabled = false; - config.channels.signal.phoneNumber = ''; - saveConfig(config); - - console.log(''); - console.log(chalk.yellow(' ⚠️ This will clear all Signal access and group connection.')); - const deleteBinary = await ask(chalk.white(' Also delete the signal-cli binary? (y/N): ')); - if (deleteBinary.toLowerCase() === 'y' || deleteBinary.toLowerCase() === 'yes') { - const { removeSignalCli, getSignalCliDir } = await import('./signal/setup.js'); - const { existsSync } = await import('node:fs'); - if (existsSync(getSignalCliDir())) { - removeSignalCli(); - console.log(chalk.green(' ✓ Signal binary removed.')); - } + await runSignalSetup(config, isReconfig); + await completeInitialSignalSetup(config); } - - console.log(chalk.green(' ✓ Signal config reset.')); - console.log(chalk.dim(' Continue below to set up Signal with a new number.')); - console.log(''); - - const newPhone = await ask(chalk.white(' New Signal phone number (e.g. +1234567890): ')); - if (newPhone) { - config.channels.signal.phoneNumber = newPhone.trim(); - config.channels.signal.enabled = true; - - const modeAnswer = await ask(chalk.white(' Mode — group or private? [group]: ')); - config.channels.signal.mode = modeAnswer.toLowerCase().startsWith('private') ? 'private' : 'group'; - - saveConfig(config); - } - } else if (signalPhoneInput) { - if (signalPhoneInput !== config.channels.signal.phoneNumber) { - clearSignalAccess(config); - } - config.channels.signal.phoneNumber = signalPhoneInput.trim(); - config.channels.signal.enabled = true; - - const modeAnswer = await ask(chalk.white(' Mode — group or private? [group]: ')); - config.channels.signal.mode = modeAnswer.toLowerCase().startsWith('private') ? 'private' : 'group'; - - saveConfig(config); - } else if (!config.channels.signal.phoneNumber) { - config.channels.signal.enabled = false; - saveConfig(config); + } else { + await runSignalSetup(config, isReconfig); + await completeInitialSignalSetup(config); } - await completeInitialSignalSetup(config); - hr(); console.log(''); console.log(chalk.bold.white(' Discord (optional)')); From 533b8ccefbf256cc327577a43120083603592ae9 Mon Sep 17 00:00:00 2001 From: M Shehzen Date: Sun, 21 Jun 2026 11:47:48 +0530 Subject: [PATCH 4/8] feat(imessages): add iMessages channel via Photon Spectrum - Add ImessagesChannel with full Spectrum SDK integration (cloud mode) - Supports text, markdown, attachments, typing indicators - Auto-captures bot phone number from inbound space.phone - Caches Space objects from inbound messages for reliable reply routing - Echo suppression (sender.kind === 'agent') and LRU dedup - Static allowlist (IMESSAGES_ALLOWED_USERS) accepts phone numbers and Apple ID emails - Add IMessagesChannelConfig and IMessagesAccessUser types - Add channels.imessages config block with env defaults (IMESSAGES_*) - Register imessages in ChannelRegistry and barrel exports - Add iMessages onboarding step to configure/doctor flow - Fix send_file tool: add 'imessages' to channel enum - Fix sendFileHandler: route file sends to iMessages channel - Fix sendFile: add file existence/size checks and MIME type hints - Fix Spectrum API field mapping: sender.address (not .phone), content.markdown (not .text) - Fix timestamp: handle Date objects from Spectrum - Add spectrum-ts 5.1.0 dependency (pinned exact) --- .env.example | 18 + package-lock.json | 2477 ++++++++++++++++++++-- package.json | 1 + src/capabilities/filesystem/send-file.ts | 4 +- src/channels/imessages.ts | 402 ++++ src/channels/index.ts | 1 + src/channels/registry.ts | 7 + src/imessages/index.ts | 51 + src/index.ts | 119 ++ src/types/channel.ts | 20 +- src/utils/config.ts | 37 +- 11 files changed, 2958 insertions(+), 179 deletions(-) create mode 100644 src/channels/imessages.ts create mode 100644 src/imessages/index.ts diff --git a/.env.example b/.env.example index 84b5b612..183f6abc 100644 --- a/.env.example +++ b/.env.example @@ -119,6 +119,24 @@ WHATSAPP_PHONE_NUMBER= WHATSAPP_MODE=group WHATSAPP_GROUP_NAME=Mercury +# ── iMessages ── +# Uses Photon HQ's Spectrum SDK (cloud mode) — works on any OS, no Mac required +# 1. Sign up at https://app.photon.codes (free tier: unlimited messages, up to 10 users) +# 2. Create a project → add the iMessage provider → enable Cloud Mode +# 3. Copy Project ID and Project Secret from Settings → API Credentials +# Docs: https://docs.photon.codes/spectrum-ts/providers/imessage +IMESSAGES_ENABLED=false +IMESSAGES_PROJECT_ID= +IMESSAGES_PROJECT_SECRET= +# Comma-separated addresses: phone numbers with country code (e.g., +1234567890) or Apple ID emails +IMESSAGES_ALLOWED_USERS= +# Allow anyone to message Mercury via iMessage (default: false — only allowedUsers can interact) +IMESSAGES_ALLOW_ALL_USERS=false +# Render markdown in iMessage bubbles (default: true) +IMESSAGES_MARKDOWN=true +# Enable reaction support (default: false — opt-in) +IMESSAGES_REACTIONS=false + # ── Memory ── MEMORY_DIR=./memory diff --git a/package-lock.json b/package-lock.json index 8510ca8f..1d374f2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", "react": "^18.3.1", + "spectrum-ts": "5.1.0", "sql.js": "^1.14.1", "yaml": "^2.7.0", "zod": "^3.25.76" @@ -170,6 +171,12 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.0.tgz", + "integrity": "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@cacheable/memory": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.9.tgz", @@ -792,6 +799,192 @@ "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==" }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/@grpc/proto-loader/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/node_modules/protobufjs": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/proto-loader/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@grpc/proto-loader/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/@hapi/boom": { "version": "9.1.4", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", @@ -1380,6 +1573,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@keyv/bigmap": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", @@ -1410,67 +1613,526 @@ "node": ">=8.0.0" } }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" + "node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.8.0.tgz", + "integrity": "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.218.0.tgz", + "integrity": "sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/sdk-logs": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz", + "integrity": "sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "license": "BSD-3-Clause", + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", "dependencies": { - "@protobufjs/aspromise": "^1.1.1" + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", - "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", + "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@parseaple/bplist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@parseaple/bplist/-/bplist-1.0.1.tgz", + "integrity": "sha512-HHZKfvCaY8r5lwc6YCiAIhpAwH56YR29JPQa/9ZW0UqzjqdzFidGgoWVIhIF8tPXCwivIt93vbyT0O5bpC1Twg==", + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@parseaple/typedstream": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@parseaple/typedstream/-/typedstream-2.0.2.tgz", + "integrity": "sha512-bzKNZjiBtZN2T2U7Nqq/hhv+EezkPnaWoB5WvPuBwNpQvOr3EI0XrDTkFnAzsA9DKdu57es/gcb1d2ctd1u8xw==", + "license": "MIT", + "dependencies": { + "@parseaple/bplist": "1.0.1" + } + }, + "node_modules/@photon-ai/advanced-imessage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@photon-ai/advanced-imessage/-/advanced-imessage-0.11.2.tgz", + "integrity": "sha512-3mjzy1IIBtsCQK6kAB8dbFCK0np7hS256wwW+nqNL8vKz0W5nRhu1iKAwyZxP8Z470dtNX5RjNgcl9I4wZeuTA==", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.11.0", + "@grpc/grpc-js": "^1.12.6", + "nice-grpc": "^2.1.11", + "nice-grpc-common": "^2.0.2" + }, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@photon-ai/imessage-kit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@photon-ai/imessage-kit/-/imessage-kit-3.0.0.tgz", + "integrity": "sha512-GGDETlRbrPXP5eaRvswaead7MCBGQkm8f7d3aVbx8+rXhB4GEyPiw7Q1/yz5ix53rmxqKRevWywOfG7n+kZDBw==", + "license": "MIT", + "dependencies": { + "@parseaple/typedstream": "^2.0.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "better-sqlite3": "^12.5.0" + } + }, + "node_modules/@photon-ai/otel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@photon-ai/otel/-/otel-1.0.0.tgz", + "integrity": "sha512-5fACVHN7BtGS+phglVAxun5QLckNUSS4p750oCxR//m9JNVzFQMoLFWfC6DnWBP/W0/+B2yP75+3NMVLodEJEg==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api-logs": "^0.218.0", + "@opentelemetry/context-async-hooks": "^2.7.1", + "@opentelemetry/exporter-logs-otlp-http": "^0.218.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", + "@opentelemetry/resources": "^2.7.1", + "@opentelemetry/sdk-logs": "^0.218.0", + "@opentelemetry/sdk-trace-base": "^2.7.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "typescript": "^5 || ^6.0.0" + } + }, + "node_modules/@photon-ai/proto": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@photon-ai/proto/-/proto-0.2.4.tgz", + "integrity": "sha512-DQANEp0gHvtwqpMGEF0ufa0hs1nniRdsSuo0Q/TG6GoL1WjC5tp59tHFSLUeIm6fvnAuRjREsGzURz3+/69g7g==", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.12.0", + "nice-grpc-common": "^2.0.3" + } + }, + "node_modules/@photon-ai/slack": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@photon-ai/slack/-/slack-0.2.0.tgz", + "integrity": "sha512-k3XzGKIVS1EsjrPz9pbWvrvScmiy/iqKdA2npS5xu1CtCoycBkdFsqJKyB6iIyDR02Q3/38kQh7MvKsCzUyo3Q==", + "dependencies": { + "@bufbuild/protobuf": "^2.12.0", + "@grpc/grpc-js": "^1.14.3", + "nice-grpc": "^2.1.15", + "nice-grpc-common": "^2.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@photon-ai/telegram-ts": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@photon-ai/telegram-ts/-/telegram-ts-10.0.0.tgz", + "integrity": "sha512-kYGj/ieKOCG+OxoD1R69xHoT7zHl9dboF52LMPUl4FnorbwA8b2pid0uFoDYF55WIfoeo+VSqwlmY84GgpSedg==", + "license": "MIT", + "dependencies": { + "zod": "^4.4.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@photon-ai/telegram-ts/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@photon-ai/whatsapp-business": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@photon-ai/whatsapp-business/-/whatsapp-business-0.1.1.tgz", + "integrity": "sha512-JAf/gHh92+H6h/7AMOQ6l+WFYBWdeRH5fZ+tvOUYJJnX1C4aJPDFa23VTH4ndhKPgA5bK/h++cxUZx2lMubvYQ==", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "long": "^5.3.2", + "nice-grpc": "^2.1.15", + "nice-grpc-common": "^2.0.3", + "protobufjs": "^8.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@photon-ai/whatsapp-business/node_modules/protobufjs": { + "version": "8.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.4.tgz", + "integrity": "sha512-/+XMv9JalknuncEJSwsyEVlwcxVLKx2iaoSUXFZA86MJkdqyOdfrlB1sB7S6aKyUk9tl20YY+SgQe5J2sJHTcg==", + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.1", @@ -1478,6 +2140,12 @@ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, + "node_modules/@repeaterjs/repeater": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.1.0.tgz", + "integrity": "sha512-TaoVksZRSx2KWYYpyLQtMQXXeS98VsgZImzW65xmiVgbYhXLk+aEsmzPLirqVuE4/XuUapH2iMtxUzaBNDzdSQ==", + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -1861,86 +2529,288 @@ "@types/express": "^5.0.0" } }, - "node_modules/@slack/logger": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.1.tgz", - "integrity": "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==", + "node_modules/@slack/logger": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.1.tgz", + "integrity": "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==", + "license": "MIT", + "dependencies": { + "@types/node": ">=18" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/oauth": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-3.0.5.tgz", + "integrity": "sha512-exqFQySKhNDptWYSWhvRUJ4/+ndu2gayIy7vg/JfmJq3wGtGdHk531P96fAZyBm5c1Le3yaPYqv92rL4COlU3A==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.1", + "@slack/web-api": "^7.15.0", + "@types/jsonwebtoken": "^9", + "@types/node": ">=18", + "jsonwebtoken": "^9" + }, + "engines": { + "node": ">=18", + "npm": ">=8.6.0" + } + }, + "node_modules/@slack/socket-mode": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.7.tgz", + "integrity": "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.1", + "@slack/web-api": "^7.15.0", + "@types/node": ">=18", + "@types/ws": "^8", + "eventemitter3": "^5", + "ws": "^8" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.21.1.tgz", + "integrity": "sha512-I8vmSjNYWsaxuWPx6dz4yeh0h7vRBWbgAMK14LEmblbZ404BtrPbXs6jDPx4cYgGf8msDGF4A9opLZBu21FViQ==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.17.0.tgz", + "integrity": "sha512-jejr34a8B4L5AS713wOAx1LAqNkW16HVMDEa6sYBvFDc/llUBl8hXaiI4BwF+Al+Sug19Vn2O7iokTVIhVvZ1Q==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.1", + "@slack/types": "^2.21.0", + "@types/node": ">=18", + "@types/retry": "0.12.0", + "axios": "^1.16.0", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.4", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@spectrum-ts/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/core/-/core-5.1.0.tgz", + "integrity": "sha512-07ynd5YILbhxTLk5iGA3MJpMSt0rsl0ed5sSi4SRw65jXgzaRqY4aBIz2f6V+dGSyPKl8KTamMiw89jxgi2rSA==", + "license": "MIT", + "dependencies": { + "@photon-ai/otel": "^1.0.0", + "@photon-ai/proto": "^0.2.4", + "@repeaterjs/repeater": "^3.0.6", + "marked": "^18.0.5", + "mime-types": "^3.0.1", + "open-graph-scraper": "^6.11.0", + "vcf": "^2.1.2", + "zod": "^4.2.1" + }, + "peerDependencies": { + "elysia": "^1", + "express": "^4 || ^5", + "ffmpeg-static": "^5", + "hono": "^4", + "typescript": "^5 || ^6.0.0" + }, + "peerDependenciesMeta": { + "elysia": { + "optional": true + }, + "express": { + "optional": true + }, + "ffmpeg-static": { + "optional": true + }, + "hono": { + "optional": true + } + } + }, + "node_modules/@spectrum-ts/core/node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@spectrum-ts/core/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@spectrum-ts/imessage": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/imessage/-/imessage-5.1.0.tgz", + "integrity": "sha512-dsXVEjxqEHCffRK63gZKinoLebwrC0MawHfeVCOnuENrge6eJmm5+i3uv2B8G0RxlGImiQRUamk+kGOpK5v3NQ==", + "license": "MIT", + "dependencies": { + "@photon-ai/advanced-imessage": "^0.11.0", + "@photon-ai/imessage-kit": "^3.0.0", + "@photon-ai/otel": "^1.0.0", + "lru-cache": "^11.0.0", + "marked": "^18.0.5", + "zod": "^4.2.1" + }, + "peerDependencies": { + "@spectrum-ts/core": "^5.0.0", + "typescript": "^5 || ^6.0.0" + } + }, + "node_modules/@spectrum-ts/imessage/node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@spectrum-ts/imessage/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@spectrum-ts/slack": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/slack/-/slack-5.1.0.tgz", + "integrity": "sha512-uOLqeJssAthGwNTqhfre8Z5SBCoQK4DSiEylzPc3WiNAvIOV47RzuE5PYfQy4UWU461sL/CmESFXrrGZ7Efl1A==", + "license": "MIT", + "dependencies": { + "@photon-ai/slack": "^0.2.0", + "zod": "^4.2.1" + }, + "peerDependencies": { + "@spectrum-ts/core": "^5.0.0", + "typescript": "^5 || ^6.0.0" + } + }, + "node_modules/@spectrum-ts/slack/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@spectrum-ts/telegram": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/telegram/-/telegram-5.1.0.tgz", + "integrity": "sha512-+Y0xZt/6EYFSOGlEBMppUZY7QbhVrDzW8XUbss0zJzDtjcAO9M10nViwTaLWwfFR2OqviKRO9HoB3jKp2X40gQ==", "license": "MIT", "dependencies": { - "@types/node": ">=18" + "@photon-ai/telegram-ts": "10.0.0", + "marked": "^18.0.5", + "zod": "^4.2.1" }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" + "peerDependencies": { + "@spectrum-ts/core": "^5.0.0", + "typescript": "^5 || ^6.0.0" } }, - "node_modules/@slack/oauth": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-3.0.5.tgz", - "integrity": "sha512-exqFQySKhNDptWYSWhvRUJ4/+ndu2gayIy7vg/JfmJq3wGtGdHk531P96fAZyBm5c1Le3yaPYqv92rL4COlU3A==", + "node_modules/@spectrum-ts/telegram/node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", "license": "MIT", - "dependencies": { - "@slack/logger": "^4.0.1", - "@slack/web-api": "^7.15.0", - "@types/jsonwebtoken": "^9", - "@types/node": ">=18", - "jsonwebtoken": "^9" + "bin": { + "marked": "bin/marked.js" }, "engines": { - "node": ">=18", - "npm": ">=8.6.0" + "node": ">= 20" } }, - "node_modules/@slack/socket-mode": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.7.tgz", - "integrity": "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ==", + "node_modules/@spectrum-ts/telegram/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@spectrum-ts/terminal": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/terminal/-/terminal-5.1.0.tgz", + "integrity": "sha512-x3HsH2qAlU56cWYn/h9ALoGEDogHLYG9d5Xfc6lsNG1IE9A0rrsoz8ujjDlwNqwzJTMqYHrvbzIXEwzE2vyLiQ==", "license": "MIT", "dependencies": { - "@slack/logger": "^4.0.1", - "@slack/web-api": "^7.15.0", - "@types/node": ">=18", - "@types/ws": "^8", - "eventemitter3": "^5", - "ws": "^8" + "zod": "^4.2.1" }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" + "peerDependencies": { + "@spectrum-ts/core": "^5.0.0", + "typescript": "^5 || ^6.0.0" } }, - "node_modules/@slack/types": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.21.1.tgz", - "integrity": "sha512-I8vmSjNYWsaxuWPx6dz4yeh0h7vRBWbgAMK14LEmblbZ404BtrPbXs6jDPx4cYgGf8msDGF4A9opLZBu21FViQ==", + "node_modules/@spectrum-ts/terminal/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", - "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@slack/web-api": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.17.0.tgz", - "integrity": "sha512-jejr34a8B4L5AS713wOAx1LAqNkW16HVMDEa6sYBvFDc/llUBl8hXaiI4BwF+Al+Sug19Vn2O7iokTVIhVvZ1Q==", + "node_modules/@spectrum-ts/whatsapp-business": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/whatsapp-business/-/whatsapp-business-5.1.0.tgz", + "integrity": "sha512-EwUiCLuesGtL8o6I5iDeWNiKewNOHbNQy6zQngINfE7lCmTn9Yu7it/E0n5jIcui+8HUJrB+/dVnnNFkc5uQuQ==", "license": "MIT", "dependencies": { - "@slack/logger": "^4.0.1", - "@slack/types": "^2.21.0", - "@types/node": ">=18", - "@types/retry": "0.12.0", - "axios": "^1.16.0", - "eventemitter3": "^5.0.1", - "form-data": "^4.0.4", - "is-electron": "2.2.2", - "is-stream": "^2", - "p-queue": "^6", - "p-retry": "^4", - "retry": "^0.13.1" + "@photon-ai/whatsapp-business": "^0.1.1", + "mime-types": "^3.0.1", + "zod": "^4.2.1" }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" + "peerDependencies": { + "@spectrum-ts/core": "^5.0.0", + "typescript": "^5 || ^6.0.0" + } + }, + "node_modules/@spectrum-ts/whatsapp-business/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/@standard-schema/spec": { @@ -2446,6 +3316,12 @@ "node": ">=6.5" } }, + "node_modules/abort-controller-x": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.5.0.tgz", + "integrity": "sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==", + "license": "MIT" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -2696,6 +3572,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -2837,6 +3719,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "license": "MIT" + }, "node_modules/check-error": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", @@ -2846,6 +3734,57 @@ "node": ">= 16" } }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -3124,6 +4063,34 @@ "node": ">=6.6.0" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -3262,6 +4229,61 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -3316,6 +4338,31 @@ "node": ">= 0.8" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/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/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -3325,6 +4372,18 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -3433,6 +4492,15 @@ "@esbuild/win32-x64": "0.27.7" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3640,6 +4708,12 @@ "rollup": "^4.34.8" } }, + "node_modules/foldline": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/foldline/-/foldline-1.1.0.tgz", + "integrity": "sha512-9SheyADS50hjvFYjFJ3OB/GlDz2mD1T2CHd7auIk4Uto5YYWPBcw8iYo3F+gENJ+/SOeH9tT0loHZSqlUlumTA==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -3898,6 +4972,37 @@ "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", "license": "MIT" }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -4242,6 +5347,12 @@ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -4555,6 +5666,26 @@ "node": ">= 0.6" } }, + "node_modules/nice-grpc": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.16.tgz", + "integrity": "sha512-Cl3Pn00212Hl8/U6bpgMxmhZj5lyv3nWoJov4cd3FjWarktrMHP4DNvSjCnDwkMWYx4W1tyscEia4JX6Y4GVCQ==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.14.0", + "abort-controller-x": "^0.5.0", + "nice-grpc-common": "^2.0.3" + } + }, + "node_modules/nice-grpc-common": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.3.tgz", + "integrity": "sha512-MEhnD3JMah0mgyivpb9hpRDbOBuXBxI/TVO+OK1h6rC97WM42HsPMR+zzRNQ0C5BqYJTw1nyWiQRD0DucO+pjQ==", + "license": "MIT", + "dependencies": { + "ts-error": "^1.0.6" + } + }, "node_modules/node-abi": { "version": "3.92.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", @@ -4597,6 +5728,18 @@ } } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4708,6 +5851,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open-graph-scraper": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/open-graph-scraper/-/open-graph-scraper-6.11.0.tgz", + "integrity": "sha512-KkO3qMMzJj9KYGtCl19dRtncb+RuBiG/P9BgukcAG4p2w9wSAWTE90vL6/xqth1K9ThkYF/+xfTGrVvU79TJtQ==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "cheerio": "^1.1.2", + "iconv-lite": "^0.7.0", + "undici": "^7.16.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/open-graph-scraper/node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -4800,6 +5967,55 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5765,6 +6981,20 @@ "node": ">=0.10.0" } }, + "node_modules/spectrum-ts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/spectrum-ts/-/spectrum-ts-5.1.0.tgz", + "integrity": "sha512-rgRemlXhCZLgGce+zO7/OE+pF5omv2Jx9hL+YU8kdFGBEhTq0FkAhJD6W8H2eSqYuF3Xk4q5hBPh+XqmyY3mGA==", + "license": "MIT", + "dependencies": { + "@spectrum-ts/core": "5.1.0", + "@spectrum-ts/imessage": "5.1.0", + "@spectrum-ts/slack": "5.1.0", + "@spectrum-ts/telegram": "5.1.0", + "@spectrum-ts/terminal": "5.1.0", + "@spectrum-ts/whatsapp-business": "5.1.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -6087,6 +7317,12 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-error": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", + "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==", + "license": "MIT" + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -6224,7 +7460,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6298,6 +7533,16 @@ "node": ">= 0.8" } }, + "node_modules/vcf": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vcf/-/vcf-2.1.2.tgz", + "integrity": "sha512-oLYtZ+GJPjpKS950fw70+HavdP7ZO2Q+xMCMeCyiUKuXkJJJG1/wUjCKTagPryS1gApYjZOWW/khmdLsch8jxg==", + "license": "MIT", + "dependencies": { + "camelcase": "^5.0.0", + "foldline": "^1.1.0" + } + }, "node_modules/vite": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", @@ -6934,6 +8179,40 @@ "integrity": "sha512-yYO1qSs0Fe7tGtnxOFHomocUD6IZtoAgmA4oDFyGIRZ67D3QZk3w7swA6XXFXNQngiyrg2k7tul6IrM3eUFh7A==", "license": "MIT" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/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/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -7215,6 +8494,11 @@ "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==" }, + "@bufbuild/protobuf": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.0.tgz", + "integrity": "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==" + }, "@cacheable/memory": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.9.tgz", @@ -7536,6 +8820,131 @@ "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==" }, + "@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "requires": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + } + }, + "@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "requires": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "protobufjs": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } + } + }, "@hapi/boom": { "version": "9.1.4", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", @@ -7751,87 +9160,393 @@ "@emnapi/runtime": "^1.11.0" } }, - "@img/sharp-webcontainers-wasm32": { - "version": "0.35.1", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.1.tgz", - "integrity": "sha512-xU2ml2bU2OPxYVvW2A6ae4M1g5QKyhKG06P4FAt+YEaFQQO0919Qx+XxIZEUuWTMoDViLpMws2/dQwoe/VcA6A==", - "optional": true, - "peer": true, + "@img/sharp-webcontainers-wasm32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.1.tgz", + "integrity": "sha512-xU2ml2bU2OPxYVvW2A6ae4M1g5QKyhKG06P4FAt+YEaFQQO0919Qx+XxIZEUuWTMoDViLpMws2/dQwoe/VcA6A==", + "optional": true, + "peer": true, + "requires": { + "@img/sharp-wasm32": "0.35.1" + } + }, + "@img/sharp-win32-arm64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.1.tgz", + "integrity": "sha512-IkmHwuFhYpd3bTsN5SAahjwhiAcyXPooBt8vEUgxY3T0IP70sSJ0nU1xiPzZY8AH/OB1XpV3j8aZSVSOSfTbdA==", + "optional": true, + "peer": true + }, + "@img/sharp-win32-ia32": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.1.tgz", + "integrity": "sha512-wQahqCi9MD8Yxzg4gVM4fNrZxh+r6vD55PyIg+WJPaM5ZRUyF35iQpwJCuma3r6viU9/8Pxlc+XHV+woVa6nCQ==", + "optional": true, + "peer": true + }, + "@img/sharp-win32-x64": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.1.tgz", + "integrity": "sha512-WzBtkYtZHATLPe8XRharxZXxQ9cdLrQWHiwxt+BJ5rBsisQrKeeV86ErxPSVhcG6xCEuNhs0SqLpWr7XDa2k6w==", + "optional": true, + "peer": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@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 + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==" + }, + "@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "requires": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + } + }, + "@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==" + }, + "@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==" + }, + "@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/context-async-hooks": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.8.0.tgz", + "integrity": "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw==", + "requires": {} + }, + "@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/exporter-logs-otlp-http": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.218.0.tgz", + "integrity": "sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==", + "requires": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/sdk-logs": "0.218.0" + } + }, + "@opentelemetry/exporter-trace-otlp-http": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz", + "integrity": "sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "dependencies": { + "@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + } + }, + "@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "requires": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "dependencies": { + "@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/resources": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "requires": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "requires": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "dependencies": { + "@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "requires": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", + "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", "requires": { - "@img/sharp-wasm32": "0.35.1" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@img/sharp-win32-arm64": { - "version": "0.35.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.1.tgz", - "integrity": "sha512-IkmHwuFhYpd3bTsN5SAahjwhiAcyXPooBt8vEUgxY3T0IP70sSJ0nU1xiPzZY8AH/OB1XpV3j8aZSVSOSfTbdA==", - "optional": true, - "peer": true + "@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==" }, - "@img/sharp-win32-ia32": { - "version": "0.35.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.1.tgz", - "integrity": "sha512-wQahqCi9MD8Yxzg4gVM4fNrZxh+r6vD55PyIg+WJPaM5ZRUyF35iQpwJCuma3r6viU9/8Pxlc+XHV+woVa6nCQ==", - "optional": true, - "peer": true + "@parseaple/bplist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@parseaple/bplist/-/bplist-1.0.1.tgz", + "integrity": "sha512-HHZKfvCaY8r5lwc6YCiAIhpAwH56YR29JPQa/9ZW0UqzjqdzFidGgoWVIhIF8tPXCwivIt93vbyT0O5bpC1Twg==" }, - "@img/sharp-win32-x64": { - "version": "0.35.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.1.tgz", - "integrity": "sha512-WzBtkYtZHATLPe8XRharxZXxQ9cdLrQWHiwxt+BJ5rBsisQrKeeV86ErxPSVhcG6xCEuNhs0SqLpWr7XDa2k6w==", - "optional": true, - "peer": true + "@parseaple/typedstream": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@parseaple/typedstream/-/typedstream-2.0.2.tgz", + "integrity": "sha512-bzKNZjiBtZN2T2U7Nqq/hhv+EezkPnaWoB5WvPuBwNpQvOr3EI0XrDTkFnAzsA9DKdu57es/gcb1d2ctd1u8xw==", + "requires": { + "@parseaple/bplist": "1.0.1" + } }, - "@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, + "@photon-ai/advanced-imessage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@photon-ai/advanced-imessage/-/advanced-imessage-0.11.2.tgz", + "integrity": "sha512-3mjzy1IIBtsCQK6kAB8dbFCK0np7hS256wwW+nqNL8vKz0W5nRhu1iKAwyZxP8Z470dtNX5RjNgcl9I4wZeuTA==", "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@bufbuild/protobuf": "^2.11.0", + "@grpc/grpc-js": "^1.12.6", + "nice-grpc": "^2.1.11", + "nice-grpc-common": "^2.0.2" } }, - "@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 + "@photon-ai/imessage-kit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@photon-ai/imessage-kit/-/imessage-kit-3.0.0.tgz", + "integrity": "sha512-GGDETlRbrPXP5eaRvswaead7MCBGQkm8f7d3aVbx8+rXhB4GEyPiw7Q1/yz5ix53rmxqKRevWywOfG7n+kZDBw==", + "requires": { + "@parseaple/typedstream": "^2.0.0", + "better-sqlite3": "^12.5.0" + } }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true + "@photon-ai/otel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@photon-ai/otel/-/otel-1.0.0.tgz", + "integrity": "sha512-5fACVHN7BtGS+phglVAxun5QLckNUSS4p750oCxR//m9JNVzFQMoLFWfC6DnWBP/W0/+B2yP75+3NMVLodEJEg==", + "requires": { + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api-logs": "^0.218.0", + "@opentelemetry/context-async-hooks": "^2.7.1", + "@opentelemetry/exporter-logs-otlp-http": "^0.218.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", + "@opentelemetry/resources": "^2.7.1", + "@opentelemetry/sdk-logs": "^0.218.0", + "@opentelemetry/sdk-trace-base": "^2.7.1" + } }, - "@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "@photon-ai/proto": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@photon-ai/proto/-/proto-0.2.4.tgz", + "integrity": "sha512-DQANEp0gHvtwqpMGEF0ufa0hs1nniRdsSuo0Q/TG6GoL1WjC5tp59tHFSLUeIm6fvnAuRjREsGzURz3+/69g7g==", "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@bufbuild/protobuf": "^2.12.0", + "nice-grpc-common": "^2.0.3" } }, - "@keyv/bigmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", - "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "@photon-ai/slack": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@photon-ai/slack/-/slack-0.2.0.tgz", + "integrity": "sha512-k3XzGKIVS1EsjrPz9pbWvrvScmiy/iqKdA2npS5xu1CtCoycBkdFsqJKyB6iIyDR02Q3/38kQh7MvKsCzUyo3Q==", "requires": { - "hashery": "^1.4.0", - "hookified": "^1.15.0" + "@bufbuild/protobuf": "^2.12.0", + "@grpc/grpc-js": "^1.14.3", + "nice-grpc": "^2.1.15", + "nice-grpc-common": "^2.0.3" } }, - "@keyv/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==" + "@photon-ai/telegram-ts": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@photon-ai/telegram-ts/-/telegram-ts-10.0.0.tgz", + "integrity": "sha512-kYGj/ieKOCG+OxoD1R69xHoT7zHl9dboF52LMPUl4FnorbwA8b2pid0uFoDYF55WIfoeo+VSqwlmY84GgpSedg==", + "requires": { + "zod": "^4.4.3" + }, + "dependencies": { + "zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + } + } }, - "@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==" + "@photon-ai/whatsapp-business": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@photon-ai/whatsapp-business/-/whatsapp-business-0.1.1.tgz", + "integrity": "sha512-JAf/gHh92+H6h/7AMOQ6l+WFYBWdeRH5fZ+tvOUYJJnX1C4aJPDFa23VTH4ndhKPgA5bK/h++cxUZx2lMubvYQ==", + "requires": { + "@grpc/grpc-js": "^1.14.3", + "long": "^5.3.2", + "nice-grpc": "^2.1.15", + "nice-grpc-common": "^2.0.3", + "protobufjs": "^8.0.1" + }, + "dependencies": { + "protobufjs": { + "version": "8.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.4.tgz", + "integrity": "sha512-/+XMv9JalknuncEJSwsyEVlwcxVLKx2iaoSUXFZA86MJkdqyOdfrlB1sB7S6aKyUk9tl20YY+SgQe5J2sJHTcg==", + "requires": { + "long": "^5.3.2" + } + } + } }, "@pinojs/redact": { "version": "0.4.0", @@ -7891,6 +9606,11 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==" }, + "@repeaterjs/repeater": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.1.0.tgz", + "integrity": "sha512-TaoVksZRSx2KWYYpyLQtMQXXeS98VsgZImzW65xmiVgbYhXLk+aEsmzPLirqVuE4/XuUapH2iMtxUzaBNDzdSQ==" + }, "@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -8159,6 +9879,128 @@ "retry": "^0.13.1" } }, + "@spectrum-ts/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/core/-/core-5.1.0.tgz", + "integrity": "sha512-07ynd5YILbhxTLk5iGA3MJpMSt0rsl0ed5sSi4SRw65jXgzaRqY4aBIz2f6V+dGSyPKl8KTamMiw89jxgi2rSA==", + "requires": { + "@photon-ai/otel": "^1.0.0", + "@photon-ai/proto": "^0.2.4", + "@repeaterjs/repeater": "^3.0.6", + "marked": "^18.0.5", + "mime-types": "^3.0.1", + "open-graph-scraper": "^6.11.0", + "vcf": "^2.1.2", + "zod": "^4.2.1" + }, + "dependencies": { + "marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==" + }, + "zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + } + } + }, + "@spectrum-ts/imessage": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/imessage/-/imessage-5.1.0.tgz", + "integrity": "sha512-dsXVEjxqEHCffRK63gZKinoLebwrC0MawHfeVCOnuENrge6eJmm5+i3uv2B8G0RxlGImiQRUamk+kGOpK5v3NQ==", + "requires": { + "@photon-ai/advanced-imessage": "^0.11.0", + "@photon-ai/imessage-kit": "^3.0.0", + "@photon-ai/otel": "^1.0.0", + "lru-cache": "^11.0.0", + "marked": "^18.0.5", + "zod": "^4.2.1" + }, + "dependencies": { + "marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==" + }, + "zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + } + } + }, + "@spectrum-ts/slack": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/slack/-/slack-5.1.0.tgz", + "integrity": "sha512-uOLqeJssAthGwNTqhfre8Z5SBCoQK4DSiEylzPc3WiNAvIOV47RzuE5PYfQy4UWU461sL/CmESFXrrGZ7Efl1A==", + "requires": { + "@photon-ai/slack": "^0.2.0", + "zod": "^4.2.1" + }, + "dependencies": { + "zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + } + } + }, + "@spectrum-ts/telegram": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/telegram/-/telegram-5.1.0.tgz", + "integrity": "sha512-+Y0xZt/6EYFSOGlEBMppUZY7QbhVrDzW8XUbss0zJzDtjcAO9M10nViwTaLWwfFR2OqviKRO9HoB3jKp2X40gQ==", + "requires": { + "@photon-ai/telegram-ts": "10.0.0", + "marked": "^18.0.5", + "zod": "^4.2.1" + }, + "dependencies": { + "marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==" + }, + "zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + } + } + }, + "@spectrum-ts/terminal": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/terminal/-/terminal-5.1.0.tgz", + "integrity": "sha512-x3HsH2qAlU56cWYn/h9ALoGEDogHLYG9d5Xfc6lsNG1IE9A0rrsoz8ujjDlwNqwzJTMqYHrvbzIXEwzE2vyLiQ==", + "requires": { + "zod": "^4.2.1" + }, + "dependencies": { + "zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + } + } + }, + "@spectrum-ts/whatsapp-business": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-ts/whatsapp-business/-/whatsapp-business-5.1.0.tgz", + "integrity": "sha512-EwUiCLuesGtL8o6I5iDeWNiKewNOHbNQy6zQngINfE7lCmTn9Yu7it/E0n5jIcui+8HUJrB+/dVnnNFkc5uQuQ==", + "requires": { + "@photon-ai/whatsapp-business": "^0.1.1", + "mime-types": "^3.0.1", + "zod": "^4.2.1" + }, + "dependencies": { + "zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + } + } + }, "@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -8548,6 +10390,11 @@ "event-target-shim": "^5.0.0" } }, + "abort-controller-x": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.5.0.tgz", + "integrity": "sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==" + }, "accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -8710,6 +10557,11 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -8798,12 +10650,55 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==" }, + "chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==" + }, "check-error": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true }, + "cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "dependencies": { + "undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==" + } + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -8986,6 +10881,23 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" }, + "css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==" + }, "csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -9076,6 +10988,39 @@ "undici": "6.24.1" } }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, "dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -9114,6 +11059,25 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, + "encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "requires": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "dependencies": { + "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==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -9123,6 +11087,11 @@ "once": "^1.4.0" } }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, "environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -9202,6 +11171,11 @@ "@esbuild/win32-x64": "0.27.7" } }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -9350,6 +11324,11 @@ "rollup": "^4.34.8" } }, + "foldline": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/foldline/-/foldline-1.1.0.tgz", + "integrity": "sha512-9SheyADS50hjvFYjFJ3OB/GlDz2mD1T2CHd7auIk4Uto5YYWPBcw8iYo3F+gENJ+/SOeH9tT0loHZSqlUlumTA==" + }, "follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -9507,6 +11486,24 @@ "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==" }, + "htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + }, + "dependencies": { + "entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==" + } + } + }, "http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -9738,6 +11735,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -9945,6 +11947,24 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" }, + "nice-grpc": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.16.tgz", + "integrity": "sha512-Cl3Pn00212Hl8/U6bpgMxmhZj5lyv3nWoJov4cd3FjWarktrMHP4DNvSjCnDwkMWYx4W1tyscEia4JX6Y4GVCQ==", + "requires": { + "@grpc/grpc-js": "^1.14.0", + "abort-controller-x": "^0.5.0", + "nice-grpc-common": "^2.0.3" + } + }, + "nice-grpc-common": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.3.tgz", + "integrity": "sha512-MEhnD3JMah0mgyivpb9hpRDbOBuXBxI/TVO+OK1h6rC97WM42HsPMR+zzRNQ0C5BqYJTw1nyWiQRD0DucO+pjQ==", + "requires": { + "ts-error": "^1.0.6" + } + }, "node-abi": { "version": "3.92.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", @@ -9970,6 +11990,14 @@ "whatwg-url": "^5.0.0" } }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10040,6 +12068,24 @@ "mimic-fn": "^2.1.0" } }, + "open-graph-scraper": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/open-graph-scraper/-/open-graph-scraper-6.11.0.tgz", + "integrity": "sha512-KkO3qMMzJj9KYGtCl19dRtncb+RuBiG/P9BgukcAG4p2w9wSAWTE90vL6/xqth1K9ThkYF/+xfTGrVvU79TJtQ==", + "requires": { + "chardet": "^2.1.1", + "cheerio": "^1.1.2", + "iconv-lite": "^0.7.0", + "undici": "^7.16.0" + }, + "dependencies": { + "undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==" + } + } + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -10099,6 +12145,38 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "requires": { + "entities": "^6.0.0" + }, + "dependencies": { + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" + } + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "requires": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + } + }, + "parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "requires": { + "parse5": "^7.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -10720,6 +12798,19 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, + "spectrum-ts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/spectrum-ts/-/spectrum-ts-5.1.0.tgz", + "integrity": "sha512-rgRemlXhCZLgGce+zO7/OE+pF5omv2Jx9hL+YU8kdFGBEhTq0FkAhJD6W8H2eSqYuF3Xk4q5hBPh+XqmyY3mGA==", + "requires": { + "@spectrum-ts/core": "5.1.0", + "@spectrum-ts/imessage": "5.1.0", + "@spectrum-ts/slack": "5.1.0", + "@spectrum-ts/telegram": "5.1.0", + "@spectrum-ts/terminal": "5.1.0", + "@spectrum-ts/whatsapp-business": "5.1.0" + } + }, "split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -10960,6 +13051,11 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, + "ts-error": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", + "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==" + }, "ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -11040,8 +13136,7 @@ "typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==" }, "ufo": { "version": "1.6.4", @@ -11085,6 +13180,15 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, + "vcf": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vcf/-/vcf-2.1.2.tgz", + "integrity": "sha512-oLYtZ+GJPjpKS950fw70+HavdP7ZO2Q+xMCMeCyiUKuXkJJJG1/wUjCKTagPryS1gApYjZOWW/khmdLsch8jxg==", + "requires": { + "camelcase": "^5.0.0", + "foldline": "^1.1.0" + } + }, "vite": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", @@ -11372,6 +13476,29 @@ "resolved": "https://registry.npmjs.org/whatsapp-rust-bridge/-/whatsapp-rust-bridge-0.5.4.tgz", "integrity": "sha512-yYO1qSs0Fe7tGtnxOFHomocUD6IZtoAgmA4oDFyGIRZ67D3QZk3w7swA6XXFXNQngiyrg2k7tul6IrM3eUFh7A==" }, + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "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==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" + }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/package.json b/package.json index 5ae65faf..e5fc8fb0 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", "react": "^18.3.1", + "spectrum-ts": "5.1.0", "sql.js": "^1.14.1", "yaml": "^2.7.0", "zod": "^3.25.76" diff --git a/src/capabilities/filesystem/send-file.ts b/src/capabilities/filesystem/send-file.ts index 1bdf4581..a13e362f 100644 --- a/src/capabilities/filesystem/send-file.ts +++ b/src/capabilities/filesystem/send-file.ts @@ -11,10 +11,10 @@ export function createSendFileTool( ) { return tool({ description: - 'Send a file to the user. Use the channel parameter to send via a specific channel (signal, telegram, whatsapp). If omitted, sends via the current channel or the best available channel.', + 'Send a file to the user. Use the channel parameter to send via a specific channel (imessages, signal, telegram, whatsapp). If omitted, sends via the current channel or the best available channel.', inputSchema: zodSchema(z.object({ path: z.string().describe('Absolute or relative path to the file to send'), - channel: z.enum(['signal', 'telegram', 'whatsapp']).optional().describe('The channel to send the file via. Use this when the user explicitly requests a specific channel, e.g. "send via Signal" or "send to Telegram" or "send to WhatsApp".'), + channel: z.enum(['imessages', 'signal', 'telegram', 'whatsapp']).optional().describe('The channel to send the file via. Use this when the user explicitly requests a specific channel, e.g. "send via iMessage" or "send to Signal" or "send to Telegram" or "send to WhatsApp".'), })), execute: async ({ path, channel }) => { const resolved = isAbsolute(path) ? resolve(path) : resolve(getCwd(), path); diff --git a/src/channels/imessages.ts b/src/channels/imessages.ts new file mode 100644 index 00000000..422d61f0 --- /dev/null +++ b/src/channels/imessages.ts @@ -0,0 +1,402 @@ +import { existsSync, statSync } from 'node:fs'; +import { resolve, basename, extname } from 'node:path'; +import { BaseChannel } from './base.js'; +import type { ChannelMessage } from '../types/channel.js'; +import type { MercuryConfig } from '../utils/config.js'; +import { isIMessagesAllowed } from '../utils/config.js'; +import { logger } from '../utils/logger.js'; +import { createImessagesApp, getContentBuilders, getImessagesProvider } from '../imessages/index.js'; + +const MAX_MESSAGE_LENGTH = 4000; +const DEDUP_MAX_SIZE = 4000; +const DEDUP_WINDOW_MS = 48 * 3600 * 1000; + +type DedupEntry = { id: string; ts: number }; + +function stripMarkdown(text: string): string { + return text + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/\*(.+?)\*/g, '$1') + .replace(/__(.+?)__/g, '$1') + .replace(/_(.+?)_/g, '$1') + .replace(/~~(.+?)~~/g, '$1') + .replace(/`{3}[\s\S]*?`{3}/g, (m) => m.replace(/`{3}/g, '').trim()) + .replace(/`(.+?)`/g, '$1') + .replace(/^#{1,6}\s+/gm, '') + .replace(/^[-*+]\s+/gm, '') + .replace(/^\d+\.\s+/gm, '') + .replace(/^>\s+/gm, '') + .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') + .replace(/\n{3,}/g, '\n\n') + .trim(); +} + +export class ImessagesChannel extends BaseChannel { + readonly type = 'imessages' as const; + + private app: any = null; + private config: MercuryConfig; + private dedupSet: DedupEntry[] = []; + private messageLoop: AbortController | null = null; + private spaceCache = new Map(); + private botPhoneNumber: string | null = null; + + constructor(config: MercuryConfig) { + super(); + this.config = config; + } + + async start(): Promise { + const { projectId, projectSecret } = this.config.channels.imessages; + if (!projectId || !projectSecret) { + logger.error('iMessages: IMESSAGES_PROJECT_ID and IMESSAGES_PROJECT_SECRET are required'); + return; + } + + try { + this.app = await createImessagesApp({ projectId, projectSecret }); + this.ready = true; + logger.info({ channel: 'imessages' }, 'iMessages channel connected via Photon Spectrum'); + + this.startMessageLoop(); + } catch (err) { + logger.error({ err, channel: 'imessages' }, 'iMessages: failed to start'); + throw err; + } + } + + private startMessageLoop(): void { + if (!this.app) return; + this.messageLoop = new AbortController(); + + const drain = async () => { + try { + for await (const [space, message] of this.app.messages) { + if (this.messageLoop?.signal.aborted) break; + this.handleInbound(space, message); + } + } catch (err: any) { + if (this.messageLoop?.signal.aborted) return; + logger.error({ err, channel: 'imessages' }, 'iMessages: message stream error, will reconnect'); + } + }; + + drain(); + } + + private handleInbound(space: any, message: any): void { + if (!message) return; + + const sender = message.sender; + const senderId = sender?.address || sender?.id || 'unknown'; + const senderName = sender?.name || sender?.address || undefined; + + if (sender?.kind === 'agent') { + logger.debug({ channel: 'imessages', messageId: message.id }, 'iMessages: echo suppression — skipping own outbound'); + return; + } + + if (!this.config.channels.imessages.allowAllUsers) { + if (!isIMessagesAllowed(this.config, senderId)) { + logger.info({ channel: 'imessages', senderId }, 'iMessages: message from unallowed user, ignoring'); + return; + } + } + + if (message.id && this.isDuplicate(message.id)) { + return; + } + + const content = this.extractContent(message); + if (!content) { + logger.debug({ channel: 'imessages', messageId: message.id, contentType: message.content?.type }, 'iMessages: could not extract text content, skipping'); + return; + } + + const spaceId = space?.id || message.space?.id || senderId; + + this.spaceCache.set(spaceId, space); + + if (space?.phone && space.phone !== 'shared' && !this.botPhoneNumber) { + this.botPhoneNumber = space.phone; + logger.info({ channel: 'imessages', botPhone: this.botPhoneNumber }, 'iMessages: bot phone number auto-captured from first inbound space'); + } + + const channelId = `imessages:${spaceId}`; + + const channelMessage: ChannelMessage = { + id: message.id || `imsg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + channelId, + channelType: 'imessages', + senderId, + senderName, + content, + timestamp: message.timestamp instanceof Date ? message.timestamp.getTime() : (message.timestamp || Date.now()), + metadata: { + spaceId, + platform: message.platform, + ...this.extractMediaMetadata(message), + }, + }; + + logger.info({ channel: 'imessages', from: senderId, spaceId, contentPreview: content.slice(0, 50) }, 'iMessages: inbound message processed'); + this.emit(channelMessage); + } + + private extractContent(message: any): string | null { + const content = message.content; + if (!content) return null; + + if (typeof content === 'string') return content; + + if (content.type === 'text' && content.text) return content.text; + + if (content.type === 'markdown') { + if (content.markdown) return content.markdown; + if (content.text) return content.text; + } + + if (content.type === 'reply' && content.content) { + const inner = this.extractContent({ content: content.content }); + if (inner) return inner; + } + + if (content.type === 'group' && Array.isArray(content.items)) { + const parts: string[] = []; + for (const item of content.items) { + const extracted = this.extractContent({ content: item }); + if (extracted) parts.push(extracted); + } + return parts.join(' ') || null; + } + + if (Array.isArray(content)) { + const parts: string[] = []; + for (const part of content) { + if (typeof part === 'string') parts.push(part); + else if (part?.type === 'text' && part.text) parts.push(part.text); + else if (part?.type === 'markdown' && (part.markdown || part.text)) parts.push(part.markdown || part.text); + } + return parts.join(' ') || null; + } + + if (content.text) return content.text; + + return null; + } + + private extractMediaMetadata(message: any): Record { + const meta: Record = {}; + const content = message.content; + + if (!content) return meta; + + const items = Array.isArray(content) ? content : [content]; + const mediaTypes: string[] = []; + + for (const item of items) { + if (item?.type === 'attachment' || item?.type === 'voice') { + if (item.name) meta.attachmentName = item.name; + if (item.mimeType) mediaTypes.push(item.mimeType); + if (item.id) meta.attachmentId = item.id; + if (item.size) meta.attachmentSize = item.size; + if (item.type === 'voice' && item.duration) meta.voiceDuration = item.duration; + } + } + + if (mediaTypes.length > 0) meta.mediaTypes = mediaTypes; + + return meta; + } + + private isDuplicate(id: string): boolean { + const now = Date.now(); + this.dedupSet = this.dedupSet.filter((e) => now - e.ts < DEDUP_WINDOW_MS); + if (this.dedupSet.some((e) => e.id === id)) return true; + this.dedupSet.push({ id, ts: now }); + if (this.dedupSet.length > DEDUP_MAX_SIZE) { + this.dedupSet = this.dedupSet.slice(-DEDUP_MAX_SIZE); + } + return false; + } + + getBotPhoneNumber(): string | null { + return this.botPhoneNumber; + } + + async send(content: string, targetId?: string, elapsedMs?: number): Promise { + if (!this.app) throw new Error('iMessages channel not started'); + const builders = getContentBuilders(); + const useMarkdown = this.config.channels.imessages.markdown; + + const text = useMarkdown ? content : stripMarkdown(content); + const chunks = this.chunkText(text, MAX_MESSAGE_LENGTH); + + const space = await this.resolveSpace(targetId); + if (!space) { + logger.warn({ channel: 'imessages', targetId }, 'iMessages: could not resolve space for send'); + return; + } + + for (const chunk of chunks) { + const contentObj = useMarkdown ? builders.markdown(chunk) : builders.text(chunk); + await space.send(contentObj); + } + } + + async sendFile(filePath: string, targetId?: string): Promise { + if (!this.app) throw new Error('iMessages channel not started'); + + const resolved = resolve(filePath); + if (!existsSync(resolved)) { + throw new Error(`File not found: ${resolved}`); + } + const stat = statSync(resolved); + if (stat.isDirectory()) { + throw new Error(`${resolved} is a directory, not a file`); + } + if (stat.size > this.config.channels.imessages.maxInlineAttachmentBytes) { + throw new Error(`File too large (${Math.round(stat.size / (1024 * 1024))}MB). Maximum is ${Math.round(this.config.channels.imessages.maxInlineAttachmentBytes / (1024 * 1024))}MB`); + } + + const { attachment } = getContentBuilders(); + const ext = extname(resolved).toLowerCase(); + const mimeMap: Record = { + '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', + '.webp': 'image/webp', '.pdf': 'application/pdf', '.txt': 'text/plain', + '.mp3': 'audio/mpeg', '.m4a': 'audio/mp4', '.wav': 'audio/wav', + '.mp4': 'video/mp4', '.mov': 'video/quicktime', + }; + const mimeType = mimeMap[ext]; + const filename = basename(resolved); + const attachOpts: { name?: string; mimeType?: string } = {}; + if (mimeType) attachOpts.mimeType = mimeType; + if (filename !== extname(resolved)) attachOpts.name = filename; + + const space = await this.resolveSpace(targetId); + if (!space) { + logger.warn({ channel: 'imessages', targetId }, 'iMessages: could not resolve space for sendFile'); + return; + } + + await space.send(attachment(resolved, attachOpts)); + } + + async stream(content: AsyncIterable, targetId?: string): Promise { + let full = ''; + for await (const chunk of content) { + full += chunk; + } + await this.send(full, targetId); + return full; + } + + async typing(targetId?: string): Promise { + if (!this.app) return; + const { typing } = getContentBuilders(); + + try { + const space = await this.resolveSpace(targetId); + if (space) { + await space.startTyping(); + } + } catch { + // typing indicator failures are non-critical + } + } + + async askToContinue(question: string, targetId?: string): Promise { + await this.send(question, targetId); + return true; + } + + async stop(): Promise { + this.messageLoop?.abort(); + this.messageLoop = null; + this.spaceCache.clear(); + + if (this.app) { + try { + await this.app.stop(); + } catch (err) { + logger.warn({ err, channel: 'imessages' }, 'iMessages: error during stop'); + } + this.app = null; + } + + this.ready = false; + logger.info({ channel: 'imessages' }, 'iMessages channel stopped'); + } + + private async resolveSpace(targetId?: string): Promise { + if (!this.app) return null; + + const bareId = targetId?.startsWith('imessages:') ? targetId.slice('imessages:'.length) : targetId; + + if (bareId && this.spaceCache.has(bareId)) { + return this.spaceCache.get(bareId); + } + + if (bareId && bareId !== 'notification') { + try { + const narrowed = getImessagesProvider()(this.app); + const existing = await narrowed.space.get(bareId); + if (existing) { + this.spaceCache.set(bareId, existing); + return existing; + } + } catch { + // fall through to create + } + + try { + const narrowed = getImessagesProvider()(this.app); + const user = await narrowed.user(bareId); + const space = await narrowed.space.create(user); + this.spaceCache.set(bareId, space); + return space; + } catch (err) { + logger.debug({ err, targetId: bareId, channel: 'imessages' }, 'iMessages: failed to resolve/create space'); + return null; + } + } + + if (bareId === 'notification' || !bareId) { + if (this.spaceCache.size > 0) { + const firstSpace = this.spaceCache.values().next().value; + if (firstSpace) return firstSpace; + } + + const allowedUsers = this.config.channels.imessages.allowedUsers; + if (allowedUsers.length > 0) { + return this.resolveSpace(allowedUsers[0]); + } + + return null; + } + + return null; + } + + private chunkText(text: string, maxLength: number): string[] { + if (text.length <= maxLength) return [text]; + + const chunks: string[] = []; + let remaining = text; + while (remaining.length > 0) { + if (remaining.length <= maxLength) { + chunks.push(remaining); + break; + } + + let splitAt = remaining.lastIndexOf('\n', maxLength); + if (splitAt === -1 || splitAt > maxLength) splitAt = remaining.lastIndexOf(' ', maxLength); + if (splitAt === -1 || splitAt > maxLength) splitAt = maxLength; + + chunks.push(remaining.slice(0, splitAt)); + remaining = remaining.slice(splitAt + (remaining[splitAt] === '\n' || remaining[splitAt] === ' ' ? 1 : 0)); + } + + return chunks; + } +} \ No newline at end of file diff --git a/src/channels/index.ts b/src/channels/index.ts index d0b6b5b4..474b04a0 100644 --- a/src/channels/index.ts +++ b/src/channels/index.ts @@ -7,4 +7,5 @@ export { WebChannel } from './web.js'; export { DiscordChannel } from './discord.js'; export { SlackChannel } from './slack.js'; export { WhatsAppChannel } from './whatsapp.js'; +export { ImessagesChannel } from './imessages.js'; export { ChannelRegistry } from './registry.js'; \ No newline at end of file diff --git a/src/channels/registry.ts b/src/channels/registry.ts index 6d54e30f..465f7d56 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -6,6 +6,7 @@ import { SignalChannel } from './signal.js'; import { DiscordChannel } from './discord.js'; import { SlackChannel } from './slack.js'; import { WhatsAppChannel } from './whatsapp.js'; +import { ImessagesChannel } from './imessages.js'; import type { MercuryConfig } from '../utils/config.js'; import { logger } from '../utils/logger.js'; @@ -34,6 +35,10 @@ export class ChannelRegistry { if (config.channels.whatsapp.enabled && config.channels.whatsapp.phoneNumber) { this.register('whatsapp', new WhatsAppChannel(config)); } + + if (config.channels.imessages.enabled && config.channels.imessages.projectId) { + this.register('imessages', new ImessagesChannel(config)); + } } getCliChannel(): CLIChannel | undefined { @@ -97,6 +102,8 @@ export class ChannelRegistry { if (slack?.isReady()) return slack; const whatsapp = this.channels.get('whatsapp'); if (whatsapp?.isReady()) return whatsapp; + const imessages = this.channels.get('imessages'); + if (imessages?.isReady()) return imessages; const cli = this.channels.get('cli'); if (cli?.isReady()) return cli; return this.channels.values().next().value; diff --git a/src/imessages/index.ts b/src/imessages/index.ts new file mode 100644 index 00000000..d8b5e2ea --- /dev/null +++ b/src/imessages/index.ts @@ -0,0 +1,51 @@ +import { logger } from '../utils/logger.js'; + +export type IMessagesConfig = { + projectId: string; + projectSecret: string; +}; + +let spectrumMod: any = null; +let imessageMod: any = null; + +async function ensureImports(): Promise { + if (spectrumMod) return; + try { + spectrumMod = await import('spectrum-ts'); + imessageMod = await import('spectrum-ts/providers/imessage'); + } catch (err) { + logger.error({ err }, 'iMessages: failed to import spectrum-ts. Install it with: npm install spectrum-ts'); + throw err; + } +} + +export async function createImessagesApp(config: IMessagesConfig): Promise { + await ensureImports(); + const app = await spectrumMod.Spectrum({ + projectId: config.projectId, + projectSecret: config.projectSecret, + providers: [imessageMod.imessage.config()], + }); + return app; +} + +export function getContentBuilders(): Record any> { + if (!spectrumMod) throw new Error('spectrum-ts not loaded — call createImessagesApp first'); + return { + text: spectrumMod.text, + markdown: spectrumMod.markdown, + attachment: spectrumMod.attachment, + voice: spectrumMod.voice, + typing: spectrumMod.typing, + read: spectrumMod.read, + reply: spectrumMod.reply, + edit: spectrumMod.edit, + reaction: spectrumMod.reaction, + unsend: spectrumMod.unsend, + }; +} + +export function getImessagesProvider(): any { + if (!imessageMod) throw new Error('spectrum-ts not loaded — call createImessagesApp first'); + return imessageMod.imessage; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 3b190218..bfa19d05 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,6 +49,8 @@ import { setWhatsAppAdmin, clearWhatsAppAccess, isWhatsAppPaired, + isIMessagesAllowed, + getIMessagesAccessSummary, } from './utils/config.js'; import type { MercuryConfig } from './utils/config.js'; import type { ProviderName } from './utils/config.js'; @@ -2157,6 +2159,108 @@ async function configure(existingConfig?: MercuryConfig): Promise { } } + // ── iMessages ────────────────────────────────────────────────────── + hr(); + console.log(''); + console.log(chalk.bold.white(' iMessages (optional)')); + console.log(chalk.dim(' Chat with Mercury through iMessage via Photon Spectrum (cloud mode).')); + console.log(chalk.dim(' Works on any OS — no Mac required. Auto-discovers the bot number.')); + console.log(chalk.dim(' You can add it later with mercury doctor.')); + console.log(''); + + const im = config.channels.imessages; + const imConfigured = im.enabled && !!im.projectId && !!im.projectSecret; + + if (isReconfig && imConfigured) { + const summary = getIMessagesAccessSummary(config); + console.log(chalk.green(` ✓ iMessages configured — ${summary}`)); + const reconfig = await ask(chalk.white(' Reconfigure? (y/N): ')); + if (reconfig.trim().toLowerCase() === 'y' || reconfig.trim().toLowerCase() === 'yes') { + console.log(chalk.dim(' Enter "none" to disable iMessages.')); + console.log(''); + const imProjectId = await ask(chalk.white(` Photon Project ID [${maskKey(im.projectId)}]: `)); + if (imProjectId.toLowerCase() === 'none') { + config.channels.imessages.enabled = false; + config.channels.imessages.projectId = ''; + config.channels.imessages.projectSecret = ''; + config.channels.imessages.allowedUsers = []; + config.channels.imessages.allowAllUsers = false; + saveConfig(config); + } else if (imProjectId) { + config.channels.imessages.projectId = imProjectId; + const imProjectSecret = await ask(chalk.white(` Photon Project Secret [${maskKey(im.projectSecret)}]: `)); + if (imProjectSecret) { + appendToEnv('IMESSAGES_PROJECT_SECRET', imProjectSecret); + config.channels.imessages.projectSecret = imProjectSecret; + } + const imUsers = await ask(chalk.white(` Allowed addresses (comma-separated phone/email) [${im.allowedUsers.join(', ')}]: `)); + if (imUsers) { + config.channels.imessages.allowedUsers = imUsers.split(',').map((u: string) => u.trim()).filter(Boolean); + } + const imAllowAll = await ask(chalk.white(` Allow all users? (y/N) [${im.allowAllUsers ? 'Y' : 'N'}]: `)); + config.channels.imessages.allowAllUsers = imAllowAll.toLowerCase() === 'y' || imAllowAll.toLowerCase() === 'yes'; + config.channels.imessages.enabled = true; + saveConfig(config); + } + } + } else { + const imSetup = await ask(chalk.white(' Configure iMessages? (y/N): ')); + if (imSetup.toLowerCase() === 'y' || imSetup.toLowerCase() === 'yes') { + console.log(''); + console.log(chalk.dim(' ── How to get your Photon credentials ──')); + console.log(''); + console.log(chalk.dim(' 1. Sign up at https://app.photon.codes')); + console.log(chalk.dim(' (free tier: unlimited messages, up to 10 users)')); + console.log(''); + console.log(chalk.dim(' 2. Create a new project in the dashboard')); + console.log(''); + console.log(chalk.dim(' 3. In the project, add the iMessage provider:')); + console.log(chalk.dim(' click "Add Platform" → select "iMessage" → enable "Cloud Mode"')); + console.log(chalk.dim(' (Cloud Mode gives you a managed iMessage line — no Mac needed)')); + console.log(''); + console.log(chalk.dim(' 4. Copy the Project ID and Project Secret from')); + console.log(chalk.dim(' Settings → API Credentials')); + console.log(''); + console.log(chalk.dim(' Docs: https://docs.photon.codes/spectrum-ts/providers/imessage')); + console.log(''); + const imProjectId = await ask(chalk.white(' Photon Project ID: ')); + if (imProjectId) { + const imProjectSecret = await ask(chalk.white(' Photon Project Secret: ')); + if (imProjectSecret) { + appendToEnv('IMESSAGES_PROJECT_SECRET', imProjectSecret); + } + console.log(''); + console.log(chalk.dim(' Enter addresses of users who can message Mercury.')); + console.log(chalk.dim(' Phone numbers with country code (e.g., +1234567890) or Apple ID emails.')); + console.log(chalk.dim(' Enter "all" to allow anyone, or press Enter to set up the allow list later.')); + const imUsers = await ask(chalk.white(' Allowed addresses (comma-separated, or "all"): ')); + if (imUsers.toLowerCase() === 'all') { + config.channels.imessages.allowAllUsers = true; + config.channels.imessages.allowedUsers = []; + } else if (imUsers) { + config.channels.imessages.allowedUsers = imUsers.split(',').map((u: string) => u.trim()).filter(Boolean); + config.channels.imessages.allowAllUsers = false; + } + config.channels.imessages.projectId = imProjectId; + config.channels.imessages.projectSecret = imProjectSecret || ''; + config.channels.imessages.enabled = true; + saveConfig(config); + console.log(''); + console.log(chalk.green(' ✓ iMessages configured!')); + console.log(chalk.dim(' Mercury will connect to iMessage via Photon Spectrum when started.')); + console.log(chalk.dim(' The bot phone number will be auto-detected from the first message.')); + console.log(chalk.dim(' Text your Photon iMessage line to verify the connection.')); + console.log(''); + } else { + config.channels.imessages.enabled = false; + saveConfig(config); + } + } else { + config.channels.imessages.enabled = false; + saveConfig(config); + } + } + hr(); console.log(''); console.log(chalk.bold.white(' GitHub Integration (optional)')); @@ -2544,8 +2648,13 @@ async function runAgent(isDaemon: boolean = false): Promise { const telegram = channels.get('telegram'); const signal = channels.get('signal'); const whatsapp = channels.get('whatsapp'); + const imessages = channels.get('imessages'); // Explicit channel override from the user + if (channel === 'imessages' && imessages) { + await imessages.sendFile(filePath, channelType === 'imessages' ? channelId : undefined); + return; + } if (channel === 'signal' && signal) { await signal.sendFile(filePath, channelType === 'signal' ? channelId : undefined); return; @@ -2559,6 +2668,11 @@ async function runAgent(isDaemon: boolean = false): Promise { return; } + if (channelType === 'imessages' && imessages) { + await imessages.sendFile(filePath, channelId); + return; + } + if (channelType === 'telegram' && telegram) { await telegram.sendFile(filePath, channelId); return; @@ -2574,6 +2688,11 @@ async function runAgent(isDaemon: boolean = false): Promise { return; } + if (config.channels.imessages.enabled && imessages && (config.channels.imessages.allowAllUsers || config.channels.imessages.allowedUsers.length > 0)) { + await imessages.sendFile(filePath); + return; + } + if (config.channels.telegram.enabled && telegram && getTelegramApprovedUsers(config).length > 0) { await telegram.sendFile(filePath); return; diff --git a/src/types/channel.ts b/src/types/channel.ts index 4b8cc192..fce88f79 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -16,7 +16,7 @@ export interface TelegramPendingRequest { pairingCode?: string; } -export type ChannelType = 'cli' | 'telegram' | 'web' | 'internal' | 'signal' | 'discord' | 'slack' | 'whatsapp'; +export type ChannelType = 'cli' | 'telegram' | 'web' | 'internal' | 'signal' | 'discord' | 'slack' | 'whatsapp' | 'imessages'; export interface ChannelMessage { id: string; @@ -158,3 +158,21 @@ export interface WhatsAppChannelConfig extends ChannelConfig { groupId?: string; groupName?: string; } + +export interface IMessagesAccessUser { + address: string; + name?: string; + role: 'admin' | 'member'; + pairedAt: string; +} + +export interface IMessagesChannelConfig extends ChannelConfig { + type: 'imessages'; + projectId: string; + projectSecret: string; + allowedUsers: string[]; + allowAllUsers: boolean; + markdown: boolean; + reactions: boolean; + maxInlineAttachmentBytes: number; +} diff --git a/src/utils/config.ts b/src/utils/config.ts index 45401ba6..1ce59da5 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -3,7 +3,7 @@ import { join, resolve } from 'node:path'; import { homedir } from 'node:os'; import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; import { config as loadDotenv } from 'dotenv'; -import type { SignalAccessUser, SignalPendingRequest, DiscordAccessUser, DiscordPendingRequest, SlackAccessUser, SlackPendingRequest, WhatsAppAdmin } from '../types/channel.js'; +import type { SignalAccessUser, SignalPendingRequest, DiscordAccessUser, DiscordPendingRequest, SlackAccessUser, SlackPendingRequest, WhatsAppAdmin, IMessagesAccessUser } from '../types/channel.js'; const MERCURY_HOME = join(homedir(), '.mercury'); @@ -144,6 +144,16 @@ export interface MercuryConfig { groupId?: string; groupName?: string; }; + imessages: { + enabled: boolean; + projectId: string; + projectSecret: string; + allowedUsers: string[]; + allowAllUsers: boolean; + markdown: boolean; + reactions: boolean; + maxInlineAttachmentBytes: number; + }; }; github: { username: string; @@ -357,6 +367,18 @@ export function getDefaultConfig(): MercuryConfig { groupId: getEnv('WHATSAPP_GROUP_ID', ''), groupName: getEnv('WHATSAPP_GROUP_NAME', 'Mercury'), }, + imessages: { + enabled: getEnvBool('IMESSAGES_ENABLED', false), + projectId: getEnv('IMESSAGES_PROJECT_ID', ''), + projectSecret: getEnv('IMESSAGES_PROJECT_SECRET', ''), + allowedUsers: getEnv('IMESSAGES_ALLOWED_USERS', '') + .split(',') + .filter(Boolean), + allowAllUsers: getEnvBool('IMESSAGES_ALLOW_ALL_USERS', false), + markdown: getEnvBool('IMESSAGES_MARKDOWN', true), + reactions: getEnvBool('IMESSAGES_REACTIONS', false), + maxInlineAttachmentBytes: getEnvNum('IMESSAGES_MAX_INLINE_ATTACHMENT_BYTES', 20 * 1024 * 1024), + }, }, github: { username: getEnv('GITHUB_USERNAME', ''), @@ -1226,3 +1248,16 @@ export function migrateLegacyWhatsAppAccess(config: MercuryConfig): MercuryConfi delete wa.pending; return config; } + +// ── iMessage access helpers ────────────────────────────────────── + +export function isIMessagesAllowed(config: MercuryConfig, address: string): boolean { + if (config.channels.imessages.allowAllUsers) return true; + return config.channels.imessages.allowedUsers.includes(address); +} + +export function getIMessagesAccessSummary(config: MercuryConfig): string { + const { allowedUsers, allowAllUsers } = config.channels.imessages; + if (allowAllUsers) return 'open access (all users)'; + return `${allowedUsers.length} allowed user${allowedUsers.length === 1 ? '' : 's'}`; +} From a7948a899bcb2f5b776da3f231e2114ca5c0a69a Mon Sep 17 00:00:00 2001 From: M Shehzen Date: Sun, 21 Jun 2026 16:12:21 +0530 Subject: [PATCH 5/8] feat: add OpenRouter provider integration - New OpenRouterProvider using @ai-sdk/openai with attribution headers - OpenRouter appears as option 4 in doctor/onboarding provider selection - Search-based model selector with type-to-filter for 300+ models - Model validation against full OpenRouter catalog (rejects typos with suggestions) - 15 curated preferred models shown by default, search for more - Config: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL, OPENROUTER_ENABLED - Web API: openrouter in valid provider names, model validation on save - Provider registry: openrouter entry with fallback chain support - Increased model limit for OpenRouter from 7 to 15 in catalog --- .env.example | 8 +- src/index.ts | 214 ++++++++++++++++++++++++++++++++++- src/providers/index.ts | 1 + src/providers/openrouter.ts | 42 +++++++ src/providers/registry.ts | 4 + src/utils/arrow-select.ts | 213 ++++++++++++++++++++++++++++++++++ src/utils/config.ts | 14 ++- src/utils/provider-models.ts | 104 ++++++++++++++++- src/web/api/providers.ts | 31 ++++- 9 files changed, 623 insertions(+), 8 deletions(-) create mode 100644 src/providers/openrouter.ts diff --git a/.env.example b/.env.example index 183f6abc..1bbd260f 100644 --- a/.env.example +++ b/.env.example @@ -55,7 +55,13 @@ MIMO_TOKEN_PLAN_BASE_URL=https://token-plan-cn.xiaomimimo.com/v1 MIMO_TOKEN_PLAN_MODEL=mimo-v2.5-pro MIMO_TOKEN_PLAN_ENABLED=false -# Default provider: deepseek | openai | anthropic | grok | ollamaCloud | ollamaLocal | openaiCompat | mimo | mimoTokenPlan +# OpenRouter (access 300+ models via single API key — openrouter.ai) +OPENROUTER_API_KEY= +OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 +OPENROUTER_MODEL=anthropic/claude-sonnet-4 +OPENROUTER_ENABLED=true + +# Default provider: deepseek | openai | anthropic | grok | ollamaCloud | ollamaLocal | openaiCompat | mimo | mimoTokenPlan | openrouter DEFAULT_PROVIDER=deepseek # ── Telegram ── diff --git a/src/index.ts b/src/index.ts index bfa19d05..452bbe10 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,10 @@ import { Command } from 'commander'; import readline from 'node:readline'; import chalk from 'chalk'; +// Prevent EPIPE errors from crashing the process — these occur when writing +// to a closed pipe (e.g. terminal exits, daemon stdout closes). Harmless. +process.on('SIGPIPE', () => {}); + import { loadConfig, saveConfig, @@ -83,8 +87,8 @@ import { startBackground, stopDaemon, showLogs, getDaemonStatus, restartDaemon, import { installService, uninstallService, showServiceStatus, isServiceInstalled } from './cli/service.js'; import { runWithWatchdog } from './cli/watchdog.js'; import { setGitHubToken } from './utils/github.js'; -import { selectWithArrowKeys } from './utils/arrow-select.js'; -import { ProviderModelFetchError, fetchProviderModelCatalog } from './utils/provider-models.js'; +import { selectWithArrowKeys, searchWithArrowKeys } from './utils/arrow-select.js'; +import { ProviderModelFetchError, fetchProviderModelCatalog, fetchFullOpenRouterCatalog } from './utils/provider-models.js'; import { startWebServer, stopWebServer, updateStatus as updateWebStatus, setUserMemory as setWebUserMemory, setWebChannel as setWebWebChannel, setScheduler as setWebScheduler, setAgentSupervisor as setWebSupervisor, setBackgroundTaskManager as setWebBgTasks, setSpotifyClient as setWebSpotify, setProgrammingMode as setWebProgrammingMode, setModelSwitchCallback as setWebModelSwitch, setCurrentProviderCallback as setWebCurrentProvider, setKanbanSupervisor as setWebKanban, setKanbanBoardManager as setWebBoardManager, setKanbanProviders as setWebKanbanProviders, setIDEProviders as setWebIDEProviders } from './web/server.js'; import { isWebAuthInitialized, setWebPassword } from './web/auth.js'; @@ -156,6 +160,7 @@ const PROVIDER_OPTIONS: Array<{ key: ProviderName; label: string }> = [ { key: 'deepseek', label: 'DeepSeek' }, { key: 'openai', label: 'OpenAI' }, { key: 'anthropic', label: 'Anthropic' }, + { key: 'openrouter', label: 'OpenRouter' }, { key: 'githubCopilot', label: 'GitHub Copilot' }, { key: 'grok', label: 'Grok (xAI)' }, { key: 'ollamaCloud', label: 'Ollama Cloud' }, @@ -176,6 +181,7 @@ function getConfiguredProviderNames(config: MercuryConfig): ProviderName[] { function getProviderLabel(name: ProviderName): string { if (name === 'chatgptWeb') return 'OpenAI (ChatGPT Plus/Pro)'; + if (name === 'openrouter') return 'OpenRouter'; return PROVIDER_OPTIONS.find((option) => option.key === name)?.label || name; } @@ -318,6 +324,12 @@ function validateApiKey(provider: ProviderName, value: string): string | null { : 'MiMo Token Plan keys must start with `tp-`.'; } + if (provider === 'openrouter') { + return /^sk-or-v[12]-/.test(value) + ? null + : 'OpenRouter keys must start with `sk-or-v1-` or `sk-or-v2-`.'; + } + return null; } @@ -385,6 +397,194 @@ async function chooseProviderModel( } } +async function chooseOpenRouterModel( + recommendedModel: string, + models: string[], + fullCatalog: string[], +): Promise { + const SEARCH_OPTION = '__search__'; + const CUSTOM_OPTION = '__custom__'; + const DEFAULT_OPTION = '__default__'; + + const topModels = models.slice(0, 15); + + const selection = await selectWithArrowKeys( + 'OpenRouter Models', + [ + { value: SEARCH_OPTION, label: '🔍 Search models...' }, + { value: DEFAULT_OPTION, label: `Use provider default (${recommendedModel})` }, + ...topModels.map((model) => ({ + value: model, + label: model, + })), + { value: CUSTOM_OPTION, label: 'Enter a custom model name' }, + ], + { helperText: 'Use arrow keys, then press Enter. Select "Search" to filter 300+ models.' }, + ); + + if (!selection || selection === DEFAULT_OPTION) { + return recommendedModel; + } + + if (selection === SEARCH_OPTION) { + const allOptions: Array<{ value: string; label: string }> = fullCatalog.map((m) => ({ + value: m, + label: m, + })); + + const searched = await searchWithArrowKeys( + 'Search OpenRouter Models', + allOptions, + { helperText: 'Type to search, arrow keys to navigate, Enter to select, Esc to clear.' }, + ); + + if (!searched) { + return chooseOpenRouterModel(recommendedModel, models, fullCatalog); + } + + return searched; + } + + if (selection !== CUSTOM_OPTION) { + return selection; + } + + while (true) { + const customModel = await ask(chalk.white(' OpenRouter model name [Enter for default]: ')); + if (!customModel) { + return recommendedModel; + } + + const error = validateModelName(customModel); + if (!error) { + if (fullCatalog.length > 0 && !fullCatalog.includes(customModel)) { + const suggestion = findClosestModel(customModel, fullCatalog); + if (suggestion) { + console.log(chalk.red(` Model "${customModel}" not found in OpenRouter catalog.`)); + console.log(chalk.dim(` Did you mean: ${suggestion}?`)); + const useSuggestion = await ask(chalk.white(` Use "${suggestion}" instead? [Y/n]: `)); + if (useSuggestion.toLowerCase() !== 'n') { + return suggestion; + } + } else { + console.log(chalk.red(` Model "${customModel}" not found in OpenRouter catalog.`)); + console.log(chalk.dim(' Please select from the catalog or use the search option.')); + } + continue; + } + return customModel; + } + + console.log(chalk.red(` ${error}`)); + } +} + +function findClosestModel(input: string, catalog: string[]): string | null { + const lower = input.toLowerCase(); + let bestMatch = ''; + let bestScore = Infinity; + + for (const model of catalog) { + const modelLower = model.toLowerCase(); + if (modelLower === lower) return model; + + if (modelLower.includes(lower) || lower.includes(modelLower.split('/').pop() ?? '')) { + const score = Math.abs(model.length - input.length); + if (score < bestScore) { + bestScore = score; + bestMatch = model; + } + } + } + + if (!bestMatch && catalog.length > 0) { + const inputParts = lower.split('/'); + if (inputParts.length === 2) { + const [provider, modelName] = inputParts; + for (const model of catalog) { + const modelLower = model.toLowerCase(); + const modelParts = modelLower.split('/'); + if (modelParts.length === 2 && modelParts[0] === provider) { + if (modelParts[1].includes(modelName) || modelName.includes(modelParts[1])) { + return model; + } + } + } + } + } + + return bestMatch || null; +} + +async function promptOpenRouterModelSelection(config: MercuryConfig, isReconfig: boolean): Promise<{ apiKey?: string; model?: string; skipped: boolean }> { + const existingConfig = config.providers.openrouter; + + while (true) { + const mask = isReconfig && existingConfig.apiKey ? ` [${maskKey(existingConfig.apiKey)}]` : ''; + const value = await ask(chalk.white(` OpenRouter API key${mask}${isReconfig ? '' : ' (Enter to skip)'}: `)); + if (!value) { + if (isReconfig && existingConfig.apiKey) { + return { apiKey: existingConfig.apiKey, model: existingConfig.model, skipped: true }; + } + return { skipped: true }; + } + + const formatError = validateApiKey('openrouter', value); + if (formatError) { + console.log(chalk.red(` ${formatError}`)); + continue; + } + + console.log(chalk.dim(' Validating OpenRouter and fetching models...')); + try { + const catalog = await fetchProviderModelCatalog('openrouter', { + ...existingConfig, + apiKey: value, + }); + + let fullCatalog: string[] = []; + try { + fullCatalog = await fetchFullOpenRouterCatalog({ + ...existingConfig, + apiKey: value, + }); + } catch { + fullCatalog = catalog.models; + } + + const model = await chooseOpenRouterModel( + catalog.recommendedModel, + catalog.models, + fullCatalog, + ); + return { apiKey: value, model, skipped: false }; + } catch (error) { + const message = error instanceof ProviderModelFetchError + ? error.message + : 'Mercury could not fetch models for OpenRouter.'; + console.log(chalk.yellow(` ${message}`)); + console.log(chalk.dim(' The API key looks valid but Mercury could not reach OpenRouter.')); + console.log(chalk.dim(' You can enter a model name manually, or skip OpenRouter for now.')); + + const manualModel = await ask(chalk.white(' OpenRouter model name (Enter to skip OpenRouter for now): ')); + if (!manualModel) { + if (isReconfig && existingConfig.apiKey) { + return { apiKey: existingConfig.apiKey, model: existingConfig.model, skipped: true }; + } + return { skipped: true }; + } + + const modelError = validateModelName(manualModel); + if (modelError) { + console.log(chalk.red(` ${modelError}`)); + continue; + } + + return { apiKey: value, model: manualModel, skipped: false }; + } + } +} + async function promptApiKeyWithModelSelection( config: MercuryConfig, provider: ProviderName, @@ -1690,6 +1890,16 @@ async function configure(existingConfig?: MercuryConfig): Promise { continue; } + if (provider === 'openrouter') { + const result = await promptOpenRouterModelSelection(config, isReconfig); + if (!result.skipped && result.apiKey && result.model) { + config.providers.openrouter.apiKey = result.apiKey; + config.providers.openrouter.model = result.model; + config.providers.openrouter.enabled = true; + } + continue; + } + if (provider === 'githubCopilot') { const { loadGitHubSession, isGitHubSessionValid } = await import('./auth/github-session.js'); const existing = loadGitHubSession(); diff --git a/src/providers/index.ts b/src/providers/index.ts index 568dbe4e..3262a73d 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -4,5 +4,6 @@ export { AnthropicProvider } from './anthropic.js'; export { DeepSeekProvider } from './deepseek.js'; export { OllamaProvider } from './ollama.js'; export { MiMoProvider } from './mimo.js'; +export { OpenRouterProvider } from './openrouter.js'; export { ProviderRegistry } from './registry.js'; export type { LLMResponse, LLMStreamChunk } from './base.js'; diff --git a/src/providers/openrouter.ts b/src/providers/openrouter.ts new file mode 100644 index 00000000..ae24fa12 --- /dev/null +++ b/src/providers/openrouter.ts @@ -0,0 +1,42 @@ +import { createOpenAI } from '@ai-sdk/openai'; +import { BaseProvider } from './base.js'; +import type { ProviderConfig } from '../utils/config.js'; + +export class OpenRouterProvider extends BaseProvider { + readonly name: string; + readonly model: string; + private modelInstance: any; + + constructor(config: ProviderConfig) { + super(config); + this.name = config.name; + this.model = config.model; + + const client = createOpenAI({ + apiKey: config.apiKey || 'no-key', + baseURL: config.baseUrl || 'https://openrouter.ai/api/v1', + name: 'openrouter', + headers: { + 'HTTP-Referer': 'https://mercuryagent.ai', + 'X-Title': 'Mercury Agent', + }, + }); + this.modelInstance = client.chat(config.model); + } + + async generateText(_prompt: string, _systemPrompt: string): Promise { + throw new Error('Use getModelInstance() with the AI SDK agent loop'); + } + + async *streamText(_prompt: string, _systemPrompt: string): AsyncIterable { + throw new Error('Use getModelInstance() with the AI SDK agent loop'); + } + + isAvailable(): boolean { + return this.config.apiKey.length > 0; + } + + getModelInstance() { + return this.modelInstance; + } +} \ No newline at end of file diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 2de70f09..25b78dc0 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -30,6 +30,9 @@ async function createProvider(pc: ProviderConfig): Promise { } else if (pc.name === 'githubCopilot') { const { GitHubCopilotProvider } = await import('./github-copilot.js'); return new GitHubCopilotProvider(pc); + } else if (pc.name === 'openrouter') { + const { OpenRouterProvider } = await import('./openrouter.js'); + return new OpenRouterProvider(pc); } else { const { OpenAICompatProvider } = await import('./openai-compat.js'); return new OpenAICompatProvider(pc); @@ -60,6 +63,7 @@ export class ProviderRegistry { config.providers.mimoTokenPlan, config.providers.chatgptWeb, config.providers.githubCopilot, + config.providers.openrouter, ]; // Load only configured providers in parallel diff --git a/src/utils/arrow-select.ts b/src/utils/arrow-select.ts index 5498cbda..d7d00c1c 100644 --- a/src/utils/arrow-select.ts +++ b/src/utils/arrow-select.ts @@ -153,3 +153,216 @@ export async function selectWithArrowKeys( render(); }); } + +export interface SearchSelectConfig { + helperText?: string; + maxVisibleOptions?: number; + signal?: AbortSignal; + searchPlaceholder?: string; +} + +export async function searchWithArrowKeys( + title: string, + allOptions: ArrowSelectOption[], + config: SearchSelectConfig = {}, +): Promise { + if (!process.stdin.isTTY || !process.stdout.isTTY || allOptions.length === 0) { + return allOptions[0]?.value ?? ''; + } + + readline.emitKeypressEvents(process.stdin); + + const stdin = process.stdin; + const stdout = process.stdout; + const canUseRawMode = typeof stdin.setRawMode === 'function'; + const helperText = config.helperText || 'Type to search, use arrow keys to navigate, Enter to select.'; + const searchPlaceholder = config.searchPlaceholder || 'Search...'; + const maxVisibleOptions = Math.max( + 1, + Math.min( + 10, + config.maxVisibleOptions ?? Math.max(5, (stdout.rows || 12) - 9), + ), + ); + + let searchQuery = ''; + let filteredOptions = allOptions; + let activeIndex = 0; + let renderedLineCount = 0; + let windowStart = 0; + + const filterOptions = (query: string): ArrowSelectOption[] => { + if (!query) return allOptions; + const lower = query.toLowerCase(); + return allOptions.filter((opt) => + opt.label.toLowerCase().includes(lower) || opt.value.toLowerCase().includes(lower), + ); + }; + + const writeLines = (lines: string[]) => { + if (renderedLineCount > 0) { + readline.moveCursor(stdout, 0, -(renderedLineCount - 1)); + } + + for (let index = 0; index < lines.length; index += 1) { + readline.cursorTo(stdout, 0); + readline.clearLine(stdout, 0); + stdout.write(lines[index]); + if (index < lines.length - 1) { + stdout.write('\n'); + } + } + }; + + const render = () => { + filteredOptions = filterOptions(searchQuery); + if (activeIndex >= filteredOptions.length) { + activeIndex = Math.max(0, filteredOptions.length - 1); + } + + if (activeIndex < windowStart) { + windowStart = activeIndex; + } else if (activeIndex >= windowStart + maxVisibleOptions) { + windowStart = activeIndex - maxVisibleOptions + 1; + } + + const visibleOptions = filteredOptions.slice(windowStart, windowStart + maxVisibleOptions); + const hasHiddenAbove = windowStart > 0; + const hasHiddenBelow = windowStart + maxVisibleOptions < filteredOptions.length; + + const searchDisplay = searchQuery + ? chalk.cyanBright(searchQuery) + chalk.dim('▏') + : chalk.dim(searchPlaceholder) + chalk.dim('▏'); + + const resultCount = filteredOptions.length === allOptions.length + ? chalk.dim(` ${allOptions.length} models`) + : chalk.dim(` ${filteredOptions.length} of ${allOptions.length} models`); + + const lines = [ + chalk.bold.white(` ${title}`), + chalk.dim(` ${helperText}`), + '', + ` ${chalk.dim('🔍')} ${searchDisplay}`, + resultCount, + '', + topIndicator(hasHiddenAbove), + ...visibleOptions.map((option, visibleIndex) => { + const index = windowStart + visibleIndex; + const isActive = index === activeIndex; + const marker = isActive ? chalk.cyanBright('●') : chalk.dim('·'); + const text = isActive ? chalk.cyanBright(option.label) : chalk.dim(option.label); + return ` ${marker} ${text}`; + }), + bottomIndicator(hasHiddenBelow), + '', + ]; + + writeLines(lines); + renderedLineCount = lines.length; + }; + + const topIndicator = (hasHiddenAbove: boolean) => ( + hasHiddenAbove ? chalk.dim(' ↑ more') : ' ' + ); + const bottomIndicator = (hasHiddenBelow: boolean) => ( + hasHiddenBelow ? chalk.dim(' ↓ more') : ' ' + ); + + return await new Promise((resolve, reject) => { + const cleanup = () => { + stdin.off('keypress', onKeypress); + config.signal?.removeEventListener('abort', onAbort); + if (canUseRawMode) { + stdin.setRawMode(false); + } + }; + + const onAbort = () => { + cleanup(); + reject(new ArrowSelectCancelledError()); + }; + + const onKeypress = (_input: string, key: readline.Key) => { + if (key.ctrl && key.name === 'c') { + cleanup(); + process.kill(process.pid, 'SIGINT'); + return; + } + + if (key.name === 'escape') { + if (searchQuery) { + searchQuery = ''; + activeIndex = 0; + windowStart = 0; + render(); + return; + } + cleanup(); + resolve(''); + return; + } + + if (key.name === 'up') { + if (filteredOptions.length > 0) { + activeIndex = (activeIndex - 1 + filteredOptions.length) % filteredOptions.length; + } + render(); + return; + } + + if (key.name === 'down') { + if (filteredOptions.length > 0) { + activeIndex = (activeIndex + 1) % filteredOptions.length; + } + render(); + return; + } + + if (key.name === 'backspace') { + if (searchQuery.length > 0) { + searchQuery = searchQuery.slice(0, -1); + activeIndex = 0; + windowStart = 0; + render(); + } + return; + } + + if (key.name === 'return') { + if (filteredOptions.length === 0) { + return; + } + const selected = filteredOptions[activeIndex]?.value ?? ''; + cleanup(); + stdout.write('\n'); + resolve(selected); + return; + } + + if (key.name === 'left' || key.name === 'right' || key.name === 'tab') { + return; + } + + if (_input && !key.ctrl && !key.meta && _input.length === 1) { + searchQuery += _input; + activeIndex = 0; + windowStart = 0; + render(); + } + }; + + if (canUseRawMode) { + stdin.setRawMode(true); + } + stdin.resume(); + stdin.on('keypress', onKeypress); + config.signal?.addEventListener('abort', onAbort, { once: true }); + + if (config.signal?.aborted) { + onAbort(); + return; + } + + render(); + }); +} diff --git a/src/utils/config.ts b/src/utils/config.ts index 1ce59da5..293a472f 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -65,7 +65,8 @@ export type ProviderName = | 'mimo' | 'mimoTokenPlan' | 'chatgptWeb' - | 'githubCopilot'; + | 'githubCopilot' + | 'openrouter'; export interface MercuryConfig { identity: { @@ -86,6 +87,7 @@ export interface MercuryConfig { mimoTokenPlan: ProviderConfig; chatgptWeb: ProviderConfig; githubCopilot: ProviderConfig; + openrouter: ProviderConfig; }; channels: { telegram: { @@ -309,6 +311,13 @@ export function getDefaultConfig(): MercuryConfig { model: getEnv('GITHUB_COPILOT_MODEL', 'gpt-4o'), enabled: getEnvBool('GITHUB_COPILOT_ENABLED', false), }, + openrouter: { + name: 'openrouter', + apiKey: getEnv('OPENROUTER_API_KEY', ''), + baseUrl: getEnv('OPENROUTER_BASE_URL', 'https://openrouter.ai/api/v1'), + model: getEnv('OPENROUTER_MODEL', 'anthropic/claude-sonnet-4'), + enabled: getEnvBool('OPENROUTER_ENABLED', true), + }, }, channels: { telegram: { @@ -521,6 +530,9 @@ export function isProviderConfigured(provider: ProviderConfig): boolean { // Considered "configured" if enabled with a model selected. return provider.model.length > 0; } + if (provider.name === 'openrouter') { + return provider.apiKey.length > 0; + } return provider.apiKey.length > 0; } diff --git a/src/utils/provider-models.ts b/src/utils/provider-models.ts index 3a50d38f..c9f32a5c 100644 --- a/src/utils/provider-models.ts +++ b/src/utils/provider-models.ts @@ -92,6 +92,26 @@ const GITHUB_COPILOT_PREFERRED_MODELS = [ 'gemini-3.1-pro-preview', ] as const; +const OPENROUTER_PREFERRED_MODELS = [ + 'anthropic/claude-sonnet-4', + 'anthropic/claude-opus-4', + 'openai/gpt-5.2', + 'openai/gpt-4o', + 'openai/gpt-4.1', + 'openai/gpt-4.1-mini', + 'google/gemini-2.5-pro', + 'google/gemini-2.5-flash', + 'meta-llama/llama-4-maverick', + 'deepseek/deepseek-r1', + 'deepseek/deepseek-chat', + 'mistralai/mistral-large-latest', + 'xai/grok-4', + 'qwen/qwen3-235b-a22b', + 'microsoft/phi-4', +] as const; + +const OPENROUTER_MAX_MODEL_OPTIONS = 15; + export class ProviderModelFetchError extends Error { constructor(message: string) { super(message); @@ -200,6 +220,7 @@ function chooseRecommendedModel( mimoTokenPlan: MIMO_TOKEN_PLAN_PREFERRED_MODELS, chatgptWeb: CHATGPT_WEB_PREFERRED_MODELS, githubCopilot: GITHUB_COPILOT_PREFERRED_MODELS, + openrouter: OPENROUTER_PREFERRED_MODELS, }; for (const candidate of preferredByProvider[provider]) { @@ -238,14 +259,17 @@ export function buildModelCatalog( mimoTokenPlan: MIMO_TOKEN_PLAN_PREFERRED_MODELS, chatgptWeb: CHATGPT_WEB_PREFERRED_MODELS, githubCopilot: GITHUB_COPILOT_PREFERRED_MODELS, + openrouter: OPENROUTER_PREFERRED_MODELS, }; const withoutRecommended = filtered.filter((model) => model !== recommendedModel); const prioritized = prioritizeModels(withoutRecommended, preferredByProvider[provider]); + const maxModels = provider === 'openrouter' ? OPENROUTER_MAX_MODEL_OPTIONS : MAX_MODEL_OPTIONS; + return { recommendedModel, - models: limitModels(prioritized), + models: prioritized.slice(0, maxModels), }; } @@ -399,6 +423,80 @@ async function fetchMiMoTokenPlanModels(config: ProviderConfig): Promise { + let response: Response; + try { + response = await fetch( + `${trimTrailingSlash(config.baseUrl)}/models`, + { + headers: { + Authorization: `Bearer ${config.apiKey}`, + 'HTTP-Referer': 'https://mercuryagent.ai', + }, + signal: AbortSignal.timeout(30_000), + }, + ); + } catch { + throw new ProviderModelFetchError('Mercury could not reach OpenRouter. Please check your network connection and try again.'); + } + + if (!response.ok) { + if (response.status === 401 || response.status === 403) { + throw new ProviderModelFetchError('Mercury could not authenticate with OpenRouter. Please check your API key.'); + } + throw new ProviderModelFetchError(`OpenRouter returned an error (HTTP ${response.status}). Please try again later.`); + } + + let data: OpenAIModelResponse; + try { + data = await response.json() as OpenAIModelResponse; + } catch { + throw new ProviderModelFetchError('Mercury could not read the model list returned by OpenRouter.'); + } + + const ids = (data.data ?? []) + .map((model) => model.id?.trim() ?? '') + .filter((id) => id.includes('/')); + + return buildModelCatalog('openrouter', ids, config.model); +} + +export async function fetchFullOpenRouterCatalog(config: ProviderConfig): Promise { + let response: Response; + try { + response = await fetch( + `${trimTrailingSlash(config.baseUrl)}/models`, + { + headers: { + Authorization: `Bearer ${config.apiKey}`, + 'HTTP-Referer': 'https://mercuryagent.ai', + }, + signal: AbortSignal.timeout(30_000), + }, + ); + } catch { + throw new ProviderModelFetchError('Mercury could not reach OpenRouter. Please check your network connection and try again.'); + } + + if (!response.ok) { + if (response.status === 401 || response.status === 403) { + throw new ProviderModelFetchError('Mercury could not authenticate with OpenRouter. Please check your API key.'); + } + throw new ProviderModelFetchError(`OpenRouter returned an error (HTTP ${response.status}). Please try again later.`); + } + + let data: OpenAIModelResponse; + try { + data = await response.json() as OpenAIModelResponse; + } catch { + throw new ProviderModelFetchError('Mercury could not read the model list returned by OpenRouter.'); + } + + return (data.data ?? []) + .map((model) => model.id?.trim() ?? '') + .filter((id) => id.includes('/')); +} + export async function fetchProviderModelCatalog( provider: ProviderName, config: ProviderConfig, @@ -454,5 +552,9 @@ export async function fetchProviderModelCatalog( return fetchGitHubModels(session.accessToken); } + if (provider === 'openrouter') { + return fetchOpenRouterModels(config); + } + return fetchOpenAICompatModels(provider, config); } diff --git a/src/web/api/providers.ts b/src/web/api/providers.ts index 8a9d3006..c95c279d 100644 --- a/src/web/api/providers.ts +++ b/src/web/api/providers.ts @@ -26,7 +26,7 @@ providers.post('/api/providers/:name', async (c) => { const body = await c.req.json(); const config = loadConfig(); - const validNames: ProviderName[] = ['openai', 'anthropic', 'deepseek', 'grok', 'ollamaCloud', 'ollamaLocal']; + const validNames: ProviderName[] = ['openai', 'anthropic', 'deepseek', 'grok', 'ollamaCloud', 'ollamaLocal', 'mimo', 'mimoTokenPlan', 'openrouter']; if (!validNames.includes(providerName)) { return c.json({ error: 'Unknown provider' }, 400); } @@ -34,7 +34,31 @@ providers.post('/api/providers/:name', async (c) => { const p = config.providers[providerName]; if (body.apiKey !== undefined) p.apiKey = body.apiKey; if (body.baseUrl !== undefined) p.baseUrl = body.baseUrl; - if (body.model !== undefined) p.model = body.model; + if (body.model !== undefined) { + if (providerName === 'openrouter' && body.model) { + try { + const { fetchFullOpenRouterCatalog } = await import('../../utils/provider-models.js'); + const fullCatalog = await fetchFullOpenRouterCatalog(p as ProviderConfig); + if (!fullCatalog.includes(body.model)) { + let suggestion = ''; + const inputLower = (body.model as string).toLowerCase(); + for (const model of fullCatalog) { + if (model.toLowerCase().includes(inputLower) || inputLower.includes(model.toLowerCase().split('/').pop() ?? '')) { + suggestion = model; + break; + } + } + const detail = suggestion + ? `Did you mean: ${suggestion}?` + : 'Please select a model from the OpenRouter catalog.'; + return c.json({ error: `Model "${body.model}" not found in OpenRouter catalog. ${detail}` }, 400); + } + } catch { + // Catalog fetch failed — allow the model through without validation + } + } + p.model = body.model; + } if (body.enabled !== undefined) p.enabled = body.enabled; saveConfig(config); @@ -53,7 +77,8 @@ providers.post('/api/providers/:name/test', async (c) => { try { const { fetchProviderModelCatalog } = await import('../../utils/provider-models.js'); const catalog = await fetchProviderModelCatalog(providerName, p as ProviderConfig); - return c.json({ success: true, models: catalog.models.slice(0, 10), recommendedModel: catalog.recommendedModel }); + const maxModels = providerName === 'openrouter' ? 20 : 10; + return c.json({ success: true, models: catalog.models.slice(0, maxModels), recommendedModel: catalog.recommendedModel }); } catch (err: any) { return c.json({ success: false, error: err.message || 'Connection failed' }, 400); } From 08c2ac017fd27e8c622f7449d7804e4bccb0017e Mon Sep 17 00:00:00 2001 From: M Shehzen Date: Sun, 21 Jun 2026 16:12:26 +0530 Subject: [PATCH 6/8] fix: suppress EPIPE crash and spectrum lifecycle log noise - Add SIGPIPE handler at process entry point to prevent Node.js from terminating on closed pipe writes - Filter EPIPE errors in uncaughtException/unhandledRejection handlers instead of writing crash flags for harmless pipe closures - Add process.exit(1) to uncaughtException handler for real errors (previously continued after fatal errors) - Remove crash-flag writes from unhandledRejection (non-fatal, was over-aggressive) - Suppress spectrum-ts INFO logs by setting logLevel to 'error' in the Spectrum constructor (eliminates [spectrum.lifecycle] noise) --- src/imessages/index.ts | 1 + src/web/server.ts | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/imessages/index.ts b/src/imessages/index.ts index d8b5e2ea..3a7be4b3 100644 --- a/src/imessages/index.ts +++ b/src/imessages/index.ts @@ -25,6 +25,7 @@ export async function createImessagesApp(config: IMessagesConfig): Promise projectId: config.projectId, projectSecret: config.projectSecret, providers: [imessageMod.imessage.config()], + options: { logLevel: 'error' }, }); return app; } diff --git a/src/web/server.ts b/src/web/server.ts index 53bee61e..3b67b91f 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -201,22 +201,26 @@ process.on('exit', () => { }); process.on('uncaughtException', (err) => { - logger.error({ err: err.message }, 'Uncaught exception in web server'); - // Write crash flag so next startup can report to the user. + if (err && (err as any).code === 'EPIPE') { + return; + } + logger.error({ err: err.message }, 'Uncaught exception'); try { const { writeCrashFlag } = require('../core/crash-flag.js'); writeCrashFlag({ reason: `Uncaught exception: ${err.message}`.slice(0, 300), timestamp: Date.now() }); } catch { /* best effort */ } + process.exit(1); }); process.on('unhandledRejection', (reason: any) => { - logger.warn({ err: reason?.message || reason }, 'Unhandled rejection in web server (non-fatal)'); - try { - const { writeCrashFlag } = require('../core/crash-flag.js'); - writeCrashFlag({ reason: `Unhandled rejection: ${reason?.message || reason}`.slice(0, 300), timestamp: Date.now() }); - } catch { /* best effort */ } + if (reason && (reason as any).code === 'EPIPE') { + return; + } + logger.warn({ err: reason?.message || reason }, 'Unhandled rejection (non-fatal)'); }); +process.on('SIGPIPE', () => {}); + export function startWebServer(): { port: number; url: string } { const port = getWebPort(); initWebAuth(); From 4707bfbf03662f96729ef62776ac5b227168c43b Mon Sep 17 00:00:00 2001 From: M Shehzen Date: Mon, 22 Jun 2026 11:17:49 +0530 Subject: [PATCH 7/8] fix: suppress Slack socket-mode pong timeout log noise --- src/channels/slack.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/channels/slack.ts b/src/channels/slack.ts index ff3ddb8e..3bb086b2 100644 --- a/src/channels/slack.ts +++ b/src/channels/slack.ts @@ -1,6 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; -import { App } from '@slack/bolt'; +import { App, LogLevel } from '@slack/bolt'; import type { ChannelMessage } from '../types/channel.js'; import { BaseChannel, type PermissionMode } from './base.js'; import type { MercuryConfig } from '../utils/config.js'; @@ -103,6 +103,7 @@ export class SlackChannel extends BaseChannel { token: slackConfig.botToken, socketMode: true, appToken: slackConfig.appToken, + logLevel: LogLevel.ERROR, }); this.registerHandlers(); From 27a0821446f4ea77d271ed6da16059ac34fd9e72 Mon Sep 17 00:00:00 2001 From: AI Agent Date: Fri, 26 Jun 2026 17:58:59 +0300 Subject: [PATCH 8/8] fix: stabilize chatgpt web telegram daemon --- src/channels/telegram.ts | 6 ++-- src/cli/daemon.test.ts | 46 +++++++++++++++++++++++++++++++ src/cli/daemon.ts | 13 +++++++++ src/core/agent.ts | 13 +++++++-- src/index.ts | 9 +++++- src/providers/chatgpt-web.test.ts | 27 ++++++++++++++++++ src/providers/chatgpt-web.ts | 31 ++++++++++++++++++--- 7 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 src/cli/daemon.test.ts create mode 100644 src/providers/chatgpt-web.test.ts diff --git a/src/channels/telegram.ts b/src/channels/telegram.ts index fa058df2..7110beb3 100644 --- a/src/channels/telegram.ts +++ b/src/channels/telegram.ts @@ -311,8 +311,10 @@ export class TelegramChannel extends BaseChannel { const fullContent = content + timeSuffix; if (!fullContent.trim()) return; - // If this looks like a final AI response (long, not a system notice), defer it - const isSystemNotice = content.startsWith('☿ ') || content.startsWith('⚠') || content.startsWith(' [') || content.length < 200; + // If this looks like a final AI response, defer it even when it is short. + // Short tool-backed answers like token-budget status are user-visible + // results, not status-card notices. + const isSystemNotice = content.startsWith('☿ ') || content.startsWith('⚠') || content.startsWith(' [') || content.startsWith('Permission Mode'); if (isSystemNotice) { // Append as a notice line in the status card const notices = this.statusNotices.get(key) || []; diff --git a/src/cli/daemon.test.ts b/src/cli/daemon.test.ts new file mode 100644 index 00000000..66a43b2f --- /dev/null +++ b/src/cli/daemon.test.ts @@ -0,0 +1,46 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +let home: string; + +vi.mock('../utils/config.js', () => ({ + getMercuryHome: () => home, +})); + +const daemon = await import('./daemon.js'); + +describe('daemon pid file ownership', () => { + afterEach(() => { + if (home && existsSync(home)) { + rmSync(home, { recursive: true, force: true }); + } + }); + + it('lets a daemon child claim the pid file with its real pid', () => { + home = mkdtempSync(join(tmpdir(), 'mercury-daemon-')); + + daemon.writeCurrentPid(); + + expect(readFileSync(join(home, 'daemon.pid'), 'utf8')).toBe(String(process.pid)); + expect(daemon.getDaemonStatus()).toMatchObject({ running: true, pid: process.pid }); + }); + + it('does not unlink a pid file owned by another daemon process', () => { + home = mkdtempSync(join(tmpdir(), 'mercury-daemon-')); + const pidPath = join(home, 'daemon.pid'); + + daemon.writeCurrentPid(); + const claimedPid = readFileSync(pidPath, 'utf8'); + expect(claimedPid).toBe(String(process.pid)); + + // Simulate another supervised daemon having replaced this process as owner. + // PID 1 should exist on every supported platform in test environments. + writeFileSync(pidPath, '1'); + + daemon.unlinkPidIfCurrent(); + + expect(readFileSync(pidPath, 'utf8')).toBe('1'); + }); +}); diff --git a/src/cli/daemon.ts b/src/cli/daemon.ts index e05a67dd..ea1f4d32 100644 --- a/src/cli/daemon.ts +++ b/src/cli/daemon.ts @@ -67,6 +67,19 @@ export function readPid(): number | null { } } +export function writeCurrentPid(): void { + const home = getMercuryHome(); + if (!existsSync(home)) { + mkdirSync(home, { recursive: true }); + } + writeFileSync(pidPath(), String(process.pid)); +} + +export function unlinkPidIfCurrent(): void { + if (readPid() !== process.pid) return; + try { unlinkSync(pidPath()); } catch {} +} + function isProcessRunning(pid: number): boolean { try { process.kill(pid, 0); diff --git a/src/core/agent.ts b/src/core/agent.ts index ea18956f..ae1abb78 100644 --- a/src/core/agent.ts +++ b/src/core/agent.ts @@ -1236,6 +1236,7 @@ export class Agent { let usedProvider: { name: string; model: string } | null = null; let lastError: any = null; let streamedText = ''; + let lastToolResultText = ''; const loopDetector = new ToolCallLoopDetector(); const loopAbortController = new AbortController(); let loopWarningSent = false; @@ -1316,6 +1317,9 @@ export class Agent { const tc = toolCalls[i]; const tr = toolResults[i] as any; const resultStr = typeof tr?.result === 'string' ? tr.result : JSON.stringify(tr?.result ?? ''); + if (resultStr.trim() && resultStr.length < 4000) { + lastToolResultText = resultStr; + } const failed = resultStr.length < 5000 && ( resultStr.startsWith('Error:') || resultStr.startsWith('⚠') || @@ -1601,6 +1605,9 @@ export class Agent { const tc = toolCalls[i]; const tr = toolResults[i] as any; const resultStr = typeof tr?.result === 'string' ? tr.result : JSON.stringify(tr?.result ?? ''); + if (resultStr.trim() && resultStr.length < 4000) { + lastToolResultText = resultStr; + } const failed = resultStr.length < 5000 && ( resultStr.startsWith('Error:') || resultStr.startsWith('⚠') || @@ -1889,7 +1896,8 @@ export class Agent { return; } - const finalText = (streamedText || result.text || '').trim() || '(no text response)'; + const generatedText = (streamedText || result.text || '').trim(); + const finalText = (generatedText && generatedText !== '""' ? generatedText : lastToolResultText.trim()) || '(no text response)'; this.markProgress('Finalizing response...'); // Store plan output when in plan mode for later execution @@ -1981,7 +1989,8 @@ export class Agent { (channel as TelegramChannel).endTask(msg.channelId); // Flush deferred response const deferred = (channel as TelegramChannel).popDeferredResponse(msg.channelId); - const responseText = deferred || (!streamedText && finalText ? finalText : null); + const cleanDeferred = deferred?.trim(); + const responseText = cleanDeferred && cleanDeferred !== '""' ? deferred : (finalText ? finalText : null); if (responseText && responseText.trim()) { await channel.send(responseText, msg.channelId, elapsed); } diff --git a/src/index.ts b/src/index.ts index 452bbe10..dd5a675d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -83,7 +83,7 @@ import { CapabilityRegistry } from './capabilities/registry.js'; import { SkillLoader } from './skills/loader.js'; import { registerSkillsCommand } from './skills/cli.js'; import { getManual } from './utils/manual.js'; -import { startBackground, stopDaemon, showLogs, getDaemonStatus, restartDaemon, tryAutoDaemonize, isStandaloneBinary } from './cli/daemon.js'; +import { startBackground, stopDaemon, showLogs, getDaemonStatus, restartDaemon, tryAutoDaemonize, isStandaloneBinary, writeCurrentPid, unlinkPidIfCurrent } from './cli/daemon.js'; import { installService, uninstallService, showServiceStatus, isServiceInstalled } from './cli/service.js'; import { runWithWatchdog } from './cli/watchdog.js'; import { setGitHubToken } from './utils/github.js'; @@ -2722,6 +2722,10 @@ async function runAgent(isDaemon: boolean = false): Promise { config = ensureCreatorField(config); const name = config.identity.name; + if (isDaemon) { + writeCurrentPid(); + } + // Check for crash flag from previous run — if Mercury crashed mid-task, // report it to the user immediately so they don't have to investigate. const { readCrashFlag, clearCrashFlag } = await import('./core/crash-flag.js'); @@ -3326,6 +3330,9 @@ async function runAgent(isDaemon: boolean = false): Promise { } await stopWebServer(); await agent.shutdown(); + if (isDaemon) { + unlinkPidIfCurrent(); + } process.exit(0); }; diff --git a/src/providers/chatgpt-web.test.ts b/src/providers/chatgpt-web.test.ts new file mode 100644 index 00000000..d03a7de7 --- /dev/null +++ b/src/providers/chatgpt-web.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; +import { sanitiseBody } from './chatgpt-web.js'; + +describe('ChatGPT Web request sanitisation', () => { + it('removes non-persisted Responses API item references when store=false', () => { + const body = JSON.parse(sanitiseBody(JSON.stringify({ + model: 'gpt-5.5', + input: [ + { role: 'user', content: 'make an html bunny' }, + { id: 'rs_123', type: 'reasoning', summary: [] }, + { id: 'fc_456', type: 'function_call', name: 'write_file', arguments: '{}' }, + { id: 'call_789', type: 'function_call_output', output: 'ok' }, + { id: 'msg_keep', type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'done' }] }, + ], + store: true, + stream: false, + max_output_tokens: 4096, + temperature: 0.2, + }))); + + expect(body.store).toBe(false); + expect(body.stream).toBe(true); + expect(body.max_output_tokens).toBeUndefined(); + expect(body.temperature).toBeUndefined(); + expect(body.input.map((item: any) => item.id ?? item.role)).toEqual(['user', 'msg_keep']); + }); +}); diff --git a/src/providers/chatgpt-web.ts b/src/providers/chatgpt-web.ts index 2de35558..3d413241 100644 --- a/src/providers/chatgpt-web.ts +++ b/src/providers/chatgpt-web.ts @@ -16,16 +16,29 @@ const UNSUPPORTED_FIELDS = [ /** * Sanitise an outgoing request body for the ChatGPT codex/responses endpoint. */ -function sanitiseBody(raw: string): string { +export function sanitiseBody(raw: string): string { const body = JSON.parse(raw); - // Required + // Required by the ChatGPT/Codex endpoint. It rejects store=true. Because + // store=false means reasoning items are not persisted, any later input that + // references an rs_* item must be removed instead of having its id stripped. body.store = false; body.stream = true; if (!body.instructions) body.instructions = 'You are a helpful assistant.'; // Strip unsupported & undefined/null for (const key of UNSUPPORTED_FIELDS) delete body[key]; + if (Array.isArray(body.input)) { + body.input = body.input.filter((item: any) => { + if (!item || typeof item !== 'object') return true; + if (item.type === 'reasoning') return false; + // ChatGPT/Codex currently forces store=false, so server-side response + // item IDs are not persisted. Replaying historical reasoning or function + // call items by id makes the next request fail with "Item not found". + if (typeof item.id === 'string' && /^(rs|fc|call)_/.test(item.id)) return false; + return true; + }); + } for (const key of Object.keys(body)) { if (body[key] === undefined || body[key] === null) delete body[key]; } @@ -139,8 +152,11 @@ export class ChatGPTWebProvider extends BaseProvider { logger.warn('ChatGPT Web: no valid session — requests will fail'); } - const accessToken = session?.accessToken ?? ''; - const accountId = session?.accountId ?? ''; + // Capture a mutable auth snapshot inside the custom fetch. A long-running + // daemon can outlive the 1h ChatGPT access token; refreshing in-place lets + // the next request recover instead of sending an expired bearer forever. + let accessToken = session?.accessToken ?? ''; + let accountId = session?.accountId ?? ''; // Rebuild the model instance if token changed or first call if (!this.cachedModelInstance || accessToken !== this.cachedAccessToken) { @@ -157,6 +173,13 @@ export class ChatGPTWebProvider extends BaseProvider { apiKey: 'chatgpt-oauth-token', // dummy — real auth is in custom fetch baseURL: `${CHATGPT_BACKEND_API}/codex`, fetch: async (url, init) => { + const freshSession = await getValidChatGPTSession(); + if (freshSession?.accessToken && freshSession?.accountId) { + accessToken = freshSession.accessToken; + accountId = freshSession.accountId; + this.cachedAccessToken = accessToken; + } + // Inject OAuth headers const headers = new Headers(init?.headers); headers.set('Authorization', `Bearer ${accessToken}`);