nubot-lite 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/lib/api.mjs +6 -2
- package/lib/cloudflare.mjs +13 -12
- package/lib/commands/init.mjs +260 -251
- package/package.json +1 -1
- package/template/.claude/skills/nubot-lite/SKILL.md +7 -1
- package/template/panel/PANEL.md +1 -1
- package/template/panel/assets/AgendarDemoPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/{AgentDatasetsPage-D8vQhRWO.js → AgentDatasetsPage-wLtBtSB2.js} +2 -2
- package/template/panel/assets/AgentKnowledgeGroupsPage-BGHNFj-e.js +1 -0
- package/template/panel/assets/AgentListPage-F3ja9_o_.js +1 -0
- package/template/panel/assets/AgentQaTrainingPage-CojiU8_W.js +1 -0
- package/template/panel/assets/AiAgentWhatsappPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/BlogPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/BlogPostPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/CalculadoraTiempoRespuestaPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/{CampaignDetailPage-DFMctNEj.js → CampaignDetailPage-DUNnckvn.js} +1 -1
- package/template/panel/assets/CampaignsPage-CS60yZBd.js +1 -0
- package/template/panel/assets/ChannelSelector-C7R9zqrt.js +1 -0
- package/template/panel/assets/ChatbotParaWhatsappPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/ConfirmModal-B27OBQdm.js +1 -0
- package/template/panel/assets/ContactsPage-CvoDlFUN.js +14 -0
- package/template/panel/assets/CreateAgentPage-CbhikMY1.js +19 -0
- package/template/panel/assets/CreditLimitBanner-j72L7jgK.js +1 -0
- package/template/panel/assets/CrmParaWhatsappPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/DashboardPage-BGWkXY7W.js +1 -0
- package/template/panel/assets/{EmojiPicker-BToAe-MT.js → EmojiPicker-ByTMSXTj.js} +1 -1
- package/template/panel/assets/{EventTypesSettingsPage-DCedeQzM.js → EventTypesSettingsPage-CLpO1VBL.js} +1 -1
- package/template/panel/assets/GeneradorEnlacesPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/GeneradorQrWhatsappPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/{GeneralSettingsPage-B04ozcHb.js → GeneralSettingsPage-CY-5aGgL.js} +1 -1
- package/template/panel/assets/GoogleCalendarManagePage-oX7DuErV.js +1 -0
- package/template/panel/assets/{InvitationAcceptancePage-BPgtBFl5.js → InvitationAcceptancePage-BYllrAYs.js} +1 -1
- package/template/panel/assets/IrAlSitioPublico-Bn_BvTp1.js +1 -0
- package/template/panel/assets/LiteAiUsagePage-Dzm8jBL2.js +1 -0
- package/template/panel/assets/{LiteSetupPage-42rgAdnG.js → LiteSetupPage-CMSAur_e.js} +1 -1
- package/template/panel/assets/MarkdownRenderer-C3TD9KYa.js +3 -0
- package/template/panel/assets/MediaProcessingService-CG2DdSyN.js +1 -0
- package/template/panel/assets/NewCampaignPage-BfSEd075.js +1 -0
- package/template/panel/assets/OrdersConfigPage-BAKKaX0L.js +1 -0
- package/template/panel/assets/OrdersPage-CXiPOCXP.js +1 -0
- package/template/panel/assets/{PasswordStrengthIndicator-HZK_oU0W.js → PasswordStrengthIndicator-4h-Y0s2r.js} +1 -1
- package/template/panel/assets/PlantillaDetailPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/PlantillasIndexPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/PrivacyPolicy-Bn_BvTp1.js +1 -0
- package/template/panel/assets/{ProductManagementPage-DNM7DEgt.js → ProductManagementPage-CRkNTicv.js} +4 -4
- package/template/panel/assets/PublicFormPage-BxPxJ3Pq.js +1 -0
- package/template/panel/assets/QaForm-Px-cmakA.js +5 -0
- package/template/panel/assets/SelectPlanPage-Gyo5C1Hq.js +1 -0
- package/template/panel/assets/SignatureEditor-DIhmrXow.js +1 -0
- package/template/panel/assets/{TagSelector-ChoKzvjW.js → TagSelector-o6tVpRs9.js} +1 -1
- package/template/panel/assets/TagsSettingsPage-DT8MfW1H.js +2 -0
- package/template/panel/assets/TeamsPage-BuWuGxZo.js +1 -0
- package/template/panel/assets/WhatsappBusinessApiPage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/WhatsappMultiagentePage-Bn_BvTp1.js +1 -0
- package/template/panel/assets/{account-D2ltXC_U.js → account-G3WXxisG.js} +1 -1
- package/template/panel/assets/{admin-lEgqZTsa.js → admin-CSMlOMR8.js} +1 -1
- package/template/panel/assets/{agent.service-BcAkMU7j.js → agent.service-BE2rJG_o.js} +1 -1
- package/template/panel/assets/{alert-dialog-D8P69s-m.js → alert-dialog-gzZZoAiu.js} +2 -2
- package/template/panel/assets/{alert-DI3GBLi1.js → alert-seLKo73M.js} +1 -1
- package/template/panel/assets/api-keys-BF33GS2N.js +1 -0
- package/template/panel/assets/{archive-LUe5bKi5.js → archive-CxUKRJwu.js} +1 -1
- package/template/panel/assets/{arrow-left-BeDdBs_Q.js → arrow-left-CI7mH7bD.js} +1 -1
- package/template/panel/assets/{arrow-up-BiBq8Pvx.js → arrow-up-DJzXCc8_.js} +1 -1
- package/template/panel/assets/{arrow-up-down-CaG2BONN.js → arrow-up-down-DkSCsCjR.js} +1 -1
- package/template/panel/assets/audit-slljESxh.js +1 -0
- package/template/panel/assets/{authService-Cbm8LdlJ.js → authService-CtvlzCDa.js} +1 -1
- package/template/panel/assets/{avatar-CILB1zwS.js → avatar-BXhSKQGY.js} +1 -1
- package/template/panel/assets/{billing-IVLNopU2.js → billing-Cy2XUBeY.js} +1 -1
- package/template/panel/assets/{brain-B9z7jZ7A.js → brain-BmrjUpsb.js} +1 -1
- package/template/panel/assets/{calendar-CIHhMjNy.js → calendar-Bu0aDtW1.js} +1 -1
- package/template/panel/assets/{calendar-days-BwJfOz8M.js → calendar-days-CXtOZxOF.js} +1 -1
- package/template/panel/assets/{card-DcKsxjXK.js → card-DdbmSh-v.js} +1 -1
- package/template/panel/assets/{checkbox-pcbjC0d3.js → checkbox-CuJh5AMO.js} +1 -1
- package/template/panel/assets/{chevrons-up-down-DC-BWTrj.js → chevrons-up-down-DNNdNhV8.js} +1 -1
- package/template/panel/assets/{circle-check-big-DzPo5zZX.js → circle-check-big-BOZQ_2Kv.js} +1 -1
- package/template/panel/assets/{circle-x-BwMKIs8b.js → circle-x-decmm7ip.js} +1 -1
- package/template/panel/assets/cloud-B3m0_VpL.js +1 -0
- package/template/panel/assets/{code-BMAyHFJe.js → code-C-UH-ZD-.js} +1 -1
- package/template/panel/assets/{cog-M7j64wa2.js → cog-BS-_-U4F.js} +1 -1
- package/template/panel/assets/{collapsible-B22t1J-V.js → collapsible-Ce_8EeFJ.js} +1 -1
- package/template/panel/assets/{command-COdSmSIB.js → command-aHRTWaYV.js} +1 -1
- package/template/panel/assets/connectionsService-BJg4S1e9.js +1 -0
- package/template/panel/assets/contactsApi-DI5dcMLo.js +1 -0
- package/template/panel/assets/creditErrors-DSbswjsG.js +1 -0
- package/template/panel/assets/{database-C1qvt3Py.js → database-Brb7Omak.js} +1 -1
- package/template/panel/assets/{datapath-C12uI_pT.js → datapath-CHeKabeh.js} +1 -1
- package/template/panel/assets/{datetime-BuZosCVk.js → datetime-D72Uxhjs.js} +1 -1
- package/template/panel/assets/{download-CiVQHPeB.js → download-CyfdxFCW.js} +1 -1
- package/template/panel/assets/{ellipsis-CMEJnUfK.js → ellipsis-GLu0u4ze.js} +1 -1
- package/template/panel/assets/{ellipsis-vertical-DTqbEYYp.js → ellipsis-vertical-CskGwbgN.js} +1 -1
- package/template/panel/assets/email-inbox-kyJ2qML5.js +1 -0
- package/template/panel/assets/es-BDgg6unH.js +1 -0
- package/template/panel/assets/external-link-w7K66ae2.js +1 -0
- package/template/panel/assets/{eye-HfQCUjxJ.js → eye-CR5C6hVv.js} +1 -1
- package/template/panel/assets/{eye-off-v1Z_lKS0.js → eye-off-CBiJXw4j.js} +1 -1
- package/template/panel/assets/facebook-D0SQMea2.js +1 -0
- package/template/panel/assets/{file-DMzjdlTL.js → file-CTZ8-xmL.js} +1 -1
- package/template/panel/assets/file-braces-54MsOnzo.js +1 -0
- package/template/panel/assets/file-spreadsheet-DpekIaV8.js +1 -0
- package/template/panel/assets/{folder-D0GKakGp.js → folder-DxTHNhQ_.js} +1 -1
- package/template/panel/assets/{folder-open-DQFc4qTJ.js → folder-open-BM4rA96e.js} +1 -1
- package/template/panel/assets/format-BG1mN6C0.js +1 -0
- package/template/panel/assets/{grip-vertical-FN-eTC2C.js → grip-vertical-CS-dU0tJ.js} +1 -1
- package/template/panel/assets/{hash-BoTE56iw.js → hash-ByrjK6gt.js} +1 -1
- package/template/panel/assets/{help-center-B1MAIBWA.js → help-center-fmvvoAkD.js} +1 -1
- package/template/panel/assets/{history-DhHayxUg.js → history-97D1GT_9.js} +1 -1
- package/template/panel/assets/house-CXew5LB-.js +1 -0
- package/template/panel/assets/{index-DsyZ_FlA.js → index-43NA2pdZ.js} +23 -23
- package/template/panel/assets/index-BG59-Kyv.js +6 -0
- package/template/panel/assets/{index-DUnioqCT.js → index-BGvtgHY3.js} +1 -1
- package/template/panel/assets/index-BL76lyPt.js +2 -0
- package/template/panel/assets/index-BUA9lP4J.js +1 -0
- package/template/panel/assets/index-Bfvq4Q3v.js +1 -0
- package/template/panel/assets/index-CK793Wzl.css +1 -0
- package/template/panel/assets/{index-BTxe6gLJ.js → index-ChrvveQm.js} +1 -1
- package/template/panel/assets/index-CoJIvJ6y.js +1 -0
- package/template/panel/assets/{index-CMEeV9dO.js → index-CsV1u-Y7.js} +2 -2
- package/template/panel/assets/{index-BiSc3WD9.js → index-DIxON2Dm.js} +1 -1
- package/template/panel/assets/index-DtoSJTyl.js +1 -0
- package/template/panel/assets/index-FBuxpbGF.js +36 -0
- package/template/panel/assets/index-YR5X3nd4.js +1 -0
- package/template/panel/assets/{instagram-CC7iYt7V.js → instagram-Bm17x-Fs.js} +1 -1
- package/template/panel/assets/interactiveLimits-C7Sx7s9W.js +1 -0
- package/template/panel/assets/landing-CEUFhLJ9.js +1 -0
- package/template/panel/assets/{layout-grid-Cm6hfBWZ.js → layout-grid-DN-t2Wjd.js} +1 -1
- package/template/panel/assets/{list-BKc6EXnV.js → list-B0uLKf1b.js} +1 -1
- package/template/panel/assets/{map-pin-CPmcofD3.js → map-pin-COYHwobx.js} +1 -1
- package/template/panel/assets/{maximize-2-WdbM7Kdx.js → maximize-2-SbmXBBYe.js} +1 -1
- package/template/panel/assets/mediaLimits-CAYdWqeQ.js +1 -0
- package/template/panel/assets/mediaValidation-Bg0xHQrv.js +1 -0
- package/template/panel/assets/message-circle-tNSzOeOW.js +1 -0
- package/template/panel/assets/{message-square-Dfn16oHc.js → message-square-B1_OirdW.js} +1 -1
- package/template/panel/assets/{meta-templates-C7yEjSOI.js → meta-templates-hhDdxBOR.js} +1 -1
- package/template/panel/assets/metadataSchemasApi-BmJko2eC.js +1 -0
- package/template/panel/assets/{mic-CT4vyzLZ.js → mic-ClFq9hMZ.js} +1 -1
- package/template/panel/assets/minus-vh4a51Mn.js +1 -0
- package/template/panel/assets/{music-Cu8AQMFp.js → music-CgtkroYi.js} +1 -1
- package/template/panel/assets/{page-components-o2TF52HV.js → page-components-DXOKtt_M.js} +1 -1
- package/template/panel/assets/{pages-Cj5Qd1QO.js → pages-BcYbyh_-.js} +1 -1
- package/template/panel/assets/{pages-B7digtXi.js → pages-BuibyrnC.js} +1 -1
- package/template/panel/assets/{pages-Dw7dvmaS.js → pages-CGXCBdff.js} +1 -1
- package/template/panel/assets/paperclip-DVRbzE7C.js +1 -0
- package/template/panel/assets/{pen-line-DLnhkIuw.js → pen-line-Cr0YFjAs.js} +1 -1
- package/template/panel/assets/{pencil-Cx8u1N8t.js → pencil-aqjc7AWb.js} +1 -1
- package/template/panel/assets/{popover-MyuU-yAK.js → popover-cWIhNmod.js} +1 -1
- package/template/panel/assets/{power-CdR2OkIX.js → power-CVFzhVJx.js} +1 -1
- package/template/panel/assets/{product.service-CRLZOQsr.js → product.service-FClit10f.js} +1 -1
- package/template/panel/assets/products.types-nZIsk2w8.js +1 -0
- package/template/panel/assets/proxy-CvX2KM2u.js +1 -0
- package/template/panel/assets/{save-Dua2yhWl.js → save-mwkHDnDL.js} +1 -1
- package/template/panel/assets/scroll-area-CSCo4HHB.js +1 -0
- package/template/panel/assets/{select-DlB_BbfM.js → select-DCY_lg9j.js} +1 -1
- package/template/panel/assets/{send-Dxmdupsl.js → send-DBQH77K9.js} +1 -1
- package/template/panel/assets/{separator-CPT3uVRM.js → separator-DznqP68l.js} +1 -1
- package/template/panel/assets/{settings-2-C-c8ECWW.js → settings-2-PhKPMrsj.js} +1 -1
- package/template/panel/assets/shield-alert-Fv3j9Wbv.js +1 -0
- package/template/panel/assets/{skeleton-B0zh8s_y.js → skeleton-CN6xlRXP.js} +1 -1
- package/template/panel/assets/{slider-CY71MXOW.js → slider-D0QB_Lme.js} +1 -1
- package/template/panel/assets/{square-BuGqLxJB.js → square-Dm5VC71z.js} +1 -1
- package/template/panel/assets/{square-pen-CJajoyrm.js → square-pen-Dbn7xmc-.js} +1 -1
- package/template/panel/assets/{star-Cuegk1GF.js → star-BMD3HmS6.js} +1 -1
- package/template/panel/assets/stat-card-W7s_-fHW.js +1 -0
- package/template/panel/assets/storage.api-C0ZJBoSz.js +20 -0
- package/template/panel/assets/{switch-ODBa0fCT.js → switch-C7XmAU1c.js} +1 -1
- package/template/panel/assets/{table-CvSELOPi.js → table-7NGq0kRw.js} +1 -1
- package/template/panel/assets/{tabs-DqXhuAr6.js → tabs-BGCKrmVw.js} +1 -1
- package/template/panel/assets/{tags-By_tlh_r.js → tags-HU2fuPFH.js} +1 -1
- package/template/panel/assets/{target-DEuCdeUI.js → target-CG_g4_Fj.js} +1 -1
- package/template/panel/assets/{textarea-BYtcLsMD.js → textarea-B1anutjq.js} +1 -1
- package/template/panel/assets/{ticket-CWba7G1i.js → ticket-CHs1Xojr.js} +1 -1
- package/template/panel/assets/{trash-2-Du2vmQCH.js → trash-2-D_kmHMwn.js} +1 -1
- package/template/panel/assets/{trending-up-Ba4HSvuW.js → trending-up-BnHeLWFs.js} +1 -1
- package/template/panel/assets/{upload-C7TwpdyA.js → upload-CQg4_W5r.js} +1 -1
- package/template/panel/assets/{useAiAgentsQuery-Bm6aRHKj.js → useAiAgentsQuery-CXo6TmU6.js} +1 -1
- package/template/panel/assets/useFunnelStats-Bk8wTET5.js +1 -0
- package/template/panel/assets/useFunnelsQuery-vDrahaDr.js +1 -0
- package/template/panel/assets/{useInfiniteQuery-BXUM1Z9V.js → useInfiniteQuery-D0sFT2K2.js} +1 -1
- package/template/panel/assets/{useMutation-a5tIBvQY.js → useMutation-0ypmz8SO.js} +1 -1
- package/template/panel/assets/useOrgSyncedFetch-FSgwRhpR.js +1 -0
- package/template/panel/assets/usePipelineBoardQuery-BysscZXI.js +9 -0
- package/template/panel/assets/{useUploadConfig-BuiF0X-3.js → useUploadConfig-CvVmSc6S.js} +1 -1
- package/template/panel/assets/{user-DwCALzIG.js → user-l48WDfDH.js} +1 -1
- package/template/panel/assets/{user-plus-BiUjcHil.js → user-plus-CWaaUP-O.js} +1 -1
- package/template/panel/assets/{video-VfwKgB6z.js → video-CILE66CA.js} +1 -1
- package/template/panel/assets/{volume-2-C5DMdOVq.js → volume-2-DfFqN0dw.js} +1 -1
- package/template/panel/assets/wrench-Dcbk4scY.js +1 -0
- package/template/panel/index.html +2 -2
- package/template/panel/version.json +2 -2
- package/template/worker/migrations/0006_ai_usage.sql +33 -0
- package/template/worker/migrations/0007_channels.sql +18 -0
- package/template/worker/src/agent/pricing.ts +37 -0
- package/template/worker/src/agent/prompt.ts +137 -134
- package/template/worker/src/agent/run.ts +350 -243
- package/template/worker/src/agent/sandbox.ts +2 -2
- package/template/worker/src/agent/tools.ts +463 -463
- package/template/worker/src/channels/accounts.ts +177 -0
- package/template/worker/src/channels/zernio.ts +161 -148
- package/template/worker/src/inbox/contacts.ts +2 -0
- package/template/worker/src/inbox/store.ts +199 -187
- package/template/worker/src/index.ts +2 -0
- package/template/worker/src/nubot/account.ts +163 -178
- package/template/worker/src/nubot/ai-usage.ts +197 -0
- package/template/worker/src/nubot/channels.ts +159 -0
- package/template/worker/src/nubot/contacts.ts +387 -386
- package/template/worker/src/nubot/funnels.ts +672 -670
- package/template/worker/src/nubot/inbox.ts +623 -616
- package/template/worker/src/nubot/index.ts +127 -122
- package/template/worker/src/nubot/mappers.ts +12 -5
- package/template/worker/src/routes/channels.ts +13 -0
- package/template/worker/src/routes/system.ts +110 -100
- package/template/worker/src/webhooks/zernio.ts +154 -125
- package/template/panel/assets/AgendarDemoPage-DOiS40FL.js +0 -1
- package/template/panel/assets/AgentKnowledgeGroupsPage-DEPS1lsk.js +0 -1
- package/template/panel/assets/AgentListPage-DqY9rze5.js +0 -1
- package/template/panel/assets/AgentQaTrainingPage-Ecis7xoc.js +0 -1
- package/template/panel/assets/AiAgentWhatsappPage-DOiS40FL.js +0 -1
- package/template/panel/assets/AiMetricsPage-DEPS1lsk.js +0 -1
- package/template/panel/assets/BlogPage-DOiS40FL.js +0 -1
- package/template/panel/assets/BlogPostPage-DOiS40FL.js +0 -1
- package/template/panel/assets/CalculadoraTiempoRespuestaPage-DOiS40FL.js +0 -1
- package/template/panel/assets/CampaignsPage-CLRaXGlj.js +0 -1
- package/template/panel/assets/ChannelSelector-q9tTirNo.js +0 -1
- package/template/panel/assets/ChatbotParaWhatsappPage-DOiS40FL.js +0 -1
- package/template/panel/assets/ConfirmModal-W7VUI3Z_.js +0 -1
- package/template/panel/assets/ContactsPage-CsOFsOPw.js +0 -14
- package/template/panel/assets/CreateAgentPage-Ifxx6_UX.js +0 -19
- package/template/panel/assets/CreditLimitBanner-J49rSVEn.js +0 -1
- package/template/panel/assets/CrmParaWhatsappPage-DOiS40FL.js +0 -1
- package/template/panel/assets/DashboardPage-3Viy83dd.js +0 -1
- package/template/panel/assets/GeneradorEnlacesPage-DOiS40FL.js +0 -1
- package/template/panel/assets/GeneradorQrWhatsappPage-DOiS40FL.js +0 -1
- package/template/panel/assets/IrAlSitioPublico-DOiS40FL.js +0 -1
- package/template/panel/assets/MediaProcessingService-glHGiE_o.js +0 -1
- package/template/panel/assets/NewCampaignPage-Cw99m3W8.js +0 -1
- package/template/panel/assets/OrdersConfigPage-B6BfM3lf.js +0 -1
- package/template/panel/assets/OrdersPage-Du7CJjty.js +0 -1
- package/template/panel/assets/PlantillaDetailPage-DOiS40FL.js +0 -1
- package/template/panel/assets/PlantillasIndexPage-DOiS40FL.js +0 -1
- package/template/panel/assets/PrivacyPolicy-DOiS40FL.js +0 -1
- package/template/panel/assets/PublicFormPage-DDb4OXEM.js +0 -1
- package/template/panel/assets/QaForm-BDboHNKo.js +0 -5
- package/template/panel/assets/SelectPlanPage-DALaf8E5.js +0 -1
- package/template/panel/assets/TagsSettingsPage-DYCKveQq.js +0 -2
- package/template/panel/assets/TeamsPage-DPUcRdlh.js +0 -1
- package/template/panel/assets/WhatsappBusinessApiPage-DOiS40FL.js +0 -1
- package/template/panel/assets/WhatsappMultiagentePage-DOiS40FL.js +0 -1
- package/template/panel/assets/api-keys-B0UQNR5E.js +0 -1
- package/template/panel/assets/audit-BrdHcTNl.js +0 -1
- package/template/panel/assets/connections-Dqwd01UI.js +0 -1
- package/template/panel/assets/connectionsService-BsJf8pFz.js +0 -3
- package/template/panel/assets/contactsApi-B851NME_.js +0 -1
- package/template/panel/assets/creditErrors-CrpkwwUI.js +0 -1
- package/template/panel/assets/email-inbox-LbGeQCty.js +0 -1
- package/template/panel/assets/es-BG480iA5.js +0 -1
- package/template/panel/assets/facebook-RxMBweMB.js +0 -1
- package/template/panel/assets/index-B183O0S5.js +0 -1
- package/template/panel/assets/index-BJCPaymI.css +0 -1
- package/template/panel/assets/index-BtxJGfLL.js +0 -6
- package/template/panel/assets/index-Bz-ixwph.js +0 -1
- package/template/panel/assets/index-CjQSv8hF.js +0 -2
- package/template/panel/assets/index-D3vkHHjD.js +0 -9
- package/template/panel/assets/index-D4WhIkMN.js +0 -1
- package/template/panel/assets/interactiveLimits-CfLJ1O4_.js +0 -1
- package/template/panel/assets/landing-If3oVsS0.js +0 -1
- package/template/panel/assets/mediaValidation-BR-ZOj5w.js +0 -1
- package/template/panel/assets/minus-CyVEzdqL.js +0 -1
- package/template/panel/assets/products.types-46xSxBS8.js +0 -1
- package/template/panel/assets/scroll-area-CavEOIyz.js +0 -1
- package/template/panel/assets/shield-alert-04pSYngI.js +0 -1
- package/template/panel/assets/storage.api-CFU3KJNp.js +0 -20
- package/template/panel/assets/useFunnelStats-tvjpmWIC.js +0 -1
|
@@ -1,122 +1,127 @@
|
|
|
1
|
-
// API con las rutas y las formas de datos que usa el panel de Nubot, montada en /api/v1.
|
|
2
|
-
// Solo cubre lo que Nubot Lite tiene; lo demás responde 404 y el panel lo muestra bloqueado.
|
|
3
|
-
|
|
4
|
-
import { Hono } from "hono";
|
|
5
|
-
import { createMiddleware } from "hono/factory";
|
|
6
|
-
import type { Env } from "../env";
|
|
7
|
-
import { hashPassword, verifyPassword, MIN_PASSWORD } from "../auth/password";
|
|
8
|
-
import { sameSecret } from "../auth/session";
|
|
9
|
-
import { newId } from "../inbox/store";
|
|
10
|
-
import { verifyAccessToken } from "./jwt";
|
|
11
|
-
import { clearRefresh, loadUser, ORG_ID, refreshUserId, sessionResponse, toPanelUser, type UserRow } from "./session";
|
|
12
|
-
import { account } from "./account";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
nubot
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
nubot.get("/auth/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
if ((
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
c.
|
|
105
|
-
await
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
nubot.
|
|
113
|
-
nubot.
|
|
114
|
-
|
|
115
|
-
nubot.route("/",
|
|
116
|
-
nubot.route("/",
|
|
117
|
-
nubot.route("/",
|
|
118
|
-
nubot.route("/",
|
|
119
|
-
nubot.route("/",
|
|
120
|
-
nubot.route("/",
|
|
121
|
-
|
|
122
|
-
nubot.
|
|
1
|
+
// API con las rutas y las formas de datos que usa el panel de Nubot, montada en /api/v1.
|
|
2
|
+
// Solo cubre lo que Nubot Lite tiene; lo demás responde 404 y el panel lo muestra bloqueado.
|
|
3
|
+
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import { createMiddleware } from "hono/factory";
|
|
6
|
+
import type { Env } from "../env";
|
|
7
|
+
import { hashPassword, verifyPassword, MIN_PASSWORD } from "../auth/password";
|
|
8
|
+
import { sameSecret } from "../auth/session";
|
|
9
|
+
import { newId } from "../inbox/store";
|
|
10
|
+
import { verifyAccessToken } from "./jwt";
|
|
11
|
+
import { clearRefresh, loadUser, ORG_ID, refreshUserId, sessionResponse, toPanelUser, type UserRow } from "./session";
|
|
12
|
+
import { account } from "./account";
|
|
13
|
+
import { channels, channelsPublic } from "./channels";
|
|
14
|
+
import { agents } from "./agents";
|
|
15
|
+
import { aiUsage } from "./ai-usage";
|
|
16
|
+
import { contacts } from "./contacts";
|
|
17
|
+
import { funnels } from "./funnels";
|
|
18
|
+
import { products } from "./products";
|
|
19
|
+
import { invitationsPublic, team } from "./team";
|
|
20
|
+
import { templates } from "./templates";
|
|
21
|
+
import { inbox } from "./inbox";
|
|
22
|
+
import { uploads } from "./uploads";
|
|
23
|
+
import { rememberPublicOrigin } from "../media/files";
|
|
24
|
+
|
|
25
|
+
export type NubotEnv = { Bindings: Env; Variables: { user: UserRow } };
|
|
26
|
+
|
|
27
|
+
export const nubot = new Hono<NubotEnv>();
|
|
28
|
+
|
|
29
|
+
nubot.onError((err, c) => {
|
|
30
|
+
console.error(err);
|
|
31
|
+
return c.json({ message: "Algo salió mal en el servidor. Intenta de nuevo.", statusCode: 500 }, 500);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** El panel consulta esto para mostrar si hay conexión con el servidor. */
|
|
35
|
+
nubot.get("/health", (c) => c.json({ status: "ok" }));
|
|
36
|
+
|
|
37
|
+
// ---------- Sesión ----------
|
|
38
|
+
|
|
39
|
+
nubot.post("/auth/login", async (c) => {
|
|
40
|
+
const body = await c.req.json<{ email?: string; password?: string }>().catch(() => ({}) as { email?: string; password?: string });
|
|
41
|
+
const row = await c.env.DB.prepare("SELECT id, email, name, role, password_hash FROM users WHERE email = ?")
|
|
42
|
+
.bind(body.email?.trim().toLowerCase() ?? "")
|
|
43
|
+
.first<UserRow & { password_hash: string }>();
|
|
44
|
+
if (!row || !body.password || !(await verifyPassword(body.password, row.password_hash))) {
|
|
45
|
+
return c.json({ message: "El correo o la contraseña no coinciden.", statusCode: 401 }, 401);
|
|
46
|
+
}
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
await c.env.DB.prepare("UPDATE users SET last_login_at = ? WHERE id = ?").bind(now, row.id).run();
|
|
49
|
+
c.executionCtx.waitUntil(rememberPublicOrigin(c.env, new URL(c.req.url).origin));
|
|
50
|
+
return sessionResponse(c, { ...row, last_login_at: now });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
nubot.post("/auth/refresh", async (c) => {
|
|
54
|
+
const userId = await refreshUserId(c);
|
|
55
|
+
const user = userId ? await loadUser(c.env, userId) : null;
|
|
56
|
+
if (!user) {
|
|
57
|
+
clearRefresh(c);
|
|
58
|
+
return c.json({ message: "La sesión venció. Vuelve a iniciar sesión.", statusCode: 401 }, 401);
|
|
59
|
+
}
|
|
60
|
+
return sessionResponse(c, user);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
nubot.post("/auth/logout", (c) => {
|
|
64
|
+
clearRefresh(c);
|
|
65
|
+
return c.json({ ok: true });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** En Lite no hay cuentas corporativas: todo se entra con correo y contraseña. */
|
|
69
|
+
nubot.get("/auth/check-domain", (c) => c.json({ isEnterprise: false, passwordLoginAllowed: true }));
|
|
70
|
+
|
|
71
|
+
nubot.get("/auth/lite-state", async (c) => {
|
|
72
|
+
const row = await c.env.DB.prepare("SELECT COUNT(*) AS n FROM users").first<{ n: number }>();
|
|
73
|
+
return c.json({ setupRequired: (row?.n ?? 0) === 0 });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/** Crea el primer administrador con la llave de admin del instalador. */
|
|
77
|
+
nubot.post("/auth/lite-setup", async (c) => {
|
|
78
|
+
const body = await c.req.json<{ adminToken?: string; email?: string; name?: string; password?: string }>();
|
|
79
|
+
if (!sameSecret(body.adminToken, c.env.ADMIN_TOKEN)) return c.json({ message: "La llave de admin no es correcta." }, 401);
|
|
80
|
+
const count = await c.env.DB.prepare("SELECT COUNT(*) AS n FROM users").first<{ n: number }>();
|
|
81
|
+
if ((count?.n ?? 0) > 0) return c.json({ message: "El administrador ya fue creado. Inicia sesión." }, 409);
|
|
82
|
+
const email = body.email?.trim().toLowerCase();
|
|
83
|
+
const name = body.name?.trim();
|
|
84
|
+
if (!email || !name) return c.json({ message: "Escribe tu nombre y tu correo." }, 400);
|
|
85
|
+
if (!body.password || body.password.length < MIN_PASSWORD) {
|
|
86
|
+
return c.json({ message: `La contraseña necesita al menos ${MIN_PASSWORD} caracteres.` }, 400);
|
|
87
|
+
}
|
|
88
|
+
const user: UserRow = { id: newId(), email, name, role: "admin", last_login_at: Date.now() };
|
|
89
|
+
await c.env.DB.prepare(
|
|
90
|
+
"INSERT INTO users (id, email, name, password_hash, role, created_at, last_login_at) VALUES (?, ?, ?, ?, 'admin', ?, ?)",
|
|
91
|
+
)
|
|
92
|
+
.bind(user.id, email, name, await hashPassword(body.password), user.last_login_at, user.last_login_at)
|
|
93
|
+
.run();
|
|
94
|
+
return sessionResponse(c, user, 201);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
nubot.route("/", invitationsPublic);
|
|
98
|
+
nubot.route("/", channelsPublic);
|
|
99
|
+
|
|
100
|
+
// ---------- Lo demás pide sesión ----------
|
|
101
|
+
|
|
102
|
+
nubot.use("*", createMiddleware<NubotEnv>(async (c, next) => {
|
|
103
|
+
const token = c.req.header("Authorization")?.replace(/^Bearer /, "");
|
|
104
|
+
const userId = await verifyAccessToken(c.env.SESSION_SECRET, token);
|
|
105
|
+
const user = userId ? await loadUser(c.env, userId) : null;
|
|
106
|
+
if (!user) return c.json({ message: "Inicia sesión para continuar.", statusCode: 401 }, 401);
|
|
107
|
+
c.set("user", user);
|
|
108
|
+
await next();
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
nubot.get("/users/me", (c) => c.json(toPanelUser(c.get("user"))));
|
|
112
|
+
nubot.patch("/users/me/status", (c) => c.json({ ok: true }));
|
|
113
|
+
nubot.patch("/users/me/preferences", (c) => c.json({ ok: true }));
|
|
114
|
+
|
|
115
|
+
nubot.route("/", account);
|
|
116
|
+
nubot.route("/", channels);
|
|
117
|
+
nubot.route("/", inbox);
|
|
118
|
+
nubot.route("/", contacts);
|
|
119
|
+
nubot.route("/", uploads);
|
|
120
|
+
nubot.route("/", funnels);
|
|
121
|
+
nubot.route("/", aiUsage);
|
|
122
|
+
nubot.route("/", agents);
|
|
123
|
+
nubot.route("/", products);
|
|
124
|
+
nubot.route("/", team);
|
|
125
|
+
nubot.route("/", templates);
|
|
126
|
+
|
|
127
|
+
nubot.notFound((c) => c.json({ message: "Esta función está disponible en Nubot.", statusCode: 404, organizationId: ORG_ID }, 404));
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
import type { Env } from "../env";
|
|
4
4
|
import { publicLink, type StoredAttachment } from "../media/files";
|
|
5
5
|
import { ORG_ID } from "./session";
|
|
6
|
+
import { isPlatform, PLATFORM_NAMES, type Platform } from "../channels/accounts";
|
|
7
|
+
|
|
8
|
+
/** El tipo de canal con el que el panel elige el ícono. Messenger va por la página de Facebook. */
|
|
9
|
+
export const PANEL_CHANNEL_TYPE: Record<Platform, string> = { whatsapp: "WHATSAPP", instagram: "INSTAGRAM", facebook: "FACEBOOK" };
|
|
6
10
|
|
|
7
|
-
export const CHANNEL_ID = "whatsapp";
|
|
8
11
|
export const AGENT_ID = "lite-agent";
|
|
9
12
|
|
|
10
13
|
/** Los enlaces de archivos que ve el panel duran un día; el panel pide otro si vence. */
|
|
@@ -13,6 +16,7 @@ export const PANEL_LINK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
13
16
|
export interface ConversationRecord {
|
|
14
17
|
id: string;
|
|
15
18
|
contact_id: string;
|
|
19
|
+
channel: string;
|
|
16
20
|
status: "ai" | "human" | "closed";
|
|
17
21
|
assigned_user_id: string | null;
|
|
18
22
|
funnel_id: string | null;
|
|
@@ -56,6 +60,7 @@ const iso = (ms: number | null | undefined) => (ms ? new Date(ms).toISOString()
|
|
|
56
60
|
|
|
57
61
|
export const CONVERSATION_COLUMNS = [
|
|
58
62
|
"id",
|
|
63
|
+
"channel",
|
|
59
64
|
"contact_id",
|
|
60
65
|
"status",
|
|
61
66
|
"assigned_user_id",
|
|
@@ -91,7 +96,7 @@ export function toTicket(c: ConversationRecord) {
|
|
|
91
96
|
id: c.id,
|
|
92
97
|
orgId: ORG_ID,
|
|
93
98
|
contactId: c.contact_id,
|
|
94
|
-
channelId:
|
|
99
|
+
channelId: c.zernio_account_id,
|
|
95
100
|
status: ticketStatus(c),
|
|
96
101
|
assignedAgentId: c.status === "ai" ? null : c.assigned_user_id,
|
|
97
102
|
aiAgentId: c.status === "ai" ? AGENT_ID : null,
|
|
@@ -124,8 +129,10 @@ export function toContact(c: ContactRecord, tags: string[] = []) {
|
|
|
124
129
|
};
|
|
125
130
|
}
|
|
126
131
|
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
/** El canal de una conversación en la forma del panel. Su id es el de la cuenta en Zernio. */
|
|
133
|
+
export function channel(platform: string, accountId?: string | null) {
|
|
134
|
+
const p = isPlatform(platform) ? platform : "whatsapp";
|
|
135
|
+
return { id: accountId ?? p, name: PLATFORM_NAMES[p], type: PANEL_CHANNEL_TYPE[p], providerId: accountId ?? "", isActive: true, config: {} };
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
const SENDER: Record<MessageRecord["sender_type"], string> = {
|
|
@@ -220,7 +227,7 @@ export async function toConversations(env: Env, rows: ConversationRecord[], orig
|
|
|
220
227
|
return {
|
|
221
228
|
ticket: toTicket(c),
|
|
222
229
|
contact: contact ? toContact(contact, tagsByContact.get(c.contact_id)) : null,
|
|
223
|
-
channel: channel(c.zernio_account_id),
|
|
230
|
+
channel: channel(c.channel, c.zernio_account_id),
|
|
224
231
|
lastMessage: last ? await toMessage(env, last, origin) : undefined,
|
|
225
232
|
messages: [],
|
|
226
233
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import type { AppEnv } from "../http";
|
|
3
|
+
import { isPlatform, saveChannel } from "../channels/accounts";
|
|
4
|
+
|
|
5
|
+
export const channelsAdmin = new Hono<AppEnv>();
|
|
6
|
+
|
|
7
|
+
/** `npx nubot-lite whatsapp` registra aquí el número que eligió, para que el panel lo muestre en Conexiones. */
|
|
8
|
+
channelsAdmin.put("/", async (c) => {
|
|
9
|
+
const body = await c.req.json<{ platform?: string; accountId?: string; name?: string }>();
|
|
10
|
+
if (!isPlatform(body.platform) || !body.accountId) return c.json({ error: "Faltan platform y accountId." }, 400);
|
|
11
|
+
const row = await saveChannel(c.env, { platform: body.platform, accountId: body.accountId, name: body.name ?? null });
|
|
12
|
+
return c.json({ id: row.id, platform: row.platform, accountId: row.zernio_account_id });
|
|
13
|
+
});
|
|
@@ -1,100 +1,110 @@
|
|
|
1
|
-
import { Hono } from "hono";
|
|
2
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
-
import type { AppEnv } from "../http";
|
|
4
|
-
import { newId } from "../inbox/store";
|
|
5
|
-
import { loadAgentSettings } from "../agent/settings";
|
|
6
|
-
import { publicOrigin } from "../media/files";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
checks.push({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return { name: "Zernio", ok: false, detail:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
import type { AppEnv } from "../http";
|
|
4
|
+
import { newId } from "../inbox/store";
|
|
5
|
+
import { loadAgentSettings } from "../agent/settings";
|
|
6
|
+
import { publicOrigin } from "../media/files";
|
|
7
|
+
import { listChannels, PLATFORM_NAMES } from "../channels/accounts";
|
|
8
|
+
|
|
9
|
+
export const system = new Hono<AppEnv>();
|
|
10
|
+
|
|
11
|
+
type Check = { name: string; ok: boolean; detail: string };
|
|
12
|
+
|
|
13
|
+
/** Revisa todo lo que necesita la instalación. Lo usa `npx nubot-lite doctor`. */
|
|
14
|
+
system.get("/health", async (c) => {
|
|
15
|
+
const checks: Check[] = [];
|
|
16
|
+
const settings = await loadAgentSettings(c.env);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const users = await c.env.DB.prepare("SELECT COUNT(*) AS n FROM users").first<{ n: number }>();
|
|
20
|
+
checks.push({ name: "Base de datos", ok: true, detail: users?.n ? `${users.n} usuario(s)` : "Falta crear el administrador desde el panel" });
|
|
21
|
+
} catch (err) {
|
|
22
|
+
checks.push({ name: "Base de datos", ok: false, detail: `No responde o faltan migraciones: ${String(err)}` });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await c.env.FILES.put("doctor/check", "ok", { expirationTtl: 60 });
|
|
27
|
+
checks.push({ name: "Archivos (KV)", ok: (await c.env.FILES.get("doctor/check")) === "ok", detail: "Se puede guardar y leer" });
|
|
28
|
+
} catch (err) {
|
|
29
|
+
checks.push({ name: "Archivos (KV)", ok: false, detail: String(err) });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!c.env.ANTHROPIC_API_KEY) {
|
|
33
|
+
checks.push({ name: "Claude", ok: false, detail: "Falta conectar el agente. Corre: npx nubot-lite claude" });
|
|
34
|
+
} else {
|
|
35
|
+
try {
|
|
36
|
+
const model = await new Anthropic({ apiKey: c.env.ANTHROPIC_API_KEY }).models.retrieve(settings.model);
|
|
37
|
+
checks.push({ name: "Claude", ok: true, detail: `La llave funciona y el modelo ${model.id} está disponible` });
|
|
38
|
+
} catch (err) {
|
|
39
|
+
const detail = err instanceof Anthropic.AuthenticationError ? "La llave de Anthropic no es válida" : err instanceof Anthropic.NotFoundError ? `El modelo ${settings.model} no está disponible para esta llave` : String(err);
|
|
40
|
+
checks.push({ name: "Claude", ok: false, detail });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!c.env.ZERNIO_API_KEY) {
|
|
45
|
+
const detail = "Falta conectar WhatsApp. Corre: npx nubot-lite whatsapp";
|
|
46
|
+
checks.push({ name: "Zernio", ok: false, detail }, { name: "Webhook en Zernio", ok: false, detail });
|
|
47
|
+
} else {
|
|
48
|
+
checks.push(await zernioCheck(c.env), await webhookCheck(c.env, `${new URL(c.req.url).origin}/webhooks/zernio`));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const connected = await listChannels(c.env);
|
|
52
|
+
checks.push({
|
|
53
|
+
name: "Canales",
|
|
54
|
+
ok: connected.length > 0,
|
|
55
|
+
detail: connected.length
|
|
56
|
+
? connected.map((ch) => `${PLATFORM_NAMES[ch.platform]}${ch.username ? ` @${ch.username}` : ch.name && ch.name !== PLATFORM_NAMES[ch.platform] ? ` (${ch.name})` : ""}`).join(", ")
|
|
57
|
+
: "Ningún canal conectado. Corre: npx nubot-lite whatsapp",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
checks.push({ name: "Notas de voz", ok: !!c.env.AI, detail: c.env.AI ? "Workers AI disponible" : "Sin Workers AI: las notas de voz no se transcriben" });
|
|
61
|
+
const origin = await publicOrigin(c.env);
|
|
62
|
+
checks.push({ name: "Webhook", ok: !!origin, detail: origin ? `Zernio ya escribió a ${origin}` : "Todavía no llegó ningún mensaje de WhatsApp" });
|
|
63
|
+
|
|
64
|
+
return c.json({ ok: checks.every((x) => x.ok), checks });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const unreachable = (err: unknown) => `No pude conectar con Zernio: ${err instanceof Error ? err.message : String(err)}`;
|
|
68
|
+
|
|
69
|
+
/** La llave de Zernio y el número conectado. El número de prueba compartido no está entre las cuentas: sale en sus números. */
|
|
70
|
+
async function zernioCheck(env: AppEnv["Bindings"]): Promise<Check> {
|
|
71
|
+
try {
|
|
72
|
+
const headers = { Authorization: `Bearer ${env.ZERNIO_API_KEY}` };
|
|
73
|
+
const res = await fetch(`${env.ZERNIO_API_BASE}/v1/accounts?platform=whatsapp`, { headers });
|
|
74
|
+
if (!res.ok) return { name: "Zernio", ok: false, detail: `La API key de Zernio no funciona (${res.status}). Corre otra vez: npx nubot-lite whatsapp` };
|
|
75
|
+
const { accounts = [] } = (await res.json().catch(() => ({}))) as { accounts?: { username?: string; displayName?: string }[] };
|
|
76
|
+
const names = accounts.map((a) => a.displayName ?? a.username ?? "WhatsApp");
|
|
77
|
+
const numbers = await fetch(`${env.ZERNIO_API_BASE}/v1/phone-numbers`, { headers }).catch(() => null);
|
|
78
|
+
const { sandbox } = numbers?.ok ? ((await numbers.json().catch(() => ({}))) as { sandbox?: { phoneNumber?: string } }) : {};
|
|
79
|
+
if (sandbox?.phoneNumber) names.push(`número de prueba ${sandbox.phoneNumber}`);
|
|
80
|
+
return names.length
|
|
81
|
+
? { name: "Zernio", ok: true, detail: `WhatsApp conectado: ${names.join(", ")}` }
|
|
82
|
+
: { name: "Zernio", ok: false, detail: "La llave funciona pero no hay un número de WhatsApp conectado en Zernio" };
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return { name: "Zernio", ok: false, detail: unreachable(err) };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function webhookCheck(env: AppEnv["Bindings"], expected: string): Promise<Check> {
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(`${env.ZERNIO_API_BASE}/v1/webhooks/settings`, { headers: { Authorization: `Bearer ${env.ZERNIO_API_KEY}` } });
|
|
91
|
+
const body = (await res.json().catch(() => ({}))) as { webhooks?: { url: string; isActive?: boolean }[] };
|
|
92
|
+
const found = (body.webhooks ?? []).find((w) => w.url === expected);
|
|
93
|
+
return {
|
|
94
|
+
name: "Webhook en Zernio",
|
|
95
|
+
ok: !!found?.isActive,
|
|
96
|
+
detail: found ? (found.isActive ? `Registrado: ${expected}` : "Registrado pero desactivado") : `Falta registrar ${expected}. Corre: npx nubot-lite whatsapp`,
|
|
97
|
+
};
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return { name: "Webhook en Zernio", ok: false, detail: unreachable(err) };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Conversación de prueba con el agente real. Cuerpo: { messages: ["hola", "¿tienen depas en Surco?"] } */
|
|
104
|
+
system.post("/agent/test", async (c) => {
|
|
105
|
+
const { messages } = await c.req.json<{ messages?: string[] }>();
|
|
106
|
+
const turns = (messages ?? []).map((m) => String(m).trim()).filter(Boolean).slice(0, 10);
|
|
107
|
+
if (!turns.length) return c.json({ error: "Manda al menos un mensaje de cliente." }, 400);
|
|
108
|
+
const stub = c.env.CONVERSATION.get(c.env.CONVERSATION.idFromName(`prueba:${newId()}`));
|
|
109
|
+
return c.json(await stub.sandbox(turns));
|
|
110
|
+
});
|