@datasynx/agentic-crm 0.1.0 → 1.0.0

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.
Files changed (286) hide show
  1. package/README.md +264 -670
  2. package/dist/{approvals-DpjxGHFp.js → approvals-CmDT2eUg.js} +7 -24
  3. package/dist/approvals-CmDT2eUg.js.map +1 -0
  4. package/dist/{ask-CID3jnuL.js → ask-D8iYqDAr.js} +6 -6
  5. package/dist/{ask-CID3jnuL.js.map → ask-D8iYqDAr.js.map} +1 -1
  6. package/dist/atomic-write-8yjqqLtS.js +29 -0
  7. package/dist/atomic-write-8yjqqLtS.js.map +1 -0
  8. package/dist/atomic-write-BYmF-ThH.cjs +37 -0
  9. package/dist/atomic-write-BYmF-ThH.cjs.map +1 -0
  10. package/dist/auth-B5DcjJ_6.js +2 -0
  11. package/dist/{auth-DFWwWcYD.js → auth-DDXZTwS0.js} +4 -13
  12. package/dist/auth-DDXZTwS0.js.map +1 -0
  13. package/dist/{autofill-Di_-SP7t.js → autofill-B9VtlR2j.js} +2 -2
  14. package/dist/{autofill-Di_-SP7t.js.map → autofill-B9VtlR2j.js.map} +1 -1
  15. package/dist/{backup-CeMk9z86.js → backup-CTlIxUdO.js} +5 -7
  16. package/dist/backup-CTlIxUdO.js.map +1 -0
  17. package/dist/backup-LFnC09oV.js +2 -0
  18. package/dist/{churn-C28IgnAj.js → churn-DN9WDGNM.js} +3 -3
  19. package/dist/{churn-C28IgnAj.js.map → churn-DN9WDGNM.js.map} +1 -1
  20. package/dist/cli.js +282 -184
  21. package/dist/cli.js.map +1 -1
  22. package/dist/{compliance-CKSBoQUe.js → compliance-Bc12Hn9a.js} +3 -3
  23. package/dist/{compliance-CKSBoQUe.js.map → compliance-Bc12Hn9a.js.map} +1 -1
  24. package/dist/{compliance-CujOqAKk.js → compliance-TqYQXhBj.js} +1 -1
  25. package/dist/{compliance-B1kk5-YS.js → compliance-kq0xHRw3.js} +3 -3
  26. package/dist/{compliance-B1kk5-YS.js.map → compliance-kq0xHRw3.js.map} +1 -1
  27. package/dist/{compliance-B91zNvCR.cjs → compliance-pAj9FcGI.cjs} +3 -3
  28. package/dist/{compliance-B91zNvCR.cjs.map → compliance-pAj9FcGI.cjs.map} +1 -1
  29. package/dist/{context-builder-BzWAp3Zs.js → context-builder-7Uab5-G4.js} +3 -2
  30. package/dist/context-builder-7Uab5-G4.js.map +1 -0
  31. package/dist/context-builder-hmOPvgso.js +2 -0
  32. package/dist/{custom-fields-CzNeD3_v.js → custom-fields-BMyz5Ruh.js} +1 -1
  33. package/dist/{custom-fields-Pl2t9xzp.js → custom-fields-GzpOHW_2.js} +4 -13
  34. package/dist/custom-fields-GzpOHW_2.js.map +1 -0
  35. package/dist/{custom-objects-CIFrmQ2V.js → custom-objects-BNy-ayR-.js} +1 -1
  36. package/dist/{custom-objects-BHgn1GEX.js → custom-objects-CxW1gHwJ.js} +10 -25
  37. package/dist/custom-objects-CxW1gHwJ.js.map +1 -0
  38. package/dist/{customer-dir-DIylZ8Q6.js → customer-dir-CkMMXhb0.js} +9 -4
  39. package/dist/customer-dir-CkMMXhb0.js.map +1 -0
  40. package/dist/daemon/worker.js +66 -40
  41. package/dist/daemon/worker.js.map +1 -1
  42. package/dist/doctor-C14-vnJ1.js +103 -0
  43. package/dist/doctor-C14-vnJ1.js.map +1 -0
  44. package/dist/{enrichment-3XvgGDfB.js → enrichment-CDFdWmvD.js} +3 -3
  45. package/dist/{enrichment-3XvgGDfB.js.map → enrichment-CDFdWmvD.js.map} +1 -1
  46. package/dist/{file-lock-B_zi7NQl.js → file-lock-CcHotQkZ.js} +3 -4
  47. package/dist/file-lock-CcHotQkZ.js.map +1 -0
  48. package/dist/{gmail-sync-rQaVqKWd.js → gmail-sync-C-NmibzS.js} +13 -9
  49. package/dist/gmail-sync-C-NmibzS.js.map +1 -0
  50. package/dist/{gmail-sync-DIaxInDT.js → gmail-sync-DueE6tl5.js} +14 -10
  51. package/dist/gmail-sync-DueE6tl5.js.map +1 -0
  52. package/dist/{gmail-sync-hHm9gaWd.cjs → gmail-sync-GEy3oVvw.cjs} +13 -9
  53. package/dist/gmail-sync-GEy3oVvw.cjs.map +1 -0
  54. package/dist/{gmail-webhook-handler-DS7OlRPX.js → gmail-webhook-handler-B26COilD.js} +2 -2
  55. package/dist/{gmail-webhook-handler-e5Od25FX.js → gmail-webhook-handler-kGKpbY9h.js} +4 -4
  56. package/dist/{gmail-webhook-handler-e5Od25FX.js.map → gmail-webhook-handler-kGKpbY9h.js.map} +1 -1
  57. package/dist/{goal-engine-KpBftn4V.js → goal-engine-BbroPhqm.js} +10 -11
  58. package/dist/goal-engine-BbroPhqm.js.map +1 -0
  59. package/dist/{goal-engine-CUZSpERI.js → goal-engine-CfDAJTFt.js} +1 -1
  60. package/dist/{google-drive-sync-DEPcqFca.js → google-drive-sync-D1n7WKZn.js} +3 -3
  61. package/dist/{google-drive-sync-DEPcqFca.js.map → google-drive-sync-D1n7WKZn.js.map} +1 -1
  62. package/dist/{hygiene-DZqfYpFf.js → hygiene-DzQPnc6P.js} +3 -3
  63. package/dist/{hygiene-DZqfYpFf.js.map → hygiene-DzQPnc6P.js.map} +1 -1
  64. package/dist/identity-CB7j-Zr1.js +2 -0
  65. package/dist/{identity-CI6olMNm.js → identity-_uZ3Lbr2.js} +2 -2
  66. package/dist/{identity-CI6olMNm.js.map → identity-_uZ3Lbr2.js.map} +1 -1
  67. package/dist/{import-hubspot-BaK71U_K.js → import-hubspot-DB4n89jy.js} +51 -45
  68. package/dist/import-hubspot-DB4n89jy.js.map +1 -0
  69. package/dist/{index-V8BFaH-b.d.ts → index-B0IMMrp_.d.ts} +8 -4
  70. package/dist/{index-V8BFaH-b.d.ts.map → index-B0IMMrp_.d.ts.map} +1 -1
  71. package/dist/{index-YqwMd6aQ.d.cts → index-pY7tYXwH.d.cts} +8 -4
  72. package/dist/{index-YqwMd6aQ.d.cts.map → index-pY7tYXwH.d.cts.map} +1 -1
  73. package/dist/index.cjs +19 -21
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.d.cts +8 -4
  76. package/dist/index.d.cts.map +1 -1
  77. package/dist/index.d.ts +8 -4
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +19 -21
  80. package/dist/index.js.map +1 -1
  81. package/dist/{interactions-writer-DO3KcSR3.js → interactions-writer-BZzUIgJd.js} +5 -2
  82. package/dist/interactions-writer-BZzUIgJd.js.map +1 -0
  83. package/dist/{interactions-writer-SLHnoEeE.js → interactions-writer-DbSyI2rt.js} +32 -3
  84. package/dist/interactions-writer-DbSyI2rt.js.map +1 -0
  85. package/dist/interactions-writer-RJB8SWf2.js +2 -0
  86. package/dist/{interactions-writer-CrPStUll.cjs → interactions-writer-a2yzBd7T.cjs} +5 -2
  87. package/dist/interactions-writer-a2yzBd7T.cjs.map +1 -0
  88. package/dist/json-store-WWsFzXub.js +43 -0
  89. package/dist/json-store-WWsFzXub.js.map +1 -0
  90. package/dist/{knowledge-base-D0Fh40kc.js → knowledge-base-DHNc4hVj.js} +43 -16
  91. package/dist/knowledge-base-DHNc4hVj.js.map +1 -0
  92. package/dist/{lancedb-CCBbpulq.js → lancedb-CswQEE5K.js} +1 -1
  93. package/dist/{lancedb-rlvWoPwl.js → lancedb-CuHKNsNZ.js} +4 -3
  94. package/dist/lancedb-CuHKNsNZ.js.map +1 -0
  95. package/dist/{lead-model-BCFzyktm.js → lead-model-CEmx7te7.js} +6 -14
  96. package/dist/lead-model-CEmx7te7.js.map +1 -0
  97. package/dist/{llm-Z8RIYkpF.js → llm-BnSUBisu.js} +2 -2
  98. package/dist/{llm-Z8RIYkpF.js.map → llm-BnSUBisu.js.map} +1 -1
  99. package/dist/{llm-iijeXmgq.cjs → llm-CXycmEl9.cjs} +2 -2
  100. package/dist/{llm-iijeXmgq.cjs.map → llm-CXycmEl9.cjs.map} +1 -1
  101. package/dist/{llm-DEjWcqmW.js → llm-DSX1-wFu.js} +1 -1
  102. package/dist/{llm-DvzZqva0.js → llm-PZzgPphl.js} +3 -3
  103. package/dist/{llm-DvzZqva0.js.map → llm-PZzgPphl.js.map} +1 -1
  104. package/dist/logger-BkInaGoV.cjs +167 -0
  105. package/dist/logger-BkInaGoV.cjs.map +1 -0
  106. package/dist/logger-Dyl4VcLO.js +147 -0
  107. package/dist/logger-Dyl4VcLO.js.map +1 -0
  108. package/dist/logger-UaF5p9d1.js +147 -0
  109. package/dist/logger-UaF5p9d1.js.map +1 -0
  110. package/dist/logger-vKQS34w9.js +2 -0
  111. package/dist/mcp-CdTJWTJf.d.cts.map +1 -1
  112. package/dist/mcp-CdTJWTJf.d.ts.map +1 -1
  113. package/dist/mcp.cjs +327 -303
  114. package/dist/mcp.cjs.map +1 -1
  115. package/dist/mcp.d.cts.map +1 -1
  116. package/dist/mcp.d.ts.map +1 -1
  117. package/dist/mcp.js +327 -303
  118. package/dist/mcp.js.map +1 -1
  119. package/dist/{memory-Cy6-Tbyl.js → memory-D8hmgD9d.js} +1 -1
  120. package/dist/{memory-Bb6ky3kb.js → memory-Dzr9dXSM.js} +4 -11
  121. package/dist/memory-Dzr9dXSM.js.map +1 -0
  122. package/dist/{microsoft-calendar-B6MMtUQK.js → microsoft-calendar-jIu9K5zX.js} +4 -4
  123. package/dist/{microsoft-calendar-B6MMtUQK.js.map → microsoft-calendar-jIu9K5zX.js.map} +1 -1
  124. package/dist/{microsoft-sync-CpZVoSuq.js → microsoft-sync-R_r8HL-B.js} +5 -5
  125. package/dist/{microsoft-sync-CpZVoSuq.js.map → microsoft-sync-R_r8HL-B.js.map} +1 -1
  126. package/dist/{nba-3wanmJ0U.js → nba-mTJ4yEqD.js} +3 -3
  127. package/dist/{nba-3wanmJ0U.js.map → nba-mTJ4yEqD.js.map} +1 -1
  128. package/dist/{notification-dispatcher-0vYNngWe.js → notification-dispatcher-inpKyuBz.js} +7 -3
  129. package/dist/notification-dispatcher-inpKyuBz.js.map +1 -0
  130. package/dist/{pipeline-writer-BqBrYrQc.js → pipeline-writer-0LJ6Qkat.js} +1 -1
  131. package/dist/{pipeline-writer-N2omexxp.cjs → pipeline-writer-B1tRAhuD.cjs} +11 -3
  132. package/dist/pipeline-writer-B1tRAhuD.cjs.map +1 -0
  133. package/dist/{pipeline-writer-BvVquKIe.js → pipeline-writer-CIllfnZl.js} +5 -3
  134. package/dist/pipeline-writer-CIllfnZl.js.map +1 -0
  135. package/dist/{pipeline-writer-eufx_0o1.js → pipeline-writer-rDj-ni6q.js} +6 -4
  136. package/dist/pipeline-writer-rDj-ni6q.js.map +1 -0
  137. package/dist/{proactive-agent-BgQXw3ac.js → proactive-agent-B7u3Bj_l.js} +6 -6
  138. package/dist/{proactive-agent-BgQXw3ac.js.map → proactive-agent-B7u3Bj_l.js.map} +1 -1
  139. package/dist/{proactive-worker-BrLHNhjH.js → proactive-worker-1zkm6aJD.js} +7 -8
  140. package/dist/proactive-worker-1zkm6aJD.js.map +1 -0
  141. package/dist/{push-manager-CowY-0IK.js → push-manager-BXM-IHfP.js} +1 -1
  142. package/dist/{push-manager-CdqIIkuh.js → push-manager-C0ECQgva.js} +4 -4
  143. package/dist/push-manager-C0ECQgva.js.map +1 -0
  144. package/dist/{quote-generator-OhSFsi3x.js → quote-generator-ByUyIYtw.js} +1 -1
  145. package/dist/{quote-generator-BfwENXzg.js → quote-generator-CTdR8eEI.js} +5 -5
  146. package/dist/quote-generator-CTdR8eEI.js.map +1 -0
  147. package/dist/rbac-DzbyFhVH.js +2 -0
  148. package/dist/{rbac-CTIktZaC.js → rbac-msmBc_tK.js} +19 -12
  149. package/dist/rbac-msmBc_tK.js.map +1 -0
  150. package/dist/regex-Jt5DatPi.js +13 -0
  151. package/dist/regex-Jt5DatPi.js.map +1 -0
  152. package/dist/{relationship-health-odxEoQdJ.js → relationship-health-ZZNXR1RZ.js} +8 -16
  153. package/dist/relationship-health-ZZNXR1RZ.js.map +1 -0
  154. package/dist/{revenue-simulation-Bqf2DLVB.js → revenue-simulation-D8f_YkUY.js} +9 -19
  155. package/dist/revenue-simulation-D8f_YkUY.js.map +1 -0
  156. package/dist/{revenue-simulation-BJdRTEHc.js → revenue-simulation-njJZlTqm.js} +1 -1
  157. package/dist/safe-path-mpp0dKtO.js +18 -0
  158. package/dist/safe-path-mpp0dKtO.js.map +1 -0
  159. package/dist/{segments-BqcD5HIl.js → segments-DI3LOQNe.js} +5 -14
  160. package/dist/segments-DI3LOQNe.js.map +1 -0
  161. package/dist/sequence-engine-C6nnewHX.js +2 -0
  162. package/dist/{sequence-engine-J1lTW_in.js → sequence-engine-DNTVLq7o.js} +15 -8
  163. package/dist/sequence-engine-DNTVLq7o.js.map +1 -0
  164. package/dist/{sequence-store-DaaWr0Os.js → sequence-store-CmYb6s0g.js} +6 -5
  165. package/dist/sequence-store-CmYb6s0g.js.map +1 -0
  166. package/dist/{server-Dyva03K8.js → server-DqSMYhSA.js} +278 -220
  167. package/dist/server-DqSMYhSA.js.map +1 -0
  168. package/dist/{session-D9ub6Wl1.js → session-B6XaP83h.js} +3 -3
  169. package/dist/session-B6XaP83h.js.map +1 -0
  170. package/dist/{session-B9AilxOE.js → session-BgGDyP2C.js} +3 -3
  171. package/dist/session-BgGDyP2C.js.map +1 -0
  172. package/dist/session-Bp4zTh4l.js +2 -0
  173. package/dist/{session-D0qFkBla.cjs → session-Mm7GQbSH.cjs} +3 -3
  174. package/dist/session-Mm7GQbSH.cjs.map +1 -0
  175. package/dist/{session-store-C8tEvMPw.js → session-store-DWxJ5Pof.js} +79 -17
  176. package/dist/session-store-DWxJ5Pof.js.map +1 -0
  177. package/dist/{session-store-B0QZE8Bx.cjs → session-store-yfwnj0OC.cjs} +126 -16
  178. package/dist/session-store-yfwnj0OC.cjs.map +1 -0
  179. package/dist/{sla-engine-5IhTsBUR.js → sla-engine-CP2KiKDS.js} +1 -1
  180. package/dist/{sla-engine-BqX-7u-7.js → sla-engine-O-A1ntu_.js} +2 -2
  181. package/dist/{sla-engine-BqX-7u-7.js.map → sla-engine-O-A1ntu_.js.map} +1 -1
  182. package/dist/{sop-Vp0UPWFW.js → sop-BV7ICAFR.js} +4 -11
  183. package/dist/sop-BV7ICAFR.js.map +1 -0
  184. package/dist/{sop-DkhVChGy.js → sop-D33qTHUb.js} +1 -1
  185. package/dist/survey-engine-DKctGcLQ.js +2 -0
  186. package/dist/{survey-engine-DBjCYqCv.js → survey-engine-DngXBv47.js} +5 -4
  187. package/dist/survey-engine-DngXBv47.js.map +1 -0
  188. package/dist/{sync-state-CwLSt_1m.js → sync-state-BaA8LbTI.js} +1 -1
  189. package/dist/{sync-state-ChaLbamC.js → sync-state-DMZgzpez.js} +4 -12
  190. package/dist/sync-state-DMZgzpez.js.map +1 -0
  191. package/dist/{ticket-writer-CjqKeIRD.js → ticket-writer-DsfpeLGZ.js} +1 -1
  192. package/dist/{ticket-writer-j2oX_Wal.js → ticket-writer-a9on36Wb.js} +12 -24
  193. package/dist/ticket-writer-a9on36Wb.js.map +1 -0
  194. package/dist/{tone-Bdm5uaht.js → tone-C7bqK69y.js} +5 -12
  195. package/dist/tone-C7bqK69y.js.map +1 -0
  196. package/dist/{tone-DRKlZgPr.cjs → tone-Cmc7O2Fx.cjs} +3 -9
  197. package/dist/tone-Cmc7O2Fx.cjs.map +1 -0
  198. package/dist/{tone-vNb2DAAD.js → tone-mXSftvTn.js} +3 -8
  199. package/dist/tone-mXSftvTn.js.map +1 -0
  200. package/dist/{transcript-watcher-CL2QUygI.js → transcript-watcher-0mh2ZhmH.js} +18 -11
  201. package/dist/transcript-watcher-0mh2ZhmH.js.map +1 -0
  202. package/dist/unmatched-transcripts-C92zAoM4.js +2 -0
  203. package/dist/unmatched-transcripts-DC-VQ9YS.js +16 -0
  204. package/dist/unmatched-transcripts-DC-VQ9YS.js.map +1 -0
  205. package/dist/update-deal-CWy1eLJI.js +2 -0
  206. package/dist/{update-deal-DKC79skb.js → update-deal-DSzr_Aau.js} +3 -3
  207. package/dist/{update-deal-DKC79skb.js.map → update-deal-DSzr_Aau.js.map} +1 -1
  208. package/dist/{usage-D0-TYJkw.js → usage-BVlFlKW_.js} +8 -6
  209. package/dist/usage-BVlFlKW_.js.map +1 -0
  210. package/dist/usage-CClTf5e6.cjs.map +1 -1
  211. package/dist/usage-D0u9a-lV.js.map +1 -1
  212. package/dist/{vault-DXCg29W-.js → vault-CfwZdNzC.js} +3 -4
  213. package/dist/vault-CfwZdNzC.js.map +1 -0
  214. package/dist/{vault-C1D3zScD.js → vault-DxKP4_R2.js} +1 -1
  215. package/dist/{webhooks-Xn6zO6kd.cjs → webhooks-CwW-3kvG.cjs} +5 -19
  216. package/dist/webhooks-CwW-3kvG.cjs.map +1 -0
  217. package/dist/{webhooks-7EpA05Qr.js → webhooks-DXr1IoKn.js} +8 -21
  218. package/dist/webhooks-DXr1IoKn.js.map +1 -0
  219. package/dist/{webhooks-BO2UAnmn.js → webhooks-sWZ8CJtR.js} +5 -18
  220. package/dist/webhooks-sWZ8CJtR.js.map +1 -0
  221. package/package.json +11 -2
  222. package/dist/approvals-DpjxGHFp.js.map +0 -1
  223. package/dist/auth-CyFuu9X_.js +0 -2
  224. package/dist/auth-DFWwWcYD.js.map +0 -1
  225. package/dist/backup-CeMk9z86.js.map +0 -1
  226. package/dist/backup-f_hC7rBV.js +0 -2
  227. package/dist/context-builder-BzWAp3Zs.js.map +0 -1
  228. package/dist/context-builder-DlrRcqmJ.js +0 -2
  229. package/dist/custom-fields-Pl2t9xzp.js.map +0 -1
  230. package/dist/custom-objects-BHgn1GEX.js.map +0 -1
  231. package/dist/customer-dir-DIylZ8Q6.js.map +0 -1
  232. package/dist/file-lock-B_zi7NQl.js.map +0 -1
  233. package/dist/gmail-sync-DIaxInDT.js.map +0 -1
  234. package/dist/gmail-sync-hHm9gaWd.cjs.map +0 -1
  235. package/dist/gmail-sync-rQaVqKWd.js.map +0 -1
  236. package/dist/goal-engine-KpBftn4V.js.map +0 -1
  237. package/dist/identity-gyfWdrcX.js +0 -2
  238. package/dist/import-hubspot-BaK71U_K.js.map +0 -1
  239. package/dist/interactions-writer-CrPStUll.cjs.map +0 -1
  240. package/dist/interactions-writer-DO3KcSR3.js.map +0 -1
  241. package/dist/interactions-writer-SLHnoEeE.js.map +0 -1
  242. package/dist/interactions-writer-dSPy1XfO.js +0 -2
  243. package/dist/knowledge-base-D0Fh40kc.js.map +0 -1
  244. package/dist/lancedb-rlvWoPwl.js.map +0 -1
  245. package/dist/lead-model-BCFzyktm.js.map +0 -1
  246. package/dist/memory-Bb6ky3kb.js.map +0 -1
  247. package/dist/notification-dispatcher-0vYNngWe.js.map +0 -1
  248. package/dist/pipeline-writer-BvVquKIe.js.map +0 -1
  249. package/dist/pipeline-writer-N2omexxp.cjs.map +0 -1
  250. package/dist/pipeline-writer-eufx_0o1.js.map +0 -1
  251. package/dist/proactive-worker-BrLHNhjH.js.map +0 -1
  252. package/dist/push-manager-CdqIIkuh.js.map +0 -1
  253. package/dist/quote-generator-BfwENXzg.js.map +0 -1
  254. package/dist/rbac-C7c8tcES.js +0 -2
  255. package/dist/rbac-CTIktZaC.js.map +0 -1
  256. package/dist/relationship-health-odxEoQdJ.js.map +0 -1
  257. package/dist/revenue-simulation-Bqf2DLVB.js.map +0 -1
  258. package/dist/segments-BqcD5HIl.js.map +0 -1
  259. package/dist/sequence-engine-CCTHEBgi.js +0 -2
  260. package/dist/sequence-engine-J1lTW_in.js.map +0 -1
  261. package/dist/sequence-store-DaaWr0Os.js.map +0 -1
  262. package/dist/server-Dyva03K8.js.map +0 -1
  263. package/dist/session-B9AilxOE.js.map +0 -1
  264. package/dist/session-D0qFkBla.cjs.map +0 -1
  265. package/dist/session-D9ub6Wl1.js.map +0 -1
  266. package/dist/session-mWHA71Lw.js +0 -2
  267. package/dist/session-store-B0QZE8Bx.cjs.map +0 -1
  268. package/dist/session-store-C8tEvMPw.js.map +0 -1
  269. package/dist/sop-Vp0UPWFW.js.map +0 -1
  270. package/dist/survey-engine-C06hcQt3.js +0 -2
  271. package/dist/survey-engine-DBjCYqCv.js.map +0 -1
  272. package/dist/sync-state-ChaLbamC.js.map +0 -1
  273. package/dist/ticket-writer-j2oX_Wal.js.map +0 -1
  274. package/dist/tone-Bdm5uaht.js.map +0 -1
  275. package/dist/tone-DRKlZgPr.cjs.map +0 -1
  276. package/dist/tone-vNb2DAAD.js.map +0 -1
  277. package/dist/transcript-watcher-CL2QUygI.js.map +0 -1
  278. package/dist/unmatched-transcripts-BsH5bhkU.js +0 -26
  279. package/dist/unmatched-transcripts-BsH5bhkU.js.map +0 -1
  280. package/dist/unmatched-transcripts-D0PrJ9iz.js +0 -2
  281. package/dist/update-deal-BNwPGaTV.js +0 -2
  282. package/dist/usage-D0-TYJkw.js.map +0 -1
  283. package/dist/vault-DXCg29W-.js.map +0 -1
  284. package/dist/webhooks-7EpA05Qr.js.map +0 -1
  285. package/dist/webhooks-BO2UAnmn.js.map +0 -1
  286. package/dist/webhooks-Xn6zO6kd.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-sync-C-NmibzS.js","names":["gmail","gmailApi"],"sources":["../src/schemas/agent-config.ts","../src/core/agent-notifier.ts","../src/sync/gmail-sync.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const AgentConfigSchema = z.object({\n slug: z.string().min(1),\n channel: z.enum([\"telegram\"]),\n wakeOn: z.array(z.enum([\"email\", \"calendar\"])).default([\"email\"]),\n createdAt: z.string(),\n lastWake: z.string().nullable().default(null),\n telegramChatId: z.string().optional(),\n});\n\nexport type AgentConfig = z.infer<typeof AgentConfigSchema>;\n","// src/core/agent-notifier.ts\n// Sends a Telegram wake notification when a new inbound email from a customer\n// domain is detected and an agent config exists for that customer slug.\n// All errors are swallowed — this is a notification feature and must never\n// crash the core loop.\n\nimport fs from \"fs\";\nimport https from \"https\";\nimport path from \"path\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\nimport { AgentConfigSchema, type AgentConfig } from \"../schemas/agent-config.js\";\nimport { summarizeEmail } from \"./llm.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface WakeContext {\n trigger: \"email\" | \"calendar\";\n subject: string;\n from: string;\n snippet: string;\n}\n\n// ─── Agent config helpers ─────────────────────────────────────────────────────\n\nfunction agentConfigPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n}\n\nfunction readAgentConfig(dataDir: string, slug: string): AgentConfig | null {\n const p = agentConfigPath(dataDir, slug);\n if (!fs.existsSync(p)) return null;\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n const result = AgentConfigSchema.safeParse(raw);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nfunction writeLastWake(dataDir: string, slug: string, config: AgentConfig): void {\n const p = agentConfigPath(dataDir, slug);\n try {\n const updated: AgentConfig = { ...config, lastWake: new Date().toISOString() };\n writeJsonFile(p, updated);\n } catch {\n // non-fatal — just a housekeeping write\n }\n}\n\n// ─── Telegram transport ───────────────────────────────────────────────────────\n\nfunction sendTelegramMessage(token: string, chatId: string, text: string): Promise<void> {\n const body = JSON.stringify({ chat_id: chatId, text, parse_mode: \"Markdown\" });\n return new Promise<void>((resolve, reject) => {\n const req = https.request(\n `https://api.telegram.org/bot${token}/sendMessage`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n },\n },\n (res) => {\n res.resume();\n resolve();\n }\n );\n req.on(\"error\", reject);\n req.write(body);\n req.end();\n });\n}\n\n// ─── Message builder ──────────────────────────────────────────────────────────\n\nfunction buildWakeMessage(\n slug: string,\n subject: string,\n summary: string,\n nextSteps: string[]\n): string {\n const suggestedAction = nextSteps[0] ?? \"Follow up within 24h\";\n return (\n `📧 New email from **${slug}**: ${subject}\\n` +\n `${summary}\\n\\n` +\n `💡 Suggested action: ${suggestedAction}`\n );\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget notification: reads the agent config for `slug`, summarises\n * the inbound email with the LLM, and sends a Telegram message.\n *\n * Silently returns (no throw) when:\n * - no agent config exists for the slug\n * - TELEGRAM_BOT_TOKEN env var is not set\n * - no chat id is available (neither in config nor in TELEGRAM_CHAT_ID env var)\n * - any HTTPS / LLM error occurs\n */\nexport async function notifyAgentWake(\n dataDir: string,\n slug: string,\n context: WakeContext\n): Promise<void> {\n try {\n // 1. Read agent config — bail silently if not found\n const config = readAgentConfig(dataDir, slug);\n if (!config) return;\n\n // 2. Check for Telegram token — bail silently if absent\n const token = process.env[\"TELEGRAM_BOT_TOKEN\"];\n if (!token) return;\n\n // 3. Determine chat id — config takes precedence, fallback to env var\n const chatId = config.telegramChatId ?? process.env[\"TELEGRAM_CHAT_ID\"];\n if (!chatId) return;\n\n // 4. Summarise the email (LLM, with fallback built into summarizeEmail itself)\n const emailSummary = await summarizeEmail(context.subject, context.snippet, context.from);\n\n // 5. Build and send the Telegram message\n const text = buildWakeMessage(\n slug,\n context.subject,\n emailSummary.summary,\n emailSummary.nextSteps\n );\n await sendTelegramMessage(token, chatId, text);\n\n // 6. Update lastWake on success\n writeLastWake(dataDir, slug, config);\n } catch {\n // Swallow all errors — this is a notification feature, never crashes core loop\n }\n}\n","// src/sync/gmail-sync.ts\nimport fs from \"fs\";\nimport path from \"path\";\nimport { gmail as gmailApi, type gmail_v1 } from \"@googleapis/gmail\";\nimport type { OAuth2Client } from \"google-auth-library\";\nimport { readInteractions, appendInteraction } from \"../fs/interactions-writer.js\";\nimport { notifyAgentWake } from \"../core/agent-notifier.js\";\nimport { logger } from \"../core/logger.js\";\n\ninterface SyncOptions {\n slug: string;\n dataDir: string;\n auth: OAuth2Client;\n query: string;\n since?: Date;\n maxPages?: number;\n}\n\n/**\n * Retry a function with exponential backoff on any error.\n * Delays: 1s, 2s, 4s, 8s … (2^attempt seconds), up to maxRetries retries.\n */\nexport async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n const delayMs = 1000 * Math.pow(2, attempt);\n await sleep(delayMs);\n attempt++;\n }\n }\n}\n\nexport async function syncGmail(opts: SyncOptions): Promise<{ synced: number; skipped: number }> {\n const gmail = gmailApi({ version: \"v1\", auth: opts.auth });\n const maxPages = opts.maxPages ?? 5;\n\n let q = opts.query;\n if (opts.since) {\n const after = Math.floor(opts.since.getTime() / 1000);\n q += ` after:${after}`;\n }\n\n // Collect all message stubs across pages (Task A — pagination)\n const allMessages: Array<{ id?: string | null; threadId?: string | null }> = [];\n let pageToken: string | undefined = undefined;\n let pagesFetched = 0;\n\n do {\n const listResp: { data: gmail_v1.Schema$ListMessagesResponse } =\n await gmail.users.messages.list({\n userId: \"me\",\n q,\n maxResults: 200,\n ...(pageToken ? { pageToken } : {}),\n });\n const pageMessages = listResp.data.messages ?? [];\n allMessages.push(...pageMessages);\n pageToken = listResp.data.nextPageToken ?? undefined;\n pagesFetched++;\n } while (pageToken && pagesFetched < maxPages);\n\n // Read existing interactions once before the loop — avoids O(messages) file reads\n let existingContent = await readInteractions(opts.dataDir, opts.slug);\n\n let synced = 0;\n let skipped = 0;\n\n for (const msg of allMessages) {\n if (!msg.id) continue;\n\n const source = `gmail://thread/${msg.threadId ?? msg.id}`;\n\n if (existingContent.includes(source)) {\n skipped++;\n continue;\n }\n\n // Rate limiting ~10 req/s\n await sleep(100);\n\n // Task B — exponential backoff retry on any error\n let msgData: gmail_v1.Schema$Message;\n try {\n const detail = await retryWithBackoff(() =>\n gmail.users.messages.get({\n userId: \"me\",\n id: msg.id!,\n format: \"metadata\",\n metadataHeaders: [\"Subject\", \"From\", \"Date\"],\n })\n );\n msgData = detail.data;\n } catch (err) {\n logger.warn(\"gmail-sync\", \"skipping message after retries\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n skipped++;\n continue;\n }\n\n const headers = msgData.payload?.headers ?? [];\n const subject = headers.find((h) => h.name === \"Subject\")?.value ?? \"(no subject)\";\n const from = headers.find((h) => h.name === \"From\")?.value ?? \"\";\n const dateStr = headers.find((h) => h.name === \"Date\")?.value;\n const date = dateStr\n ? new Date(dateStr).toISOString().slice(0, 10)\n : new Date().toISOString().slice(0, 10);\n const snippet = msgData.snippet ?? \"\";\n\n // LLM summary — non-blocking fallback to raw snippet if no API key or error\n const { summarizeEmail } = await import(\"../core/llm.js\");\n const emailSummary = await summarizeEmail(subject, snippet, from);\n\n await appendInteraction(opts.dataDir, opts.slug, {\n date,\n type: \"Email\",\n direction: detectDirection(from),\n with: from,\n subject,\n summary: emailSummary.summary,\n nextSteps: emailSummary.nextSteps,\n sourceRef: source,\n synced: new Date().toISOString(),\n });\n\n // Append to in-memory string so within-batch duplicates are detected\n existingContent += source;\n\n // Index into LanceDB for semantic search (non-blocking)\n const { indexInLanceDB } = await import(\"../core/lancedb.js\");\n await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\\n${snippet}`, source, {\n date,\n type: \"Email\",\n }).catch((err: unknown) => {\n logger.error(\"gmail-sync\", \"LanceDB index failed\", { error: (err as Error).message });\n });\n\n // Agent wake: notify if an agent config exists for this customer (fire-and-forget)\n if (agentConfigExists(opts.dataDir, opts.slug)) {\n notifyAgentWake(opts.dataDir, opts.slug, {\n trigger: \"email\",\n subject,\n from,\n snippet,\n }).catch(() => {\n // Notification is non-blocking; swallow all errors\n });\n }\n\n synced++;\n }\n\n return { synced, skipped };\n}\n\nfunction agentConfigExists(dataDir: string, slug: string): boolean {\n const configPath = path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n return fs.existsSync(configPath);\n}\n\nfunction detectDirection(_from: string): \"inbound\" | \"outbound\" {\n return \"inbound\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
+ import { i as writeJsonFile } from "./json-store-WWsFzXub.js";
1
2
  import { t as AgentConfigSchema } from "./agent-config-zPvcqu07.js";
2
- import { r as readInteractions, t as appendInteraction } from "./interactions-writer-SLHnoEeE.js";
3
- import { a as summarizeEmail } from "./llm-DvzZqva0.js";
3
+ import { i as readInteractions, n as appendInteraction } from "./interactions-writer-DbSyI2rt.js";
4
+ import { n as logger } from "./logger-Dyl4VcLO.js";
5
+ import { a as summarizeEmail } from "./llm-PZzgPphl.js";
4
6
  import path from "path";
5
7
  import fs from "fs";
6
8
  import https from "https";
@@ -23,11 +25,10 @@ function readAgentConfig(dataDir, slug) {
23
25
  function writeLastWake(dataDir, slug, config) {
24
26
  const p = agentConfigPath(dataDir, slug);
25
27
  try {
26
- const updated = {
28
+ writeJsonFile(p, {
27
29
  ...config,
28
30
  lastWake: (/* @__PURE__ */ new Date()).toISOString()
29
- };
30
- fs.writeFileSync(p, JSON.stringify(updated, null, 2), "utf-8");
31
+ });
31
32
  } catch {}
32
33
  }
33
34
  function sendTelegramMessage(token, chatId, text) {
@@ -144,7 +145,10 @@ async function syncGmail(opts) {
144
145
  ]
145
146
  }))).data;
146
147
  } catch (err) {
147
- process.stderr.write(`[gmail-sync] Skipping message ${msg.id} after retries: ${err.message}\n`);
148
+ logger.warn("gmail-sync", "skipping message after retries", {
149
+ messageId: msg.id,
150
+ error: err.message
151
+ });
148
152
  skipped++;
149
153
  continue;
150
154
  }
@@ -154,7 +158,7 @@ async function syncGmail(opts) {
154
158
  const dateStr = headers.find((h) => h.name === "Date")?.value;
155
159
  const date = dateStr ? new Date(dateStr).toISOString().slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
156
160
  const snippet = msgData.snippet ?? "";
157
- const { summarizeEmail } = await import("./llm-DEjWcqmW.js");
161
+ const { summarizeEmail } = await import("./llm-DSX1-wFu.js");
158
162
  const emailSummary = await summarizeEmail(subject, snippet, from);
159
163
  await appendInteraction(opts.dataDir, opts.slug, {
160
164
  date,
@@ -168,12 +172,12 @@ async function syncGmail(opts) {
168
172
  synced: (/* @__PURE__ */ new Date()).toISOString()
169
173
  });
170
174
  existingContent += source;
171
- const { indexInLanceDB } = await import("./lancedb-CCBbpulq.js");
175
+ const { indexInLanceDB } = await import("./lancedb-CswQEE5K.js");
172
176
  await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\n${snippet}`, source, {
173
177
  date,
174
178
  type: "Email"
175
179
  }).catch((err) => {
176
- process.stderr.write(`[gmail-sync] LanceDB index failed: ${err.message}\n`);
180
+ logger.error("gmail-sync", "LanceDB index failed", { error: err.message });
177
181
  });
178
182
  if (agentConfigExists(opts.dataDir, opts.slug)) notifyAgentWake(opts.dataDir, opts.slug, {
179
183
  trigger: "email",
@@ -201,4 +205,4 @@ function sleep(ms) {
201
205
  //#endregion
202
206
  export { syncGmail };
203
207
 
204
- //# sourceMappingURL=gmail-sync-DIaxInDT.js.map
208
+ //# sourceMappingURL=gmail-sync-DueE6tl5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-sync-DueE6tl5.js","names":["gmail","gmailApi"],"sources":["../src/core/agent-notifier.ts","../src/sync/gmail-sync.ts"],"sourcesContent":["// src/core/agent-notifier.ts\n// Sends a Telegram wake notification when a new inbound email from a customer\n// domain is detected and an agent config exists for that customer slug.\n// All errors are swallowed — this is a notification feature and must never\n// crash the core loop.\n\nimport fs from \"fs\";\nimport https from \"https\";\nimport path from \"path\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\nimport { AgentConfigSchema, type AgentConfig } from \"../schemas/agent-config.js\";\nimport { summarizeEmail } from \"./llm.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface WakeContext {\n trigger: \"email\" | \"calendar\";\n subject: string;\n from: string;\n snippet: string;\n}\n\n// ─── Agent config helpers ─────────────────────────────────────────────────────\n\nfunction agentConfigPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n}\n\nfunction readAgentConfig(dataDir: string, slug: string): AgentConfig | null {\n const p = agentConfigPath(dataDir, slug);\n if (!fs.existsSync(p)) return null;\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n const result = AgentConfigSchema.safeParse(raw);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nfunction writeLastWake(dataDir: string, slug: string, config: AgentConfig): void {\n const p = agentConfigPath(dataDir, slug);\n try {\n const updated: AgentConfig = { ...config, lastWake: new Date().toISOString() };\n writeJsonFile(p, updated);\n } catch {\n // non-fatal — just a housekeeping write\n }\n}\n\n// ─── Telegram transport ───────────────────────────────────────────────────────\n\nfunction sendTelegramMessage(token: string, chatId: string, text: string): Promise<void> {\n const body = JSON.stringify({ chat_id: chatId, text, parse_mode: \"Markdown\" });\n return new Promise<void>((resolve, reject) => {\n const req = https.request(\n `https://api.telegram.org/bot${token}/sendMessage`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n },\n },\n (res) => {\n res.resume();\n resolve();\n }\n );\n req.on(\"error\", reject);\n req.write(body);\n req.end();\n });\n}\n\n// ─── Message builder ──────────────────────────────────────────────────────────\n\nfunction buildWakeMessage(\n slug: string,\n subject: string,\n summary: string,\n nextSteps: string[]\n): string {\n const suggestedAction = nextSteps[0] ?? \"Follow up within 24h\";\n return (\n `📧 New email from **${slug}**: ${subject}\\n` +\n `${summary}\\n\\n` +\n `💡 Suggested action: ${suggestedAction}`\n );\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget notification: reads the agent config for `slug`, summarises\n * the inbound email with the LLM, and sends a Telegram message.\n *\n * Silently returns (no throw) when:\n * - no agent config exists for the slug\n * - TELEGRAM_BOT_TOKEN env var is not set\n * - no chat id is available (neither in config nor in TELEGRAM_CHAT_ID env var)\n * - any HTTPS / LLM error occurs\n */\nexport async function notifyAgentWake(\n dataDir: string,\n slug: string,\n context: WakeContext\n): Promise<void> {\n try {\n // 1. Read agent config — bail silently if not found\n const config = readAgentConfig(dataDir, slug);\n if (!config) return;\n\n // 2. Check for Telegram token — bail silently if absent\n const token = process.env[\"TELEGRAM_BOT_TOKEN\"];\n if (!token) return;\n\n // 3. Determine chat id — config takes precedence, fallback to env var\n const chatId = config.telegramChatId ?? process.env[\"TELEGRAM_CHAT_ID\"];\n if (!chatId) return;\n\n // 4. Summarise the email (LLM, with fallback built into summarizeEmail itself)\n const emailSummary = await summarizeEmail(context.subject, context.snippet, context.from);\n\n // 5. Build and send the Telegram message\n const text = buildWakeMessage(\n slug,\n context.subject,\n emailSummary.summary,\n emailSummary.nextSteps\n );\n await sendTelegramMessage(token, chatId, text);\n\n // 6. Update lastWake on success\n writeLastWake(dataDir, slug, config);\n } catch {\n // Swallow all errors — this is a notification feature, never crashes core loop\n }\n}\n","// src/sync/gmail-sync.ts\nimport fs from \"fs\";\nimport path from \"path\";\nimport { gmail as gmailApi, type gmail_v1 } from \"@googleapis/gmail\";\nimport type { OAuth2Client } from \"google-auth-library\";\nimport { readInteractions, appendInteraction } from \"../fs/interactions-writer.js\";\nimport { notifyAgentWake } from \"../core/agent-notifier.js\";\nimport { logger } from \"../core/logger.js\";\n\ninterface SyncOptions {\n slug: string;\n dataDir: string;\n auth: OAuth2Client;\n query: string;\n since?: Date;\n maxPages?: number;\n}\n\n/**\n * Retry a function with exponential backoff on any error.\n * Delays: 1s, 2s, 4s, 8s … (2^attempt seconds), up to maxRetries retries.\n */\nexport async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n const delayMs = 1000 * Math.pow(2, attempt);\n await sleep(delayMs);\n attempt++;\n }\n }\n}\n\nexport async function syncGmail(opts: SyncOptions): Promise<{ synced: number; skipped: number }> {\n const gmail = gmailApi({ version: \"v1\", auth: opts.auth });\n const maxPages = opts.maxPages ?? 5;\n\n let q = opts.query;\n if (opts.since) {\n const after = Math.floor(opts.since.getTime() / 1000);\n q += ` after:${after}`;\n }\n\n // Collect all message stubs across pages (Task A — pagination)\n const allMessages: Array<{ id?: string | null; threadId?: string | null }> = [];\n let pageToken: string | undefined = undefined;\n let pagesFetched = 0;\n\n do {\n const listResp: { data: gmail_v1.Schema$ListMessagesResponse } =\n await gmail.users.messages.list({\n userId: \"me\",\n q,\n maxResults: 200,\n ...(pageToken ? { pageToken } : {}),\n });\n const pageMessages = listResp.data.messages ?? [];\n allMessages.push(...pageMessages);\n pageToken = listResp.data.nextPageToken ?? undefined;\n pagesFetched++;\n } while (pageToken && pagesFetched < maxPages);\n\n // Read existing interactions once before the loop — avoids O(messages) file reads\n let existingContent = await readInteractions(opts.dataDir, opts.slug);\n\n let synced = 0;\n let skipped = 0;\n\n for (const msg of allMessages) {\n if (!msg.id) continue;\n\n const source = `gmail://thread/${msg.threadId ?? msg.id}`;\n\n if (existingContent.includes(source)) {\n skipped++;\n continue;\n }\n\n // Rate limiting ~10 req/s\n await sleep(100);\n\n // Task B — exponential backoff retry on any error\n let msgData: gmail_v1.Schema$Message;\n try {\n const detail = await retryWithBackoff(() =>\n gmail.users.messages.get({\n userId: \"me\",\n id: msg.id!,\n format: \"metadata\",\n metadataHeaders: [\"Subject\", \"From\", \"Date\"],\n })\n );\n msgData = detail.data;\n } catch (err) {\n logger.warn(\"gmail-sync\", \"skipping message after retries\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n skipped++;\n continue;\n }\n\n const headers = msgData.payload?.headers ?? [];\n const subject = headers.find((h) => h.name === \"Subject\")?.value ?? \"(no subject)\";\n const from = headers.find((h) => h.name === \"From\")?.value ?? \"\";\n const dateStr = headers.find((h) => h.name === \"Date\")?.value;\n const date = dateStr\n ? new Date(dateStr).toISOString().slice(0, 10)\n : new Date().toISOString().slice(0, 10);\n const snippet = msgData.snippet ?? \"\";\n\n // LLM summary — non-blocking fallback to raw snippet if no API key or error\n const { summarizeEmail } = await import(\"../core/llm.js\");\n const emailSummary = await summarizeEmail(subject, snippet, from);\n\n await appendInteraction(opts.dataDir, opts.slug, {\n date,\n type: \"Email\",\n direction: detectDirection(from),\n with: from,\n subject,\n summary: emailSummary.summary,\n nextSteps: emailSummary.nextSteps,\n sourceRef: source,\n synced: new Date().toISOString(),\n });\n\n // Append to in-memory string so within-batch duplicates are detected\n existingContent += source;\n\n // Index into LanceDB for semantic search (non-blocking)\n const { indexInLanceDB } = await import(\"../core/lancedb.js\");\n await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\\n${snippet}`, source, {\n date,\n type: \"Email\",\n }).catch((err: unknown) => {\n logger.error(\"gmail-sync\", \"LanceDB index failed\", { error: (err as Error).message });\n });\n\n // Agent wake: notify if an agent config exists for this customer (fire-and-forget)\n if (agentConfigExists(opts.dataDir, opts.slug)) {\n notifyAgentWake(opts.dataDir, opts.slug, {\n trigger: \"email\",\n subject,\n from,\n snippet,\n }).catch(() => {\n // Notification is non-blocking; swallow all errors\n });\n }\n\n synced++;\n }\n\n return { synced, skipped };\n}\n\nfunction agentConfigExists(dataDir: string, slug: string): boolean {\n const configPath = path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n return fs.existsSync(configPath);\n}\n\nfunction detectDirection(_from: string): \"inbound\" | \"outbound\" {\n return \"inbound\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
- const require_interactions_writer = require("./interactions-writer-CrPStUll.cjs");
3
- const require_llm = require("./llm-iijeXmgq.cjs");
2
+ const require_session_store = require("./session-store-yfwnj0OC.cjs");
3
+ const require_interactions_writer = require("./interactions-writer-a2yzBd7T.cjs");
4
+ const require_logger = require("./logger-BkInaGoV.cjs");
5
+ const require_llm = require("./llm-CXycmEl9.cjs");
4
6
  let path = require("path");
5
7
  path = require_chunk.__toESM(path, 1);
6
8
  let fs = require("fs");
@@ -37,11 +39,10 @@ function readAgentConfig(dataDir, slug) {
37
39
  function writeLastWake(dataDir, slug, config) {
38
40
  const p = agentConfigPath(dataDir, slug);
39
41
  try {
40
- const updated = {
42
+ require_session_store.writeJsonFile(p, {
41
43
  ...config,
42
44
  lastWake: (/* @__PURE__ */ new Date()).toISOString()
43
- };
44
- fs.default.writeFileSync(p, JSON.stringify(updated, null, 2), "utf-8");
45
+ });
45
46
  } catch {}
46
47
  }
47
48
  function sendTelegramMessage(token, chatId, text) {
@@ -158,7 +159,10 @@ async function syncGmail(opts) {
158
159
  ]
159
160
  }))).data;
160
161
  } catch (err) {
161
- process.stderr.write(`[gmail-sync] Skipping message ${msg.id} after retries: ${err.message}\n`);
162
+ require_logger.logger.warn("gmail-sync", "skipping message after retries", {
163
+ messageId: msg.id,
164
+ error: err.message
165
+ });
162
166
  skipped++;
163
167
  continue;
164
168
  }
@@ -168,7 +172,7 @@ async function syncGmail(opts) {
168
172
  const dateStr = headers.find((h) => h.name === "Date")?.value;
169
173
  const date = dateStr ? new Date(dateStr).toISOString().slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
170
174
  const snippet = msgData.snippet ?? "";
171
- const { summarizeEmail } = await Promise.resolve().then(() => require("./llm-iijeXmgq.cjs")).then((n) => n.llm_exports);
175
+ const { summarizeEmail } = await Promise.resolve().then(() => require("./llm-CXycmEl9.cjs")).then((n) => n.llm_exports);
172
176
  const emailSummary = await summarizeEmail(subject, snippet, from);
173
177
  await require_interactions_writer.appendInteraction(opts.dataDir, opts.slug, {
174
178
  date,
@@ -187,7 +191,7 @@ async function syncGmail(opts) {
187
191
  date,
188
192
  type: "Email"
189
193
  }).catch((err) => {
190
- process.stderr.write(`[gmail-sync] LanceDB index failed: ${err.message}\n`);
194
+ require_logger.logger.error("gmail-sync", "LanceDB index failed", { error: err.message });
191
195
  });
192
196
  if (agentConfigExists(opts.dataDir, opts.slug)) notifyAgentWake(opts.dataDir, opts.slug, {
193
197
  trigger: "email",
@@ -215,4 +219,4 @@ function sleep(ms) {
215
219
  //#endregion
216
220
  exports.syncGmail = syncGmail;
217
221
 
218
- //# sourceMappingURL=gmail-sync-hHm9gaWd.cjs.map
222
+ //# sourceMappingURL=gmail-sync-GEy3oVvw.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-sync-GEy3oVvw.cjs","names":["z","summarizeEmail","readInteractions","appendInteraction"],"sources":["../src/schemas/agent-config.ts","../src/core/agent-notifier.ts","../src/sync/gmail-sync.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const AgentConfigSchema = z.object({\n slug: z.string().min(1),\n channel: z.enum([\"telegram\"]),\n wakeOn: z.array(z.enum([\"email\", \"calendar\"])).default([\"email\"]),\n createdAt: z.string(),\n lastWake: z.string().nullable().default(null),\n telegramChatId: z.string().optional(),\n});\n\nexport type AgentConfig = z.infer<typeof AgentConfigSchema>;\n","// src/core/agent-notifier.ts\n// Sends a Telegram wake notification when a new inbound email from a customer\n// domain is detected and an agent config exists for that customer slug.\n// All errors are swallowed — this is a notification feature and must never\n// crash the core loop.\n\nimport fs from \"fs\";\nimport https from \"https\";\nimport path from \"path\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\nimport { AgentConfigSchema, type AgentConfig } from \"../schemas/agent-config.js\";\nimport { summarizeEmail } from \"./llm.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface WakeContext {\n trigger: \"email\" | \"calendar\";\n subject: string;\n from: string;\n snippet: string;\n}\n\n// ─── Agent config helpers ─────────────────────────────────────────────────────\n\nfunction agentConfigPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n}\n\nfunction readAgentConfig(dataDir: string, slug: string): AgentConfig | null {\n const p = agentConfigPath(dataDir, slug);\n if (!fs.existsSync(p)) return null;\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n const result = AgentConfigSchema.safeParse(raw);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nfunction writeLastWake(dataDir: string, slug: string, config: AgentConfig): void {\n const p = agentConfigPath(dataDir, slug);\n try {\n const updated: AgentConfig = { ...config, lastWake: new Date().toISOString() };\n writeJsonFile(p, updated);\n } catch {\n // non-fatal — just a housekeeping write\n }\n}\n\n// ─── Telegram transport ───────────────────────────────────────────────────────\n\nfunction sendTelegramMessage(token: string, chatId: string, text: string): Promise<void> {\n const body = JSON.stringify({ chat_id: chatId, text, parse_mode: \"Markdown\" });\n return new Promise<void>((resolve, reject) => {\n const req = https.request(\n `https://api.telegram.org/bot${token}/sendMessage`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n },\n },\n (res) => {\n res.resume();\n resolve();\n }\n );\n req.on(\"error\", reject);\n req.write(body);\n req.end();\n });\n}\n\n// ─── Message builder ──────────────────────────────────────────────────────────\n\nfunction buildWakeMessage(\n slug: string,\n subject: string,\n summary: string,\n nextSteps: string[]\n): string {\n const suggestedAction = nextSteps[0] ?? \"Follow up within 24h\";\n return (\n `📧 New email from **${slug}**: ${subject}\\n` +\n `${summary}\\n\\n` +\n `💡 Suggested action: ${suggestedAction}`\n );\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget notification: reads the agent config for `slug`, summarises\n * the inbound email with the LLM, and sends a Telegram message.\n *\n * Silently returns (no throw) when:\n * - no agent config exists for the slug\n * - TELEGRAM_BOT_TOKEN env var is not set\n * - no chat id is available (neither in config nor in TELEGRAM_CHAT_ID env var)\n * - any HTTPS / LLM error occurs\n */\nexport async function notifyAgentWake(\n dataDir: string,\n slug: string,\n context: WakeContext\n): Promise<void> {\n try {\n // 1. Read agent config — bail silently if not found\n const config = readAgentConfig(dataDir, slug);\n if (!config) return;\n\n // 2. Check for Telegram token — bail silently if absent\n const token = process.env[\"TELEGRAM_BOT_TOKEN\"];\n if (!token) return;\n\n // 3. Determine chat id — config takes precedence, fallback to env var\n const chatId = config.telegramChatId ?? process.env[\"TELEGRAM_CHAT_ID\"];\n if (!chatId) return;\n\n // 4. Summarise the email (LLM, with fallback built into summarizeEmail itself)\n const emailSummary = await summarizeEmail(context.subject, context.snippet, context.from);\n\n // 5. Build and send the Telegram message\n const text = buildWakeMessage(\n slug,\n context.subject,\n emailSummary.summary,\n emailSummary.nextSteps\n );\n await sendTelegramMessage(token, chatId, text);\n\n // 6. Update lastWake on success\n writeLastWake(dataDir, slug, config);\n } catch {\n // Swallow all errors — this is a notification feature, never crashes core loop\n }\n}\n","// src/sync/gmail-sync.ts\nimport fs from \"fs\";\nimport path from \"path\";\nimport { gmail as gmailApi, type gmail_v1 } from \"@googleapis/gmail\";\nimport type { OAuth2Client } from \"google-auth-library\";\nimport { readInteractions, appendInteraction } from \"../fs/interactions-writer.js\";\nimport { notifyAgentWake } from \"../core/agent-notifier.js\";\nimport { logger } from \"../core/logger.js\";\n\ninterface SyncOptions {\n slug: string;\n dataDir: string;\n auth: OAuth2Client;\n query: string;\n since?: Date;\n maxPages?: number;\n}\n\n/**\n * Retry a function with exponential backoff on any error.\n * Delays: 1s, 2s, 4s, 8s … (2^attempt seconds), up to maxRetries retries.\n */\nexport async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n const delayMs = 1000 * Math.pow(2, attempt);\n await sleep(delayMs);\n attempt++;\n }\n }\n}\n\nexport async function syncGmail(opts: SyncOptions): Promise<{ synced: number; skipped: number }> {\n const gmail = gmailApi({ version: \"v1\", auth: opts.auth });\n const maxPages = opts.maxPages ?? 5;\n\n let q = opts.query;\n if (opts.since) {\n const after = Math.floor(opts.since.getTime() / 1000);\n q += ` after:${after}`;\n }\n\n // Collect all message stubs across pages (Task A — pagination)\n const allMessages: Array<{ id?: string | null; threadId?: string | null }> = [];\n let pageToken: string | undefined = undefined;\n let pagesFetched = 0;\n\n do {\n const listResp: { data: gmail_v1.Schema$ListMessagesResponse } =\n await gmail.users.messages.list({\n userId: \"me\",\n q,\n maxResults: 200,\n ...(pageToken ? { pageToken } : {}),\n });\n const pageMessages = listResp.data.messages ?? [];\n allMessages.push(...pageMessages);\n pageToken = listResp.data.nextPageToken ?? undefined;\n pagesFetched++;\n } while (pageToken && pagesFetched < maxPages);\n\n // Read existing interactions once before the loop — avoids O(messages) file reads\n let existingContent = await readInteractions(opts.dataDir, opts.slug);\n\n let synced = 0;\n let skipped = 0;\n\n for (const msg of allMessages) {\n if (!msg.id) continue;\n\n const source = `gmail://thread/${msg.threadId ?? msg.id}`;\n\n if (existingContent.includes(source)) {\n skipped++;\n continue;\n }\n\n // Rate limiting ~10 req/s\n await sleep(100);\n\n // Task B — exponential backoff retry on any error\n let msgData: gmail_v1.Schema$Message;\n try {\n const detail = await retryWithBackoff(() =>\n gmail.users.messages.get({\n userId: \"me\",\n id: msg.id!,\n format: \"metadata\",\n metadataHeaders: [\"Subject\", \"From\", \"Date\"],\n })\n );\n msgData = detail.data;\n } catch (err) {\n logger.warn(\"gmail-sync\", \"skipping message after retries\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n skipped++;\n continue;\n }\n\n const headers = msgData.payload?.headers ?? [];\n const subject = headers.find((h) => h.name === \"Subject\")?.value ?? \"(no subject)\";\n const from = headers.find((h) => h.name === \"From\")?.value ?? \"\";\n const dateStr = headers.find((h) => h.name === \"Date\")?.value;\n const date = dateStr\n ? new Date(dateStr).toISOString().slice(0, 10)\n : new Date().toISOString().slice(0, 10);\n const snippet = msgData.snippet ?? \"\";\n\n // LLM summary — non-blocking fallback to raw snippet if no API key or error\n const { summarizeEmail } = await import(\"../core/llm.js\");\n const emailSummary = await summarizeEmail(subject, snippet, from);\n\n await appendInteraction(opts.dataDir, opts.slug, {\n date,\n type: \"Email\",\n direction: detectDirection(from),\n with: from,\n subject,\n summary: emailSummary.summary,\n nextSteps: emailSummary.nextSteps,\n sourceRef: source,\n synced: new Date().toISOString(),\n });\n\n // Append to in-memory string so within-batch duplicates are detected\n existingContent += source;\n\n // Index into LanceDB for semantic search (non-blocking)\n const { indexInLanceDB } = await import(\"../core/lancedb.js\");\n await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\\n${snippet}`, source, {\n date,\n type: \"Email\",\n }).catch((err: unknown) => {\n logger.error(\"gmail-sync\", \"LanceDB index failed\", { error: (err as Error).message });\n });\n\n // Agent wake: notify if an agent config exists for this customer (fire-and-forget)\n if (agentConfigExists(opts.dataDir, opts.slug)) {\n notifyAgentWake(opts.dataDir, opts.slug, {\n trigger: \"email\",\n subject,\n from,\n snippet,\n }).catch(() => {\n // Notification is non-blocking; swallow all errors\n });\n }\n\n synced++;\n }\n\n return { synced, skipped };\n}\n\nfunction agentConfigExists(dataDir: string, slug: string): boolean {\n const configPath = path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n return fs.existsSync(configPath);\n}\n\nfunction detectDirection(_from: string): \"inbound\" | \"outbound\" {\n return \"inbound\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import "./push-manager-CdqIIkuh.js";
2
- import { t as buildGmailRenewFn } from "./gmail-webhook-handler-e5Od25FX.js";
1
+ import "./push-manager-C0ECQgva.js";
2
+ import { t as buildGmailRenewFn } from "./gmail-webhook-handler-kGKpbY9h.js";
3
3
  export { buildGmailRenewFn };
@@ -1,6 +1,6 @@
1
- import { n as readSyncState, r as updateSlugSyncState } from "./sync-state-ChaLbamC.js";
2
- import { t as appendInteraction } from "./interactions-writer-SLHnoEeE.js";
3
- import { n as readSubscriptions, s as writeSubscriptions } from "./push-manager-CdqIIkuh.js";
1
+ import { n as readSyncState, r as updateSlugSyncState } from "./sync-state-DMZgzpez.js";
2
+ import { n as appendInteraction } from "./interactions-writer-DbSyI2rt.js";
3
+ import { n as readSubscriptions, s as writeSubscriptions } from "./push-manager-C0ECQgva.js";
4
4
  //#region src/sync/gmail-webhook-handler.ts
5
5
  function decodeGmailPubSubPayload(body) {
6
6
  try {
@@ -94,4 +94,4 @@ function buildGmailRenewFn(accessToken, topicName, registerFn) {
94
94
  //#endregion
95
95
  export { verifyGmailPubSubSignature as i, decodeGmailPubSubPayload as n, handleGmailPushEvent as r, buildGmailRenewFn as t };
96
96
 
97
- //# sourceMappingURL=gmail-webhook-handler-e5Od25FX.js.map
97
+ //# sourceMappingURL=gmail-webhook-handler-kGKpbY9h.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gmail-webhook-handler-e5Od25FX.js","names":["b"],"sources":["../src/sync/gmail-webhook-handler.ts"],"sourcesContent":["import {\n readSubscriptions,\n writeSubscriptions,\n type PushSubscription,\n type RenewFn,\n} from \"./push-manager.js\";\nimport { updateSlugSyncState, readSyncState } from \"../fs/sync-state.js\";\nimport { appendInteraction } from \"../fs/interactions-writer.js\";\nimport type { HistoryMessage, WatchRegistration } from \"./gmail-push-watch.js\";\n\nexport interface GmailPubSubMessage {\n emailAddress: string;\n historyId: string;\n}\n\nexport function decodeGmailPubSubPayload(body: unknown): GmailPubSubMessage | null {\n try {\n const b = body as { message?: { data?: string } };\n const data = b?.message?.data;\n if (!data) return null;\n const decoded = Buffer.from(data, \"base64\").toString(\"utf-8\");\n const parsed = JSON.parse(decoded) as { emailAddress?: string; historyId?: string };\n if (!parsed.emailAddress || !parsed.historyId) return null;\n return { emailAddress: parsed.emailAddress, historyId: parsed.historyId };\n } catch {\n return null;\n }\n}\n\nexport function verifyGmailPubSubSignature(\n authHeader: string | undefined,\n expectedToken: string\n): boolean {\n if (!authHeader) return false;\n const token = authHeader.startsWith(\"Bearer \") ? authHeader.slice(7) : authHeader;\n return token === expectedToken;\n}\n\nfunction findSubscriptionByEmail(\n subs: PushSubscription[],\n emailAddress: string\n): PushSubscription | null {\n return (\n subs.find(\n (s) =>\n s.provider === \"gmail\" &&\n s.status === \"active\" &&\n (s.providerData.gmailEmailAddress === emailAddress || s.slug === emailAddress)\n ) ?? null\n );\n}\n\nexport type FetchHistoryFn = (\n accessToken: string,\n startHistoryId: string\n) => Promise<HistoryMessage[]>;\nexport type FetchMessageFn = (\n accessToken: string,\n messageId: string\n) => Promise<{\n id: string;\n threadId: string;\n subject: string;\n from: string;\n date: string;\n body: string;\n}>;\nexport type AppendInteractionFn = typeof appendInteraction;\n\nexport interface HandleGmailPushOptions {\n fetchHistoryFn?: FetchHistoryFn;\n fetchMessageFn?: FetchMessageFn;\n appendInteractionFn?: AppendInteractionFn;\n accessToken?: string;\n}\n\nexport { readSubscriptions };\n\nexport async function handleGmailPushEvent(\n dataDir: string,\n payload: GmailPubSubMessage,\n subscriptionId: string,\n options: HandleGmailPushOptions = {}\n): Promise<{ processed: number; slug: string | null }> {\n const subs = await readSubscriptions(dataDir);\n const sub = findSubscriptionByEmail(subs, payload.emailAddress);\n if (!sub) return { processed: 0, slug: null };\n\n const slug = sub.slug;\n const syncState = readSyncState(dataDir);\n const lastHistoryId =\n syncState[slug]?.lastGmailPushHistoryId ?? sub.providerData.gmailHistoryId ?? \"0\";\n\n // Skip if already processed\n if (BigInt(payload.historyId) <= BigInt(lastHistoryId)) {\n return { processed: 0, slug };\n }\n\n const startHistoryId = sub.providerData.gmailHistoryId ?? lastHistoryId;\n\n const {\n fetchHistoryFn,\n fetchMessageFn,\n appendInteractionFn = appendInteraction,\n accessToken = \"\",\n } = options;\n\n if (!fetchHistoryFn) return { processed: 0, slug };\n\n const messages = await fetchHistoryFn(accessToken, startHistoryId);\n let processed = 0;\n\n for (const msg of messages) {\n if (!fetchMessageFn) continue;\n try {\n const full = await fetchMessageFn(accessToken, msg.id);\n const sourceRef = `gmail://thread/${full.threadId}`;\n\n await appendInteractionFn(dataDir, slug, {\n date: new Date().toISOString().slice(0, 10),\n type: \"Email\",\n direction: \"inbound\",\n with: full.from,\n subject: full.subject,\n summary: full.body.slice(0, 300) || \"(no body)\",\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n processed++;\n } catch {\n // Skip individual message errors\n }\n }\n\n // Update sync state\n updateSlugSyncState(dataDir, slug, { lastGmailPushHistoryId: payload.historyId });\n\n // Update subscription counters\n const subIdx = subs.findIndex((s) => s.id === sub.id);\n if (subIdx !== -1) {\n subs[subIdx] = {\n ...subs[subIdx]!,\n eventsProcessed: subs[subIdx]!.eventsProcessed + 1,\n lastEventAt: new Date().toISOString(),\n };\n await writeSubscriptions(dataDir, subs);\n }\n\n return { processed, slug };\n}\n\nexport type RegisterGmailWatchFn = (\n accessToken: string,\n topicName: string\n) => Promise<WatchRegistration>;\n\nexport function buildGmailRenewFn(\n accessToken: string,\n topicName: string,\n registerFn?: RegisterGmailWatchFn\n): RenewFn {\n return async (_sub: PushSubscription) => {\n const doRegister: RegisterGmailWatchFn =\n registerFn ??\n (async (token: string, topic: string) => {\n const { registerGmailWatch } = await import(\"./gmail-push-watch.js\");\n return registerGmailWatch(token, topic);\n });\n\n const registration = await doRegister(accessToken, topicName);\n return {\n expiresAt: new Date(Number(registration.expiration)).toISOString(),\n providerData: { gmailHistoryId: registration.historyId },\n };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gmail-webhook-handler-kGKpbY9h.js","names":["b"],"sources":["../src/sync/gmail-webhook-handler.ts"],"sourcesContent":["import {\n readSubscriptions,\n writeSubscriptions,\n type PushSubscription,\n type RenewFn,\n} from \"./push-manager.js\";\nimport { updateSlugSyncState, readSyncState } from \"../fs/sync-state.js\";\nimport { appendInteraction } from \"../fs/interactions-writer.js\";\nimport type { HistoryMessage, WatchRegistration } from \"./gmail-push-watch.js\";\n\nexport interface GmailPubSubMessage {\n emailAddress: string;\n historyId: string;\n}\n\nexport function decodeGmailPubSubPayload(body: unknown): GmailPubSubMessage | null {\n try {\n const b = body as { message?: { data?: string } };\n const data = b?.message?.data;\n if (!data) return null;\n const decoded = Buffer.from(data, \"base64\").toString(\"utf-8\");\n const parsed = JSON.parse(decoded) as { emailAddress?: string; historyId?: string };\n if (!parsed.emailAddress || !parsed.historyId) return null;\n return { emailAddress: parsed.emailAddress, historyId: parsed.historyId };\n } catch {\n return null;\n }\n}\n\nexport function verifyGmailPubSubSignature(\n authHeader: string | undefined,\n expectedToken: string\n): boolean {\n if (!authHeader) return false;\n const token = authHeader.startsWith(\"Bearer \") ? authHeader.slice(7) : authHeader;\n return token === expectedToken;\n}\n\nfunction findSubscriptionByEmail(\n subs: PushSubscription[],\n emailAddress: string\n): PushSubscription | null {\n return (\n subs.find(\n (s) =>\n s.provider === \"gmail\" &&\n s.status === \"active\" &&\n (s.providerData.gmailEmailAddress === emailAddress || s.slug === emailAddress)\n ) ?? null\n );\n}\n\nexport type FetchHistoryFn = (\n accessToken: string,\n startHistoryId: string\n) => Promise<HistoryMessage[]>;\nexport type FetchMessageFn = (\n accessToken: string,\n messageId: string\n) => Promise<{\n id: string;\n threadId: string;\n subject: string;\n from: string;\n date: string;\n body: string;\n}>;\nexport type AppendInteractionFn = typeof appendInteraction;\n\nexport interface HandleGmailPushOptions {\n fetchHistoryFn?: FetchHistoryFn;\n fetchMessageFn?: FetchMessageFn;\n appendInteractionFn?: AppendInteractionFn;\n accessToken?: string;\n}\n\nexport { readSubscriptions };\n\nexport async function handleGmailPushEvent(\n dataDir: string,\n payload: GmailPubSubMessage,\n subscriptionId: string,\n options: HandleGmailPushOptions = {}\n): Promise<{ processed: number; slug: string | null }> {\n const subs = await readSubscriptions(dataDir);\n const sub = findSubscriptionByEmail(subs, payload.emailAddress);\n if (!sub) return { processed: 0, slug: null };\n\n const slug = sub.slug;\n const syncState = readSyncState(dataDir);\n const lastHistoryId =\n syncState[slug]?.lastGmailPushHistoryId ?? sub.providerData.gmailHistoryId ?? \"0\";\n\n // Skip if already processed\n if (BigInt(payload.historyId) <= BigInt(lastHistoryId)) {\n return { processed: 0, slug };\n }\n\n const startHistoryId = sub.providerData.gmailHistoryId ?? lastHistoryId;\n\n const {\n fetchHistoryFn,\n fetchMessageFn,\n appendInteractionFn = appendInteraction,\n accessToken = \"\",\n } = options;\n\n if (!fetchHistoryFn) return { processed: 0, slug };\n\n const messages = await fetchHistoryFn(accessToken, startHistoryId);\n let processed = 0;\n\n for (const msg of messages) {\n if (!fetchMessageFn) continue;\n try {\n const full = await fetchMessageFn(accessToken, msg.id);\n const sourceRef = `gmail://thread/${full.threadId}`;\n\n await appendInteractionFn(dataDir, slug, {\n date: new Date().toISOString().slice(0, 10),\n type: \"Email\",\n direction: \"inbound\",\n with: full.from,\n subject: full.subject,\n summary: full.body.slice(0, 300) || \"(no body)\",\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n processed++;\n } catch {\n // Skip individual message errors\n }\n }\n\n // Update sync state\n updateSlugSyncState(dataDir, slug, { lastGmailPushHistoryId: payload.historyId });\n\n // Update subscription counters\n const subIdx = subs.findIndex((s) => s.id === sub.id);\n if (subIdx !== -1) {\n subs[subIdx] = {\n ...subs[subIdx]!,\n eventsProcessed: subs[subIdx]!.eventsProcessed + 1,\n lastEventAt: new Date().toISOString(),\n };\n await writeSubscriptions(dataDir, subs);\n }\n\n return { processed, slug };\n}\n\nexport type RegisterGmailWatchFn = (\n accessToken: string,\n topicName: string\n) => Promise<WatchRegistration>;\n\nexport function buildGmailRenewFn(\n accessToken: string,\n topicName: string,\n registerFn?: RegisterGmailWatchFn\n): RenewFn {\n return async (_sub: PushSubscription) => {\n const doRegister: RegisterGmailWatchFn =\n registerFn ??\n (async (token: string, topic: string) => {\n const { registerGmailWatch } = await import(\"./gmail-push-watch.js\");\n return registerGmailWatch(token, topic);\n });\n\n const registration = await doRegister(accessToken, topicName);\n return {\n expiresAt: new Date(Number(registration.expiration)).toISOString(),\n providerData: { gmailHistoryId: registration.historyId },\n };\n };\n}\n"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
- import { r as listCustomerSlugs } from "./customer-dir-DIylZ8Q6.js";
1
+ import { i as listCustomerSlugs } from "./customer-dir-CkMMXhb0.js";
2
+ import { i as writeJsonFile } from "./json-store-WWsFzXub.js";
2
3
  import { n as getActor } from "./audit-log-DNMY9mUZ.js";
3
- import { t as withJsonFile } from "./file-lock-B_zi7NQl.js";
4
- import { l as runSimulation } from "./revenue-simulation-Bqf2DLVB.js";
5
- import { t as readPipeline } from "./pipeline-writer-BvVquKIe.js";
6
- import { o as guardIsoDate, t as callLlm } from "./llm-DvzZqva0.js";
4
+ import { t as withJsonFile } from "./file-lock-CcHotQkZ.js";
5
+ import { l as runSimulation } from "./revenue-simulation-D8f_YkUY.js";
6
+ import { t as readPipeline } from "./pipeline-writer-CIllfnZl.js";
7
+ import { o as guardIsoDate, t as callLlm } from "./llm-PZzgPphl.js";
7
8
  import path from "path";
8
9
  import fs from "fs";
9
10
  //#region src/core/goal-engine.ts
@@ -22,12 +23,10 @@ function readGoals(dataDir) {
22
23
  }
23
24
  }
24
25
  function writeGoals(dataDir, goals) {
25
- const p = goalsPath(dataDir);
26
- fs.mkdirSync(path.dirname(p), { recursive: true });
27
- fs.writeFileSync(p, JSON.stringify({
26
+ writeJsonFile(goalsPath(dataDir), {
28
27
  goals,
29
28
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
30
- }, null, 2), "utf-8");
29
+ });
31
30
  }
32
31
  function makeGoalId() {
33
32
  return `goal_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
@@ -189,7 +188,7 @@ async function pursueGoal(dataDir, input, options = {}) {
189
188
  const today = options.today ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
190
189
  const actor = options.actor ?? getActor();
191
190
  const simInput = await (options.buildInputFn ?? (async (dir, horizon, t) => {
192
- const { buildSimulationInput } = await import("./revenue-simulation-BJdRTEHc.js");
191
+ const { buildSimulationInput } = await import("./revenue-simulation-njJZlTqm.js");
193
192
  return buildSimulationInput(dir, horizon, t);
194
193
  }))(dataDir, "quarter", today);
195
194
  const currentP50 = runSimulation(simInput).p50;
@@ -292,4 +291,4 @@ async function syncGoalProgressFromPipeline(dataDir, _today) {
292
291
  //#endregion
293
292
  export { goalsPath as a, makeGoalId as c, pursueGoal as d, rankDealsByLeverage as f, writeGoals as g, updateGoalProgress as h, getActiveGoals as i, parseLlmDecomposition as l, syncGoalProgressFromPipeline as m, cancelGoal as n, inferGoalType as o, readGoals as p, decomposeGoalRuleBased as r, inferMetric as s, buildDecompositionPrompt as t, parseTargetFromDescription as u };
294
293
 
295
- //# sourceMappingURL=goal-engine-KpBftn4V.js.map
294
+ //# sourceMappingURL=goal-engine-BbroPhqm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"goal-engine-BbroPhqm.js","names":[],"sources":["../src/core/goal-engine.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { runSimulation } from \"./revenue-simulation.js\";\nimport { callLlm } from \"./llm.js\";\nimport { getActor } from \"../fs/audit-log.js\";\nimport { withJsonFile } from \"./file-lock.js\";\nimport { guardIsoDate } from \"./input-guard.js\";\nimport type { DealSnapshot, SimulationInput } from \"./revenue-simulation.js\";\nimport { readPipeline } from \"../fs/pipeline-writer.js\";\nimport { listCustomerSlugs } from \"../fs/customer-dir.js\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type GoalMetric = \"revenue\" | \"deals_closed\" | \"meetings_booked\" | \"pipeline_created\";\nexport type GoalType = \"revenue\" | \"pipeline\" | \"relationship\" | \"churn_prevention\";\nexport type GoalStatus = \"active\" | \"completed\" | \"cancelled\" | \"blocked\";\n\nexport interface GoalSubGoal {\n priority: number;\n action: string;\n slug: string;\n dealName?: string;\n why: string;\n nextStep: string;\n targetDelta: number;\n playbookName?: string;\n}\n\nexport interface GoalDecomposition {\n analysis: string;\n currentPipeline: number;\n gap: number;\n subGoals: GoalSubGoal[];\n probabilisticOutcome: string;\n decomposedAt: string;\n}\n\nexport interface Goal {\n id: string;\n description: string;\n type: GoalType;\n target: number;\n metric: GoalMetric;\n deadline: string;\n decomposition: GoalDecomposition;\n progress: number;\n status: GoalStatus;\n createdAt: string;\n updatedAt: string;\n actor: string;\n}\n\nexport type BuildInputFn = (\n dataDir: string,\n horizon: \"quarter\" | \"year\",\n today: string\n) => Promise<SimulationInput>;\n\n// ─── Persistence ──────────────────────────────────────────────────────────────\n\nexport function goalsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"goals.json\");\n}\n\nexport function readGoals(dataDir: string): Goal[] {\n const p = goalsPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n if (Array.isArray(raw)) return raw as Goal[];\n return (raw as { goals?: Goal[] }).goals ?? [];\n } catch {\n return [];\n }\n}\n\nexport function writeGoals(dataDir: string, goals: Goal[]): void {\n writeJsonFile(goalsPath(dataDir), { goals, updatedAt: new Date().toISOString() });\n}\n\nexport function makeGoalId(): string {\n return `goal_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;\n}\n\n// ─── Parsing ──────────────────────────────────────────────────────────────────\n\nexport function parseTargetFromDescription(desc: string): number {\n // Try millions first: \"1.5M\", \"1.5 million\", \"$1.5M\"\n const millionMatch = desc.match(/[\\$€£]?\\s*(\\d+(?:\\.\\d+)?)\\s*(?:M\\b|million)/i);\n if (millionMatch) return Math.round(parseFloat(millionMatch[1]!) * 1_000_000);\n\n // Then thousands: \"500k\", \"€500k\"\n const kMatch = desc.match(/[\\$€£]?\\s*(\\d+(?:\\.\\d+)?)\\s*k\\b/i);\n if (kMatch) return Math.round(parseFloat(kMatch[1]!) * 1_000);\n\n // Then raw numbers with optional currency: \"€75000\"\n const rawMatch = desc.match(/[\\$€£]\\s*(\\d{4,}(?:[,.\\d]*\\d)?)/);\n if (rawMatch) return parseInt(rawMatch[1]!.replace(/[,. ]/g, \"\"), 10);\n\n return 0;\n}\n\nexport function inferGoalType(desc: string): GoalType {\n const lower = desc.toLowerCase();\n if (/churn|retain|renewal|renew/.test(lower)) return \"churn_prevention\";\n if (/meeting|call|book|relationship|contact/.test(lower)) return \"relationship\";\n if (/pipeline|prospect|lead|qualify/.test(lower)) return \"pipeline\";\n return \"revenue\";\n}\n\nexport function inferMetric(type: GoalType): GoalMetric {\n switch (type) {\n case \"pipeline\":\n return \"pipeline_created\";\n case \"relationship\":\n return \"meetings_booked\";\n case \"revenue\":\n return \"revenue\";\n case \"churn_prevention\":\n return \"revenue\";\n }\n}\n\n// ─── Rule-based decomposition ─────────────────────────────────────────────────\n\nexport function rankDealsByLeverage(deals: DealSnapshot[]): DealSnapshot[] {\n return deals\n .filter((d) => d.stage !== \"won\" && d.stage !== \"lost\")\n .sort((a, b) => {\n const leverageA = a.value * (a.probability / 100) * (a.healthScore / 100);\n const leverageB = b.value * (b.probability / 100) * (b.healthScore / 100);\n return leverageB - leverageA;\n });\n}\n\nfunction generateNextStep(deal: DealSnapshot): string {\n if (deal.healthScore < 40 && !deal.championPresent)\n return \"Re-engage urgently and identify a champion\";\n if (deal.healthScore < 60) return \"Schedule an urgent check-in call\";\n if (deal.daysSinceContact > 14) return \"Reach out — contact is overdue\";\n if (!deal.championPresent) return \"Identify a champion or economic buyer\";\n return \"Push to next pipeline stage\";\n}\n\nexport function decomposeGoalRuleBased(\n deals: DealSnapshot[],\n target: number,\n currentP50: number,\n today: string,\n playbookLookup?: (slug: string, deal: DealSnapshot) => string | undefined\n): GoalDecomposition {\n const gap = Math.max(0, target - currentP50);\n const decomposedAt = new Date(today + \"T00:00:00Z\").toISOString();\n\n if (gap === 0) {\n return {\n analysis: `Current pipeline (P50: €${currentP50.toLocaleString()}) already meets or exceeds the target of €${target.toLocaleString()}.`,\n currentPipeline: currentP50,\n gap: 0,\n subGoals: [],\n probabilisticOutcome: `Pipeline P50 (€${currentP50.toLocaleString()}) ≥ target (€${target.toLocaleString()}).`,\n decomposedAt,\n };\n }\n\n const ranked = rankDealsByLeverage(deals);\n\n if (ranked.length === 0) {\n return {\n analysis: `No active deals found. Gap to close: €${gap.toLocaleString()}. Focus on building pipeline.`,\n currentPipeline: currentP50,\n gap,\n subGoals: [\n {\n priority: 1,\n action: \"Build pipeline from scratch\",\n slug: \"_all\",\n why: `No active deals. Need €${gap.toLocaleString()} to reach target.`,\n nextStep:\n \"Use list_customers() to find prospects and log_interaction to initiate outreach\",\n targetDelta: target,\n },\n ],\n probabilisticOutcome: `Insufficient pipeline. Need €${gap.toLocaleString()} in new deals.`,\n decomposedAt,\n };\n }\n\n const subGoals: GoalSubGoal[] = [];\n let cumulative = 0;\n\n for (const deal of ranked.slice(0, 5)) {\n if (subGoals.length >= 5) break;\n const playbookName = playbookLookup?.(deal.slug, deal);\n const subGoal: GoalSubGoal = {\n priority: subGoals.length + 1,\n action: `Accelerate ${deal.slug}/${deal.name}`,\n slug: deal.slug,\n ...(deal.name ? { dealName: deal.name } : {}),\n why: `€${deal.value.toLocaleString()} deal in ${deal.stage} — health ${deal.healthScore}/100`,\n nextStep: generateNextStep(deal),\n targetDelta: deal.value,\n ...(playbookName ? { playbookName } : {}),\n };\n subGoals.push(subGoal);\n cumulative += deal.value;\n if (cumulative >= gap) break;\n }\n\n const projectedTotal = currentP50 + cumulative;\n return {\n analysis: `Current pipeline P50: €${currentP50.toLocaleString()}. Gap to target: €${gap.toLocaleString()}. Top ${subGoals.length} deal(s) identified.`,\n currentPipeline: currentP50,\n gap,\n subGoals,\n probabilisticOutcome: `If all recommended deals close: ~€${projectedTotal.toLocaleString()} (target: €${target.toLocaleString()}).`,\n decomposedAt,\n };\n}\n\n// ─── LLM path ─────────────────────────────────────────────────────────────────\n\nexport function buildDecompositionPrompt(\n description: string,\n target: number,\n deadline: string,\n currentP50: number,\n deals: DealSnapshot[],\n today: string\n): string {\n const gap = Math.max(0, target - currentP50);\n const dealLines = deals\n .filter((d) => d.stage !== \"won\" && d.stage !== \"lost\")\n .slice(0, 8)\n .map(\n (d, i) =>\n `${i + 1}. ${d.slug}/${d.name} — €${d.value.toLocaleString()}, stage: ${d.stage}, health: ${d.healthScore}/100, probability: ${d.probability}%${d.championPresent ? \", champion ✓\" : \"\"}`\n )\n .join(\"\\n\");\n\n return `You are a sales strategy AI helping decompose a revenue goal into actionable sub-goals.\n\nGoal: ${description}\nTarget: €${target.toLocaleString()}\nDeadline: ${deadline}\nCurrent date: ${today}\nCurrent weighted pipeline (P50): €${currentP50.toLocaleString()}\nGap to close: €${gap.toLocaleString()}\n\nActive deals (sorted by weighted value):\n${dealLines || \"(no active deals)\"}\n\nReturn JSON only (no markdown wrapper):\n{\n \"analysis\": \"<1-2 sentence summary of the situation>\",\n \"subGoals\": [\n {\n \"priority\": 1,\n \"action\": \"<what to do>\",\n \"slug\": \"<customer-slug>\",\n \"dealName\": \"<deal name>\",\n \"why\": \"<why this deal matters for the goal>\",\n \"nextStep\": \"<concrete next action with deadline>\",\n \"targetDelta\": <expected revenue contribution in euros>\n }\n ],\n \"probabilisticOutcome\": \"<P50 forecast summary after actions>\"\n}`;\n}\n\nexport function parseLlmDecomposition(\n response: string,\n fallback: GoalDecomposition\n): GoalDecomposition {\n try {\n const match = response.match(/\\{[\\s\\S]*\\}/);\n if (!match) return fallback;\n const parsed = JSON.parse(match[0]) as Partial<{\n analysis: string;\n subGoals: unknown[];\n probabilisticOutcome: string;\n }>;\n if (!parsed.analysis || !Array.isArray(parsed.subGoals)) return fallback;\n return {\n analysis: parsed.analysis,\n currentPipeline: fallback.currentPipeline,\n gap: fallback.gap,\n subGoals: (parsed.subGoals as Partial<GoalSubGoal>[]).map((s, i) => ({\n priority: s.priority ?? i + 1,\n action: s.action ?? \"\",\n slug: s.slug ?? \"_all\",\n ...(s.dealName ? { dealName: s.dealName } : {}),\n why: s.why ?? \"\",\n nextStep: s.nextStep ?? \"\",\n targetDelta: s.targetDelta ?? 0,\n ...(s.playbookName ? { playbookName: s.playbookName } : {}),\n })),\n probabilisticOutcome: parsed.probabilisticOutcome ?? fallback.probabilisticOutcome,\n decomposedAt: fallback.decomposedAt,\n };\n } catch {\n return fallback;\n }\n}\n\n// ─── pursueGoal ───────────────────────────────────────────────────────────────\n\nexport async function pursueGoal(\n dataDir: string,\n input: { description: string; deadline: string; context?: string },\n options: {\n llmFn?: (prompt: string) => Promise<string>;\n buildInputFn?: BuildInputFn;\n today?: string;\n actor?: string;\n } = {}\n): Promise<Goal> {\n guardIsoDate(input.deadline, \"deadline\");\n const today = options.today ?? new Date().toISOString().slice(0, 10);\n const actor = options.actor ?? getActor();\n\n const buildFn =\n options.buildInputFn ??\n ((async (dir, horizon, t) => {\n const { buildSimulationInput } = await import(\"./revenue-simulation.js\");\n return buildSimulationInput(dir, horizon, t);\n }) as BuildInputFn);\n\n const simInput = await buildFn(dataDir, \"quarter\", today);\n const simResult = runSimulation(simInput);\n const currentP50 = simResult.p50;\n const deals = simInput.deals;\n\n const target = parseTargetFromDescription(input.description);\n const type = inferGoalType(input.description);\n const metric = inferMetric(type);\n\n const ruleBasedDecomp = decomposeGoalRuleBased(deals, target, currentP50, today);\n\n let decomposition = ruleBasedDecomp;\n const llmFn = options.llmFn ?? callLlm;\n if (options.llmFn !== undefined) {\n const prompt = buildDecompositionPrompt(\n input.description,\n target,\n input.deadline,\n currentP50,\n deals,\n today\n );\n const response = await llmFn(prompt);\n decomposition = parseLlmDecomposition(response, ruleBasedDecomp);\n }\n\n const now = new Date().toISOString();\n const goal: Goal = {\n id: makeGoalId(),\n description: input.description,\n type,\n target,\n metric,\n deadline: input.deadline,\n decomposition,\n progress: 0,\n status: \"active\",\n createdAt: now,\n updatedAt: now,\n actor,\n };\n\n await withJsonFile<{ goals: Goal[]; updatedAt: string }>(goalsPath(dataDir), (current) => {\n const existing: Goal[] = Array.isArray(current?.goals) ? current.goals : [];\n return { goals: [...existing, goal], updatedAt: new Date().toISOString() };\n });\n return goal;\n}\n\n// ─── Goal management ──────────────────────────────────────────────────────────\n\nexport function getActiveGoals(dataDir: string): Goal[] {\n return readGoals(dataDir).filter((g) => g.status === \"active\");\n}\n\nexport async function updateGoalProgress(\n dataDir: string,\n goalId: string,\n progress: number\n): Promise<Goal | null> {\n let updated: Goal | null = null;\n await withJsonFile<{ goals: Goal[]; updatedAt: string }>(goalsPath(dataDir), (current) => {\n const goals: Goal[] = Array.isArray(current?.goals) ? [...current.goals] : [];\n const idx = goals.findIndex((g) => g.id === goalId);\n if (idx >= 0) {\n updated = { ...goals[idx]!, progress, updatedAt: new Date().toISOString() };\n goals[idx] = updated;\n }\n return { goals, updatedAt: new Date().toISOString() };\n });\n return updated;\n}\n\nexport async function cancelGoal(dataDir: string, goalId: string): Promise<Goal | null> {\n let cancelled: Goal | null = null;\n await withJsonFile<{ goals: Goal[]; updatedAt: string }>(goalsPath(dataDir), (current) => {\n const goals: Goal[] = Array.isArray(current?.goals) ? [...current.goals] : [];\n const idx = goals.findIndex((g) => g.id === goalId);\n if (idx >= 0) {\n cancelled = {\n ...goals[idx]!,\n status: \"cancelled\" as const,\n updatedAt: new Date().toISOString(),\n };\n goals[idx] = cancelled;\n }\n return { goals, updatedAt: new Date().toISOString() };\n });\n return cancelled;\n}\n\n// ─── Pipeline-driven progress sync ────────────────────────────────────────────\n\nexport interface SyncResult {\n updated: string[];\n skipped: number;\n}\n\nexport async function syncGoalProgressFromPipeline(\n dataDir: string,\n _today?: string\n): Promise<SyncResult> {\n const activeGoals = getActiveGoals(dataDir);\n const revenueGoals = activeGoals.filter((g) => g.metric === \"revenue\" && g.target > 0);\n\n if (revenueGoals.length === 0) return { updated: [], skipped: activeGoals.length };\n\n let totalWon = 0;\n for (const slug of listCustomerSlugs(dataDir)) {\n const deals = await readPipeline(dataDir, slug).catch(() => []);\n for (const deal of deals) {\n if (deal.stage === \"won\") totalWon += deal.value ?? 0;\n }\n }\n\n const updated: string[] = [];\n for (const goal of revenueGoals) {\n const progress = Math.min(100, Math.round((totalWon / goal.target) * 100));\n const result = await updateGoalProgress(dataDir, goal.id, progress);\n if (result) updated.push(goal.id);\n }\n\n return { updated, skipped: activeGoals.length - revenueGoals.length };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { g as writeGoals, m as syncGoalProgressFromPipeline, p as readGoals } from "./goal-engine-KpBftn4V.js";
1
+ import { g as writeGoals, m as syncGoalProgressFromPipeline, p as readGoals } from "./goal-engine-BbroPhqm.js";
2
2
  export { readGoals, syncGoalProgressFromPipeline, writeGoals };
@@ -1,5 +1,5 @@
1
- import { r as readInteractions, t as appendInteraction } from "./interactions-writer-SLHnoEeE.js";
2
- import { n as indexInLanceDB } from "./lancedb-rlvWoPwl.js";
1
+ import { i as readInteractions, n as appendInteraction } from "./interactions-writer-DbSyI2rt.js";
2
+ import { n as indexInLanceDB } from "./lancedb-CuHKNsNZ.js";
3
3
  import path from "path";
4
4
  import fs from "fs";
5
5
  //#region src/sync/google-drive-sync.ts
@@ -102,4 +102,4 @@ async function syncGoogleDriveFiles(opts) {
102
102
  //#endregion
103
103
  export { syncGoogleDriveFiles };
104
104
 
105
- //# sourceMappingURL=google-drive-sync-DEPcqFca.js.map
105
+ //# sourceMappingURL=google-drive-sync-D1n7WKZn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"google-drive-sync-DEPcqFca.js","names":[],"sources":["../src/sync/google-drive-sync.ts"],"sourcesContent":["import { appendInteraction } from \"../fs/interactions-writer.js\";\nimport { indexInLanceDB } from \"../core/lancedb.js\";\nimport { readInteractions } from \"../fs/interactions-writer.js\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface DriveFile {\n id: string;\n name: string;\n mimeType: string;\n webViewLink?: string;\n modifiedTime?: string;\n size?: string;\n}\n\nexport interface DriveFilesResponse {\n files: DriveFile[];\n nextPageToken?: string;\n}\n\nexport interface DriveSyncOptions {\n slug: string;\n dataDir: string;\n accessToken: string;\n customerName?: string; // If not provided, use slug\n maxFiles?: number;\n}\n\nexport interface DriveSyncResult {\n synced: number;\n skipped: number;\n errors: string[];\n}\n\nconst GOOGLE_DOC_MIME = \"application/vnd.google-apps.document\";\nconst DRIVE_API_BASE = \"https://www.googleapis.com/drive/v3\";\n\nexport async function syncGoogleDriveFiles(opts: DriveSyncOptions): Promise<DriveSyncResult> {\n const { slug, dataDir, accessToken } = opts;\n const searchName = opts.customerName ?? slug;\n const maxFiles = opts.maxFiles ?? 200;\n\n const result: DriveSyncResult = { synced: 0, skipped: 0, errors: [] };\n\n // Load existing interactions to detect already-synced files\n let existingInteractions = \"\";\n try {\n existingInteractions = await readInteractions(dataDir, slug);\n } catch {\n existingInteractions = \"\";\n }\n\n const encodedQuery = encodeURIComponent(\n `name contains \"${searchName}\" and mimeType!=\"application/vnd.google-apps.folder\"`\n );\n const fields = encodeURIComponent(\n \"files(id,name,mimeType,webViewLink,modifiedTime,size),nextPageToken\"\n );\n\n let pageToken: string | undefined;\n let totalFetched = 0;\n\n do {\n let url = `${DRIVE_API_BASE}/files?q=${encodedQuery}&fields=${fields}&pageSize=50`;\n if (pageToken) {\n url += `&pageToken=${encodeURIComponent(pageToken)}`;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n } catch (err) {\n result.errors.push(`Drive API request failed: ${(err as Error).message}`);\n break;\n }\n\n if (!response.ok) {\n result.errors.push(\n `Drive API error ${response.status}: ${await response.text().catch(() => \"unknown\")}`\n );\n break;\n }\n\n let data: DriveFilesResponse;\n try {\n data = (await response.json()) as DriveFilesResponse;\n } catch (err) {\n result.errors.push(`Failed to parse Drive API response: ${(err as Error).message}`);\n break;\n }\n\n const files = data.files ?? [];\n pageToken = data.nextPageToken;\n\n for (const file of files) {\n if (totalFetched >= maxFiles) break;\n totalFetched++;\n\n const sourceRef = `google://drive/${file.id}`;\n\n // Skip already-synced files\n if (existingInteractions.includes(sourceRef)) {\n result.skipped++;\n continue;\n }\n\n try {\n if (file.mimeType === GOOGLE_DOC_MIME) {\n // Export Google Doc as plain text\n const exportUrl = `${DRIVE_API_BASE}/files/${file.id}/export?mimeType=${encodeURIComponent(\"text/plain\")}`;\n const exportRes = await fetch(exportUrl, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n\n if (!exportRes.ok) {\n result.errors.push(`Failed to export doc '${file.name}': HTTP ${exportRes.status}`);\n continue;\n }\n\n const text = await exportRes.text();\n\n // Save to attachments directory\n const attachmentsDir = path.join(dataDir, \"customers\", slug, \"attachments\");\n fs.mkdirSync(attachmentsDir, { recursive: true });\n const safeFilename = file.name.replace(/[/\\\\?%*:|\"<>]/g, \"-\") + \".txt\";\n fs.writeFileSync(path.join(attachmentsDir, safeFilename), text, \"utf-8\");\n\n // Append interaction\n await appendInteraction(dataDir, slug, {\n date: file.modifiedTime\n ? file.modifiedTime.slice(0, 10)\n : new Date().toISOString().slice(0, 10),\n type: \"Note\",\n with: \"Google Drive\",\n summary: `Attachment: ${file.name}`,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n\n // Index in LanceDB\n const lanceOpts: { date?: string; type?: string } = { type: \"attachment\" };\n if (file.modifiedTime) lanceOpts.date = file.modifiedTime.slice(0, 10);\n await indexInLanceDB(dataDir, slug, text.slice(0, 2000), sourceRef, lanceOpts);\n } else {\n // Non-Doc file: record via appendInteraction (no binary download)\n await appendInteraction(dataDir, slug, {\n date: file.modifiedTime\n ? file.modifiedTime.slice(0, 10)\n : new Date().toISOString().slice(0, 10),\n type: \"Note\",\n with: \"Google Drive\",\n summary: `Attachment: ${file.name}${file.webViewLink ? ` — ${file.webViewLink}` : \"\"}`,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n }\n\n result.synced++;\n existingInteractions += sourceRef; // prevent double-sync within same run\n } catch (err) {\n result.errors.push(`Error processing '${file.name}': ${(err as Error).message}`);\n }\n }\n\n if (totalFetched >= maxFiles) break;\n } while (pageToken);\n\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"google-drive-sync-D1n7WKZn.js","names":[],"sources":["../src/sync/google-drive-sync.ts"],"sourcesContent":["import { appendInteraction } from \"../fs/interactions-writer.js\";\nimport { indexInLanceDB } from \"../core/lancedb.js\";\nimport { readInteractions } from \"../fs/interactions-writer.js\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface DriveFile {\n id: string;\n name: string;\n mimeType: string;\n webViewLink?: string;\n modifiedTime?: string;\n size?: string;\n}\n\nexport interface DriveFilesResponse {\n files: DriveFile[];\n nextPageToken?: string;\n}\n\nexport interface DriveSyncOptions {\n slug: string;\n dataDir: string;\n accessToken: string;\n customerName?: string; // If not provided, use slug\n maxFiles?: number;\n}\n\nexport interface DriveSyncResult {\n synced: number;\n skipped: number;\n errors: string[];\n}\n\nconst GOOGLE_DOC_MIME = \"application/vnd.google-apps.document\";\nconst DRIVE_API_BASE = \"https://www.googleapis.com/drive/v3\";\n\nexport async function syncGoogleDriveFiles(opts: DriveSyncOptions): Promise<DriveSyncResult> {\n const { slug, dataDir, accessToken } = opts;\n const searchName = opts.customerName ?? slug;\n const maxFiles = opts.maxFiles ?? 200;\n\n const result: DriveSyncResult = { synced: 0, skipped: 0, errors: [] };\n\n // Load existing interactions to detect already-synced files\n let existingInteractions = \"\";\n try {\n existingInteractions = await readInteractions(dataDir, slug);\n } catch {\n existingInteractions = \"\";\n }\n\n const encodedQuery = encodeURIComponent(\n `name contains \"${searchName}\" and mimeType!=\"application/vnd.google-apps.folder\"`\n );\n const fields = encodeURIComponent(\n \"files(id,name,mimeType,webViewLink,modifiedTime,size),nextPageToken\"\n );\n\n let pageToken: string | undefined;\n let totalFetched = 0;\n\n do {\n let url = `${DRIVE_API_BASE}/files?q=${encodedQuery}&fields=${fields}&pageSize=50`;\n if (pageToken) {\n url += `&pageToken=${encodeURIComponent(pageToken)}`;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n } catch (err) {\n result.errors.push(`Drive API request failed: ${(err as Error).message}`);\n break;\n }\n\n if (!response.ok) {\n result.errors.push(\n `Drive API error ${response.status}: ${await response.text().catch(() => \"unknown\")}`\n );\n break;\n }\n\n let data: DriveFilesResponse;\n try {\n data = (await response.json()) as DriveFilesResponse;\n } catch (err) {\n result.errors.push(`Failed to parse Drive API response: ${(err as Error).message}`);\n break;\n }\n\n const files = data.files ?? [];\n pageToken = data.nextPageToken;\n\n for (const file of files) {\n if (totalFetched >= maxFiles) break;\n totalFetched++;\n\n const sourceRef = `google://drive/${file.id}`;\n\n // Skip already-synced files\n if (existingInteractions.includes(sourceRef)) {\n result.skipped++;\n continue;\n }\n\n try {\n if (file.mimeType === GOOGLE_DOC_MIME) {\n // Export Google Doc as plain text\n const exportUrl = `${DRIVE_API_BASE}/files/${file.id}/export?mimeType=${encodeURIComponent(\"text/plain\")}`;\n const exportRes = await fetch(exportUrl, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n\n if (!exportRes.ok) {\n result.errors.push(`Failed to export doc '${file.name}': HTTP ${exportRes.status}`);\n continue;\n }\n\n const text = await exportRes.text();\n\n // Save to attachments directory\n const attachmentsDir = path.join(dataDir, \"customers\", slug, \"attachments\");\n fs.mkdirSync(attachmentsDir, { recursive: true });\n const safeFilename = file.name.replace(/[/\\\\?%*:|\"<>]/g, \"-\") + \".txt\";\n fs.writeFileSync(path.join(attachmentsDir, safeFilename), text, \"utf-8\");\n\n // Append interaction\n await appendInteraction(dataDir, slug, {\n date: file.modifiedTime\n ? file.modifiedTime.slice(0, 10)\n : new Date().toISOString().slice(0, 10),\n type: \"Note\",\n with: \"Google Drive\",\n summary: `Attachment: ${file.name}`,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n\n // Index in LanceDB\n const lanceOpts: { date?: string; type?: string } = { type: \"attachment\" };\n if (file.modifiedTime) lanceOpts.date = file.modifiedTime.slice(0, 10);\n await indexInLanceDB(dataDir, slug, text.slice(0, 2000), sourceRef, lanceOpts);\n } else {\n // Non-Doc file: record via appendInteraction (no binary download)\n await appendInteraction(dataDir, slug, {\n date: file.modifiedTime\n ? file.modifiedTime.slice(0, 10)\n : new Date().toISOString().slice(0, 10),\n type: \"Note\",\n with: \"Google Drive\",\n summary: `Attachment: ${file.name}${file.webViewLink ? ` — ${file.webViewLink}` : \"\"}`,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n }\n\n result.synced++;\n existingInteractions += sourceRef; // prevent double-sync within same run\n } catch (err) {\n result.errors.push(`Error processing '${file.name}': ${(err as Error).message}`);\n }\n }\n\n if (totalFetched >= maxFiles) break;\n } while (pageToken);\n\n return result;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { i as readMainFacts, r as listCustomerSlugs } from "./customer-dir-DIylZ8Q6.js";
2
- import { n as findDuplicateClusters, r as normalizeDomain } from "./identity-CI6olMNm.js";
1
+ import { a as readMainFacts, i as listCustomerSlugs } from "./customer-dir-CkMMXhb0.js";
2
+ import { n as findDuplicateClusters, r as normalizeDomain } from "./identity-_uZ3Lbr2.js";
3
3
  //#region src/core/hygiene.ts
4
4
  async function scanHygiene(dataDir) {
5
5
  const issues = [];
@@ -35,4 +35,4 @@ async function scanHygiene(dataDir) {
35
35
  //#endregion
36
36
  export { scanHygiene };
37
37
 
38
- //# sourceMappingURL=hygiene-DZqfYpFf.js.map
38
+ //# sourceMappingURL=hygiene-DzQPnc6P.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hygiene-DZqfYpFf.js","names":[],"sources":["../src/core/hygiene.ts"],"sourcesContent":["import { readMainFacts, listCustomerSlugs } from \"../fs/customer-dir.js\";\nimport { findDuplicateClusters, normalizeDomain } from \"./identity.js\";\n\n/**\n * Data-hygiene agent (domino D5 / C5): scans customers for quality issues\n * (missing contact info, malformed fields, duplicates) and suggests fixes.\n * Fixes are meant to be applied through the approval gate (D4) — clean data\n * lifts the quality of every downstream AI feature.\n */\nexport type HygieneIssueType = \"missing_contact\" | \"format_domain\" | \"format_email\" | \"duplicate\";\n\nexport interface HygieneIssue {\n type: HygieneIssueType;\n slug: string;\n field?: string;\n detail: string;\n suggestedFix?: string;\n}\n\nexport async function scanHygiene(dataDir: string): Promise<HygieneIssue[]> {\n const issues: HygieneIssue[] = [];\n\n for (const slug of listCustomerSlugs(dataDir)) {\n const facts = await readMainFacts(dataDir, slug).catch(() => null);\n if (!facts) continue;\n\n if (!facts.domain && !facts.email) {\n issues.push({ type: \"missing_contact\", slug, detail: \"No domain or email\" });\n }\n if (facts.domain && /^https?:\\/\\/|^www\\./i.test(facts.domain)) {\n issues.push({\n type: \"format_domain\",\n slug,\n field: \"domain\",\n detail: `Domain not normalized: ${facts.domain}`,\n suggestedFix: normalizeDomain(facts.domain),\n });\n }\n if (facts.email && !facts.email.includes(\"@\")) {\n issues.push({\n type: \"format_email\",\n slug,\n field: \"email\",\n detail: `Email missing '@': ${facts.email}`,\n });\n }\n }\n\n // Duplicate clusters (reuse identity resolution)\n for (const cluster of await findDuplicateClusters(dataDir)) {\n for (const slug of cluster.slugs) {\n issues.push({\n type: \"duplicate\",\n slug,\n detail: `Shares canonical domain '${cluster.key}' with: ${cluster.slugs\n .filter((s) => s !== slug)\n .join(\", \")}`,\n });\n }\n }\n\n return issues;\n}\n"],"mappings":";;;AAmBA,eAAsB,YAAY,SAA0C;CAC1E,MAAM,SAAyB,CAAC;CAEhC,KAAK,MAAM,QAAQ,kBAAkB,OAAO,GAAG;EAC7C,MAAM,QAAQ,MAAM,cAAc,SAAS,IAAI,EAAE,YAAY,IAAI;EACjE,IAAI,CAAC,OAAO;EAEZ,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,OAC1B,OAAO,KAAK;GAAE,MAAM;GAAmB;GAAM,QAAQ;EAAqB,CAAC;EAE7E,IAAI,MAAM,UAAU,uBAAuB,KAAK,MAAM,MAAM,GAC1D,OAAO,KAAK;GACV,MAAM;GACN;GACA,OAAO;GACP,QAAQ,0BAA0B,MAAM;GACxC,cAAc,gBAAgB,MAAM,MAAM;EAC5C,CAAC;EAEH,IAAI,MAAM,SAAS,CAAC,MAAM,MAAM,SAAS,GAAG,GAC1C,OAAO,KAAK;GACV,MAAM;GACN;GACA,OAAO;GACP,QAAQ,sBAAsB,MAAM;EACtC,CAAC;CAEL;CAGA,KAAK,MAAM,WAAW,MAAM,sBAAsB,OAAO,GACvD,KAAK,MAAM,QAAQ,QAAQ,OACzB,OAAO,KAAK;EACV,MAAM;EACN;EACA,QAAQ,4BAA4B,QAAQ,IAAI,UAAU,QAAQ,MAC/D,QAAQ,MAAM,MAAM,IAAI,EACxB,KAAK,IAAI;CACd,CAAC;CAIL,OAAO;AACT"}
1
+ {"version":3,"file":"hygiene-DzQPnc6P.js","names":[],"sources":["../src/core/hygiene.ts"],"sourcesContent":["import { readMainFacts, listCustomerSlugs } from \"../fs/customer-dir.js\";\nimport { findDuplicateClusters, normalizeDomain } from \"./identity.js\";\n\n/**\n * Data-hygiene agent (domino D5 / C5): scans customers for quality issues\n * (missing contact info, malformed fields, duplicates) and suggests fixes.\n * Fixes are meant to be applied through the approval gate (D4) — clean data\n * lifts the quality of every downstream AI feature.\n */\nexport type HygieneIssueType = \"missing_contact\" | \"format_domain\" | \"format_email\" | \"duplicate\";\n\nexport interface HygieneIssue {\n type: HygieneIssueType;\n slug: string;\n field?: string;\n detail: string;\n suggestedFix?: string;\n}\n\nexport async function scanHygiene(dataDir: string): Promise<HygieneIssue[]> {\n const issues: HygieneIssue[] = [];\n\n for (const slug of listCustomerSlugs(dataDir)) {\n const facts = await readMainFacts(dataDir, slug).catch(() => null);\n if (!facts) continue;\n\n if (!facts.domain && !facts.email) {\n issues.push({ type: \"missing_contact\", slug, detail: \"No domain or email\" });\n }\n if (facts.domain && /^https?:\\/\\/|^www\\./i.test(facts.domain)) {\n issues.push({\n type: \"format_domain\",\n slug,\n field: \"domain\",\n detail: `Domain not normalized: ${facts.domain}`,\n suggestedFix: normalizeDomain(facts.domain),\n });\n }\n if (facts.email && !facts.email.includes(\"@\")) {\n issues.push({\n type: \"format_email\",\n slug,\n field: \"email\",\n detail: `Email missing '@': ${facts.email}`,\n });\n }\n }\n\n // Duplicate clusters (reuse identity resolution)\n for (const cluster of await findDuplicateClusters(dataDir)) {\n for (const slug of cluster.slugs) {\n issues.push({\n type: \"duplicate\",\n slug,\n detail: `Shares canonical domain '${cluster.key}' with: ${cluster.slugs\n .filter((s) => s !== slug)\n .join(\", \")}`,\n });\n }\n }\n\n return issues;\n}\n"],"mappings":";;;AAmBA,eAAsB,YAAY,SAA0C;CAC1E,MAAM,SAAyB,CAAC;CAEhC,KAAK,MAAM,QAAQ,kBAAkB,OAAO,GAAG;EAC7C,MAAM,QAAQ,MAAM,cAAc,SAAS,IAAI,EAAE,YAAY,IAAI;EACjE,IAAI,CAAC,OAAO;EAEZ,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,OAC1B,OAAO,KAAK;GAAE,MAAM;GAAmB;GAAM,QAAQ;EAAqB,CAAC;EAE7E,IAAI,MAAM,UAAU,uBAAuB,KAAK,MAAM,MAAM,GAC1D,OAAO,KAAK;GACV,MAAM;GACN;GACA,OAAO;GACP,QAAQ,0BAA0B,MAAM;GACxC,cAAc,gBAAgB,MAAM,MAAM;EAC5C,CAAC;EAEH,IAAI,MAAM,SAAS,CAAC,MAAM,MAAM,SAAS,GAAG,GAC1C,OAAO,KAAK;GACV,MAAM;GACN;GACA,OAAO;GACP,QAAQ,sBAAsB,MAAM;EACtC,CAAC;CAEL;CAGA,KAAK,MAAM,WAAW,MAAM,sBAAsB,OAAO,GACvD,KAAK,MAAM,QAAQ,QAAQ,OACzB,OAAO,KAAK;EACV,MAAM;EACN;EACA,QAAQ,4BAA4B,QAAQ,IAAI,UAAU,QAAQ,MAC/D,QAAQ,MAAM,MAAM,IAAI,EACxB,KAAK,IAAI;CACd,CAAC;CAIL,OAAO;AACT"}
@@ -0,0 +1,2 @@
1
+ import { n as findDuplicateClusters } from "./identity-_uZ3Lbr2.js";
2
+ export { findDuplicateClusters };
@@ -1,4 +1,4 @@
1
- import { i as readMainFacts, r as listCustomerSlugs } from "./customer-dir-DIylZ8Q6.js";
1
+ import { a as readMainFacts, i as listCustomerSlugs } from "./customer-dir-CkMMXhb0.js";
2
2
  //#region src/core/identity.ts
3
3
  /**
4
4
  * Identity resolution (CDP v1, N4-3): deterministic deduplication of customers
@@ -38,4 +38,4 @@ async function findDuplicateClusters(dataDir) {
38
38
  //#endregion
39
39
  export { findDuplicateClusters as n, normalizeDomain as r, canonicalKey as t };
40
40
 
41
- //# sourceMappingURL=identity-CI6olMNm.js.map
41
+ //# sourceMappingURL=identity-_uZ3Lbr2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"identity-CI6olMNm.js","names":[],"sources":["../src/core/identity.ts"],"sourcesContent":["import { readMainFacts, listCustomerSlugs } from \"../fs/customer-dir.js\";\n\n/**\n * Identity resolution (CDP v1, N4-3): deterministic deduplication of customers\n * by a canonical key (normalized domain, falling back to email domain). Reports\n * clusters of likely-duplicate customers so they can be merged.\n */\nexport function normalizeDomain(value: string): string {\n let v = value.trim().toLowerCase();\n if (v.includes(\"@\")) v = v.split(\"@\").pop() ?? v; // email -> domain\n v = v.replace(/^https?:\\/\\//, \"\").replace(/^www\\./, \"\");\n v = v.replace(/\\/.*$/, \"\"); // drop path\n return v;\n}\n\nexport interface DuplicateCluster {\n key: string;\n slugs: string[];\n}\n\n/** Canonical key for a customer: normalized domain, else email domain, else \"\". */\nexport async function canonicalKey(dataDir: string, slug: string): Promise<string> {\n const facts = await readMainFacts(dataDir, slug).catch(() => null);\n if (!facts) return \"\";\n if (facts.domain) return normalizeDomain(facts.domain);\n if (facts.email) return normalizeDomain(facts.email);\n return \"\";\n}\n\n/** Group customers by canonical key; clusters with ≥2 members are duplicates. */\nexport async function findDuplicateClusters(dataDir: string): Promise<DuplicateCluster[]> {\n const byKey = new Map<string, string[]>();\n for (const slug of listCustomerSlugs(dataDir)) {\n const key = await canonicalKey(dataDir, slug);\n if (!key) continue;\n byKey.set(key, [...(byKey.get(key) ?? []), slug]);\n }\n const clusters: DuplicateCluster[] = [];\n for (const [key, slugs] of byKey) {\n if (slugs.length >= 2) clusters.push({ key, slugs });\n }\n return clusters;\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,gBAAgB,OAAuB;CACrD,IAAI,IAAI,MAAM,KAAK,EAAE,YAAY;CACjC,IAAI,EAAE,SAAS,GAAG,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK;CAC/C,IAAI,EAAE,QAAQ,gBAAgB,EAAE,EAAE,QAAQ,UAAU,EAAE;CACtD,IAAI,EAAE,QAAQ,SAAS,EAAE;CACzB,OAAO;AACT;;AAQA,eAAsB,aAAa,SAAiB,MAA+B;CACjF,MAAM,QAAQ,MAAM,cAAc,SAAS,IAAI,EAAE,YAAY,IAAI;CACjE,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,MAAM,QAAQ,OAAO,gBAAgB,MAAM,MAAM;CACrD,IAAI,MAAM,OAAO,OAAO,gBAAgB,MAAM,KAAK;CACnD,OAAO;AACT;;AAGA,eAAsB,sBAAsB,SAA8C;CACxF,MAAM,wBAAQ,IAAI,IAAsB;CACxC,KAAK,MAAM,QAAQ,kBAAkB,OAAO,GAAG;EAC7C,MAAM,MAAM,MAAM,aAAa,SAAS,IAAI;EAC5C,IAAI,CAAC,KAAK;EACV,MAAM,IAAI,KAAK,CAAC,GAAI,MAAM,IAAI,GAAG,KAAK,CAAC,GAAI,IAAI,CAAC;CAClD;CACA,MAAM,WAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OACzB,IAAI,MAAM,UAAU,GAAG,SAAS,KAAK;EAAE;EAAK;CAAM,CAAC;CAErD,OAAO;AACT"}
1
+ {"version":3,"file":"identity-_uZ3Lbr2.js","names":[],"sources":["../src/core/identity.ts"],"sourcesContent":["import { readMainFacts, listCustomerSlugs } from \"../fs/customer-dir.js\";\n\n/**\n * Identity resolution (CDP v1, N4-3): deterministic deduplication of customers\n * by a canonical key (normalized domain, falling back to email domain). Reports\n * clusters of likely-duplicate customers so they can be merged.\n */\nexport function normalizeDomain(value: string): string {\n let v = value.trim().toLowerCase();\n if (v.includes(\"@\")) v = v.split(\"@\").pop() ?? v; // email -> domain\n v = v.replace(/^https?:\\/\\//, \"\").replace(/^www\\./, \"\");\n v = v.replace(/\\/.*$/, \"\"); // drop path\n return v;\n}\n\nexport interface DuplicateCluster {\n key: string;\n slugs: string[];\n}\n\n/** Canonical key for a customer: normalized domain, else email domain, else \"\". */\nexport async function canonicalKey(dataDir: string, slug: string): Promise<string> {\n const facts = await readMainFacts(dataDir, slug).catch(() => null);\n if (!facts) return \"\";\n if (facts.domain) return normalizeDomain(facts.domain);\n if (facts.email) return normalizeDomain(facts.email);\n return \"\";\n}\n\n/** Group customers by canonical key; clusters with ≥2 members are duplicates. */\nexport async function findDuplicateClusters(dataDir: string): Promise<DuplicateCluster[]> {\n const byKey = new Map<string, string[]>();\n for (const slug of listCustomerSlugs(dataDir)) {\n const key = await canonicalKey(dataDir, slug);\n if (!key) continue;\n byKey.set(key, [...(byKey.get(key) ?? []), slug]);\n }\n const clusters: DuplicateCluster[] = [];\n for (const [key, slugs] of byKey) {\n if (slugs.length >= 2) clusters.push({ key, slugs });\n }\n return clusters;\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,gBAAgB,OAAuB;CACrD,IAAI,IAAI,MAAM,KAAK,EAAE,YAAY;CACjC,IAAI,EAAE,SAAS,GAAG,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK;CAC/C,IAAI,EAAE,QAAQ,gBAAgB,EAAE,EAAE,QAAQ,UAAU,EAAE;CACtD,IAAI,EAAE,QAAQ,SAAS,EAAE;CACzB,OAAO;AACT;;AAQA,eAAsB,aAAa,SAAiB,MAA+B;CACjF,MAAM,QAAQ,MAAM,cAAc,SAAS,IAAI,EAAE,YAAY,IAAI;CACjE,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,MAAM,QAAQ,OAAO,gBAAgB,MAAM,MAAM;CACrD,IAAI,MAAM,OAAO,OAAO,gBAAgB,MAAM,KAAK;CACnD,OAAO;AACT;;AAGA,eAAsB,sBAAsB,SAA8C;CACxF,MAAM,wBAAQ,IAAI,IAAsB;CACxC,KAAK,MAAM,QAAQ,kBAAkB,OAAO,GAAG;EAC7C,MAAM,MAAM,MAAM,aAAa,SAAS,IAAI;EAC5C,IAAI,CAAC,KAAK;EACV,MAAM,IAAI,KAAK,CAAC,GAAI,MAAM,IAAI,GAAG,KAAK,CAAC,GAAI,IAAI,CAAC;CAClD;CACA,MAAM,WAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OACzB,IAAI,MAAM,UAAU,GAAG,SAAS,KAAK;EAAE;EAAK;CAAM,CAAC;CAErD,OAAO;AACT"}