@datasynx/agentic-crm 0.1.0 → 1.1.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 (309) hide show
  1. package/README.md +270 -669
  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-CDysGnRg.js} +6 -6
  5. package/dist/{ask-CID3jnuL.js.map → ask-CDysGnRg.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/attachments-CX2GAtsw.cjs +517 -0
  11. package/dist/attachments-CX2GAtsw.cjs.map +1 -0
  12. package/dist/attachments-D207gXfN.js +514 -0
  13. package/dist/attachments-D207gXfN.js.map +1 -0
  14. package/dist/attachments-rLa96rOK.js +514 -0
  15. package/dist/attachments-rLa96rOK.js.map +1 -0
  16. package/dist/auth-B5DcjJ_6.js +2 -0
  17. package/dist/{auth-DFWwWcYD.js → auth-DDXZTwS0.js} +4 -13
  18. package/dist/auth-DDXZTwS0.js.map +1 -0
  19. package/dist/{autofill-Di_-SP7t.js → autofill-B9VtlR2j.js} +2 -2
  20. package/dist/{autofill-Di_-SP7t.js.map → autofill-B9VtlR2j.js.map} +1 -1
  21. package/dist/{backup-CeMk9z86.js → backup-CTlIxUdO.js} +5 -7
  22. package/dist/backup-CTlIxUdO.js.map +1 -0
  23. package/dist/backup-LFnC09oV.js +2 -0
  24. package/dist/chunk-BfDYWZQ8.cjs +32 -0
  25. package/dist/chunk-BfDYWZQ8.cjs.map +1 -0
  26. package/dist/chunk-BhUZmQg5.js +32 -0
  27. package/dist/chunk-BhUZmQg5.js.map +1 -0
  28. package/dist/chunk-ChC83jai.js +2 -0
  29. package/dist/chunk-e_w8qqtP.js +32 -0
  30. package/dist/chunk-e_w8qqtP.js.map +1 -0
  31. package/dist/{churn-C28IgnAj.js → churn-DN9WDGNM.js} +3 -3
  32. package/dist/{churn-C28IgnAj.js.map → churn-DN9WDGNM.js.map} +1 -1
  33. package/dist/cli.js +285 -186
  34. package/dist/cli.js.map +1 -1
  35. package/dist/{compliance-CKSBoQUe.js → compliance-Bc12Hn9a.js} +3 -3
  36. package/dist/{compliance-CKSBoQUe.js.map → compliance-Bc12Hn9a.js.map} +1 -1
  37. package/dist/{compliance-CujOqAKk.js → compliance-TqYQXhBj.js} +1 -1
  38. package/dist/{compliance-B1kk5-YS.js → compliance-kq0xHRw3.js} +3 -3
  39. package/dist/{compliance-B1kk5-YS.js.map → compliance-kq0xHRw3.js.map} +1 -1
  40. package/dist/{compliance-B91zNvCR.cjs → compliance-pAj9FcGI.cjs} +3 -3
  41. package/dist/{compliance-B91zNvCR.cjs.map → compliance-pAj9FcGI.cjs.map} +1 -1
  42. package/dist/{context-builder-BzWAp3Zs.js → context-builder-7Uab5-G4.js} +3 -2
  43. package/dist/context-builder-7Uab5-G4.js.map +1 -0
  44. package/dist/context-builder-hmOPvgso.js +2 -0
  45. package/dist/{custom-fields-CzNeD3_v.js → custom-fields-BMyz5Ruh.js} +1 -1
  46. package/dist/{custom-fields-Pl2t9xzp.js → custom-fields-GzpOHW_2.js} +4 -13
  47. package/dist/custom-fields-GzpOHW_2.js.map +1 -0
  48. package/dist/{custom-objects-CIFrmQ2V.js → custom-objects-BNy-ayR-.js} +1 -1
  49. package/dist/{custom-objects-BHgn1GEX.js → custom-objects-CxW1gHwJ.js} +10 -25
  50. package/dist/custom-objects-CxW1gHwJ.js.map +1 -0
  51. package/dist/{customer-dir-DIylZ8Q6.js → customer-dir-CkMMXhb0.js} +9 -4
  52. package/dist/customer-dir-CkMMXhb0.js.map +1 -0
  53. package/dist/daemon/worker.js +66 -40
  54. package/dist/daemon/worker.js.map +1 -1
  55. package/dist/doctor-C14-vnJ1.js +103 -0
  56. package/dist/doctor-C14-vnJ1.js.map +1 -0
  57. package/dist/email-body-BFSRa0AW.cjs +42 -0
  58. package/dist/email-body-BFSRa0AW.cjs.map +1 -0
  59. package/dist/email-body-BOd7U-D2.js +42 -0
  60. package/dist/email-body-BOd7U-D2.js.map +1 -0
  61. package/dist/{enrichment-3XvgGDfB.js → enrichment-CDFdWmvD.js} +3 -3
  62. package/dist/{enrichment-3XvgGDfB.js.map → enrichment-CDFdWmvD.js.map} +1 -1
  63. package/dist/{file-lock-B_zi7NQl.js → file-lock-CcHotQkZ.js} +3 -4
  64. package/dist/file-lock-CcHotQkZ.js.map +1 -0
  65. package/dist/{gmail-sync-DIaxInDT.js → gmail-sync-B4Iu3AQb.js} +56 -22
  66. package/dist/gmail-sync-B4Iu3AQb.js.map +1 -0
  67. package/dist/{gmail-sync-hHm9gaWd.cjs → gmail-sync-BpSVESSe.cjs} +55 -21
  68. package/dist/gmail-sync-BpSVESSe.cjs.map +1 -0
  69. package/dist/{gmail-sync-rQaVqKWd.js → gmail-sync-DIbrPnTK.js} +55 -21
  70. package/dist/gmail-sync-DIbrPnTK.js.map +1 -0
  71. package/dist/{gmail-webhook-handler-e5Od25FX.js → gmail-webhook-handler-BzOFbvgh.js} +4 -4
  72. package/dist/{gmail-webhook-handler-e5Od25FX.js.map → gmail-webhook-handler-BzOFbvgh.js.map} +1 -1
  73. package/dist/{gmail-webhook-handler-DS7OlRPX.js → gmail-webhook-handler-CvSDW_Js.js} +2 -2
  74. package/dist/{goal-engine-KpBftn4V.js → goal-engine-BbroPhqm.js} +10 -11
  75. package/dist/goal-engine-BbroPhqm.js.map +1 -0
  76. package/dist/{goal-engine-CUZSpERI.js → goal-engine-CfDAJTFt.js} +1 -1
  77. package/dist/{google-drive-sync-DEPcqFca.js → google-drive-sync-B_I1d54Y.js} +3 -3
  78. package/dist/{google-drive-sync-DEPcqFca.js.map → google-drive-sync-B_I1d54Y.js.map} +1 -1
  79. package/dist/html-BaeOCZKE.js +36 -0
  80. package/dist/html-BaeOCZKE.js.map +1 -0
  81. package/dist/html-CmOku6jS.cjs +47 -0
  82. package/dist/html-CmOku6jS.cjs.map +1 -0
  83. package/dist/{hygiene-DZqfYpFf.js → hygiene-DzQPnc6P.js} +3 -3
  84. package/dist/{hygiene-DZqfYpFf.js.map → hygiene-DzQPnc6P.js.map} +1 -1
  85. package/dist/identity-CB7j-Zr1.js +2 -0
  86. package/dist/{identity-CI6olMNm.js → identity-_uZ3Lbr2.js} +2 -2
  87. package/dist/{identity-CI6olMNm.js.map → identity-_uZ3Lbr2.js.map} +1 -1
  88. package/dist/{import-hubspot-BaK71U_K.js → import-hubspot-CTId9IGV.js} +51 -45
  89. package/dist/import-hubspot-CTId9IGV.js.map +1 -0
  90. package/dist/{index-YqwMd6aQ.d.cts → index-BAutNcAT.d.cts} +20 -12
  91. package/dist/index-BAutNcAT.d.cts.map +1 -0
  92. package/dist/{index-V8BFaH-b.d.ts → index-FzDsNSSb.d.ts} +12 -4
  93. package/dist/index-FzDsNSSb.d.ts.map +1 -0
  94. package/dist/index.cjs +19 -21
  95. package/dist/index.cjs.map +1 -1
  96. package/dist/index.d.cts +20 -12
  97. package/dist/index.d.cts.map +1 -1
  98. package/dist/index.d.ts +12 -4
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +19 -21
  101. package/dist/index.js.map +1 -1
  102. package/dist/interactions-writer-B2y-73lh.js +2 -0
  103. package/dist/{interactions-writer-SLHnoEeE.js → interactions-writer-B8XAzdqR.js} +34 -4
  104. package/dist/interactions-writer-B8XAzdqR.js.map +1 -0
  105. package/dist/{interactions-writer-CrPStUll.cjs → interactions-writer-BRJNrefF.cjs} +7 -3
  106. package/dist/interactions-writer-BRJNrefF.cjs.map +1 -0
  107. package/dist/{interactions-writer-DO3KcSR3.js → interactions-writer-ZQcpFOh9.js} +7 -3
  108. package/dist/interactions-writer-ZQcpFOh9.js.map +1 -0
  109. package/dist/json-store-WWsFzXub.js +43 -0
  110. package/dist/json-store-WWsFzXub.js.map +1 -0
  111. package/dist/{knowledge-base-D0Fh40kc.js → knowledge-base--063Kpa3.js} +51 -22
  112. package/dist/knowledge-base--063Kpa3.js.map +1 -0
  113. package/dist/{lancedb-CCBbpulq.js → lancedb-CswQEE5K.js} +1 -1
  114. package/dist/{lancedb-rlvWoPwl.js → lancedb-CuHKNsNZ.js} +4 -3
  115. package/dist/lancedb-CuHKNsNZ.js.map +1 -0
  116. package/dist/{lead-model-BCFzyktm.js → lead-model-CEmx7te7.js} +6 -14
  117. package/dist/lead-model-CEmx7te7.js.map +1 -0
  118. package/dist/{llm-Z8RIYkpF.js → llm-BnSUBisu.js} +2 -2
  119. package/dist/{llm-Z8RIYkpF.js.map → llm-BnSUBisu.js.map} +1 -1
  120. package/dist/{llm-iijeXmgq.cjs → llm-CXycmEl9.cjs} +2 -2
  121. package/dist/{llm-iijeXmgq.cjs.map → llm-CXycmEl9.cjs.map} +1 -1
  122. package/dist/{llm-DEjWcqmW.js → llm-DSX1-wFu.js} +1 -1
  123. package/dist/{llm-DvzZqva0.js → llm-PZzgPphl.js} +3 -3
  124. package/dist/{llm-DvzZqva0.js.map → llm-PZzgPphl.js.map} +1 -1
  125. package/dist/logger-BkInaGoV.cjs +167 -0
  126. package/dist/logger-BkInaGoV.cjs.map +1 -0
  127. package/dist/logger-Dyl4VcLO.js +147 -0
  128. package/dist/logger-Dyl4VcLO.js.map +1 -0
  129. package/dist/logger-UaF5p9d1.js +147 -0
  130. package/dist/logger-UaF5p9d1.js.map +1 -0
  131. package/dist/logger-vKQS34w9.js +2 -0
  132. package/dist/mcp-CdTJWTJf.d.cts.map +1 -1
  133. package/dist/mcp-CdTJWTJf.d.ts.map +1 -1
  134. package/dist/mcp.cjs +365 -319
  135. package/dist/mcp.cjs.map +1 -1
  136. package/dist/mcp.d.cts.map +1 -1
  137. package/dist/mcp.d.ts.map +1 -1
  138. package/dist/mcp.js +365 -319
  139. package/dist/mcp.js.map +1 -1
  140. package/dist/{memory-Cy6-Tbyl.js → memory-D8hmgD9d.js} +1 -1
  141. package/dist/{memory-Bb6ky3kb.js → memory-Dzr9dXSM.js} +4 -11
  142. package/dist/memory-Dzr9dXSM.js.map +1 -0
  143. package/dist/{microsoft-calendar-B6MMtUQK.js → microsoft-calendar-BgVR8GDv.js} +4 -4
  144. package/dist/{microsoft-calendar-B6MMtUQK.js.map → microsoft-calendar-BgVR8GDv.js.map} +1 -1
  145. package/dist/{microsoft-sync-CpZVoSuq.js → microsoft-sync-D30_XksI.js} +5 -5
  146. package/dist/{microsoft-sync-CpZVoSuq.js.map → microsoft-sync-D30_XksI.js.map} +1 -1
  147. package/dist/{nba-3wanmJ0U.js → nba-DwdfM93s.js} +3 -3
  148. package/dist/{nba-3wanmJ0U.js.map → nba-DwdfM93s.js.map} +1 -1
  149. package/dist/{notification-dispatcher-0vYNngWe.js → notification-dispatcher-inpKyuBz.js} +7 -3
  150. package/dist/notification-dispatcher-inpKyuBz.js.map +1 -0
  151. package/dist/{pipeline-writer-BqBrYrQc.js → pipeline-writer-0LJ6Qkat.js} +1 -1
  152. package/dist/{pipeline-writer-N2omexxp.cjs → pipeline-writer-B1tRAhuD.cjs} +11 -3
  153. package/dist/pipeline-writer-B1tRAhuD.cjs.map +1 -0
  154. package/dist/{pipeline-writer-BvVquKIe.js → pipeline-writer-CIllfnZl.js} +5 -3
  155. package/dist/pipeline-writer-CIllfnZl.js.map +1 -0
  156. package/dist/{pipeline-writer-eufx_0o1.js → pipeline-writer-rDj-ni6q.js} +6 -4
  157. package/dist/pipeline-writer-rDj-ni6q.js.map +1 -0
  158. package/dist/{proactive-agent-BgQXw3ac.js → proactive-agent-B7u3Bj_l.js} +6 -6
  159. package/dist/{proactive-agent-BgQXw3ac.js.map → proactive-agent-B7u3Bj_l.js.map} +1 -1
  160. package/dist/{proactive-worker-BrLHNhjH.js → proactive-worker-1zkm6aJD.js} +7 -8
  161. package/dist/proactive-worker-1zkm6aJD.js.map +1 -0
  162. package/dist/{push-manager-CowY-0IK.js → push-manager-BXM-IHfP.js} +1 -1
  163. package/dist/{push-manager-CdqIIkuh.js → push-manager-C0ECQgva.js} +4 -4
  164. package/dist/push-manager-C0ECQgva.js.map +1 -0
  165. package/dist/{quote-generator-OhSFsi3x.js → quote-generator-ByUyIYtw.js} +1 -1
  166. package/dist/{quote-generator-BfwENXzg.js → quote-generator-CTdR8eEI.js} +5 -5
  167. package/dist/quote-generator-CTdR8eEI.js.map +1 -0
  168. package/dist/rbac-DzbyFhVH.js +2 -0
  169. package/dist/{rbac-CTIktZaC.js → rbac-msmBc_tK.js} +19 -12
  170. package/dist/rbac-msmBc_tK.js.map +1 -0
  171. package/dist/regex-Jt5DatPi.js +13 -0
  172. package/dist/regex-Jt5DatPi.js.map +1 -0
  173. package/dist/{relationship-health-odxEoQdJ.js → relationship-health-ZZNXR1RZ.js} +8 -16
  174. package/dist/relationship-health-ZZNXR1RZ.js.map +1 -0
  175. package/dist/{revenue-simulation-Bqf2DLVB.js → revenue-simulation-D8f_YkUY.js} +9 -19
  176. package/dist/revenue-simulation-D8f_YkUY.js.map +1 -0
  177. package/dist/{revenue-simulation-BJdRTEHc.js → revenue-simulation-njJZlTqm.js} +1 -1
  178. package/dist/safe-path-mpp0dKtO.js +18 -0
  179. package/dist/safe-path-mpp0dKtO.js.map +1 -0
  180. package/dist/{segments-BqcD5HIl.js → segments-DI3LOQNe.js} +5 -14
  181. package/dist/segments-DI3LOQNe.js.map +1 -0
  182. package/dist/sequence-engine-C6nnewHX.js +2 -0
  183. package/dist/{sequence-engine-J1lTW_in.js → sequence-engine-DNTVLq7o.js} +15 -8
  184. package/dist/sequence-engine-DNTVLq7o.js.map +1 -0
  185. package/dist/{sequence-store-DaaWr0Os.js → sequence-store-CmYb6s0g.js} +6 -5
  186. package/dist/sequence-store-CmYb6s0g.js.map +1 -0
  187. package/dist/{server-Dyva03K8.js → server-DoRPPOeR.js} +308 -230
  188. package/dist/server-DoRPPOeR.js.map +1 -0
  189. package/dist/{session-D9ub6Wl1.js → session-B6XaP83h.js} +3 -3
  190. package/dist/session-B6XaP83h.js.map +1 -0
  191. package/dist/{session-B9AilxOE.js → session-BgGDyP2C.js} +3 -3
  192. package/dist/session-BgGDyP2C.js.map +1 -0
  193. package/dist/session-Bp4zTh4l.js +2 -0
  194. package/dist/{session-D0qFkBla.cjs → session-Mm7GQbSH.cjs} +3 -3
  195. package/dist/session-Mm7GQbSH.cjs.map +1 -0
  196. package/dist/{session-store-C8tEvMPw.js → session-store-DWxJ5Pof.js} +79 -17
  197. package/dist/session-store-DWxJ5Pof.js.map +1 -0
  198. package/dist/{session-store-B0QZE8Bx.cjs → session-store-yfwnj0OC.cjs} +126 -16
  199. package/dist/session-store-yfwnj0OC.cjs.map +1 -0
  200. package/dist/{sla-engine-5IhTsBUR.js → sla-engine-CP2KiKDS.js} +1 -1
  201. package/dist/{sla-engine-BqX-7u-7.js → sla-engine-O-A1ntu_.js} +2 -2
  202. package/dist/{sla-engine-BqX-7u-7.js.map → sla-engine-O-A1ntu_.js.map} +1 -1
  203. package/dist/{sop-Vp0UPWFW.js → sop-BV7ICAFR.js} +4 -11
  204. package/dist/sop-BV7ICAFR.js.map +1 -0
  205. package/dist/{sop-DkhVChGy.js → sop-D33qTHUb.js} +1 -1
  206. package/dist/survey-engine-DKctGcLQ.js +2 -0
  207. package/dist/{survey-engine-DBjCYqCv.js → survey-engine-DngXBv47.js} +5 -4
  208. package/dist/survey-engine-DngXBv47.js.map +1 -0
  209. package/dist/{sync-state-CwLSt_1m.js → sync-state-BaA8LbTI.js} +1 -1
  210. package/dist/{sync-state-ChaLbamC.js → sync-state-DMZgzpez.js} +4 -12
  211. package/dist/sync-state-DMZgzpez.js.map +1 -0
  212. package/dist/{ticket-writer-CjqKeIRD.js → ticket-writer-DsfpeLGZ.js} +1 -1
  213. package/dist/{ticket-writer-j2oX_Wal.js → ticket-writer-a9on36Wb.js} +12 -24
  214. package/dist/ticket-writer-a9on36Wb.js.map +1 -0
  215. package/dist/{tone-Bdm5uaht.js → tone-C7bqK69y.js} +5 -12
  216. package/dist/tone-C7bqK69y.js.map +1 -0
  217. package/dist/{tone-DRKlZgPr.cjs → tone-Cmc7O2Fx.cjs} +3 -9
  218. package/dist/tone-Cmc7O2Fx.cjs.map +1 -0
  219. package/dist/{tone-vNb2DAAD.js → tone-mXSftvTn.js} +3 -8
  220. package/dist/tone-mXSftvTn.js.map +1 -0
  221. package/dist/{transcript-watcher-CL2QUygI.js → transcript-watcher-BoClrJAz.js} +18 -11
  222. package/dist/transcript-watcher-BoClrJAz.js.map +1 -0
  223. package/dist/unmatched-transcripts-C92zAoM4.js +2 -0
  224. package/dist/unmatched-transcripts-DC-VQ9YS.js +16 -0
  225. package/dist/unmatched-transcripts-DC-VQ9YS.js.map +1 -0
  226. package/dist/update-deal-CWy1eLJI.js +2 -0
  227. package/dist/{update-deal-DKC79skb.js → update-deal-DSzr_Aau.js} +3 -3
  228. package/dist/{update-deal-DKC79skb.js.map → update-deal-DSzr_Aau.js.map} +1 -1
  229. package/dist/{usage-D0-TYJkw.js → usage-BVlFlKW_.js} +8 -6
  230. package/dist/usage-BVlFlKW_.js.map +1 -0
  231. package/dist/usage-CClTf5e6.cjs.map +1 -1
  232. package/dist/usage-D0u9a-lV.js.map +1 -1
  233. package/dist/{vault-DXCg29W-.js → vault-CfwZdNzC.js} +3 -4
  234. package/dist/vault-CfwZdNzC.js.map +1 -0
  235. package/dist/{vault-C1D3zScD.js → vault-DxKP4_R2.js} +1 -1
  236. package/dist/{webhooks-Xn6zO6kd.cjs → webhooks-CwW-3kvG.cjs} +5 -19
  237. package/dist/webhooks-CwW-3kvG.cjs.map +1 -0
  238. package/dist/{webhooks-7EpA05Qr.js → webhooks-DXr1IoKn.js} +8 -21
  239. package/dist/webhooks-DXr1IoKn.js.map +1 -0
  240. package/dist/{webhooks-BO2UAnmn.js → webhooks-sWZ8CJtR.js} +5 -18
  241. package/dist/webhooks-sWZ8CJtR.js.map +1 -0
  242. package/package.json +22 -2
  243. package/dist/approvals-DpjxGHFp.js.map +0 -1
  244. package/dist/auth-CyFuu9X_.js +0 -2
  245. package/dist/auth-DFWwWcYD.js.map +0 -1
  246. package/dist/backup-CeMk9z86.js.map +0 -1
  247. package/dist/backup-f_hC7rBV.js +0 -2
  248. package/dist/context-builder-BzWAp3Zs.js.map +0 -1
  249. package/dist/context-builder-DlrRcqmJ.js +0 -2
  250. package/dist/custom-fields-Pl2t9xzp.js.map +0 -1
  251. package/dist/custom-objects-BHgn1GEX.js.map +0 -1
  252. package/dist/customer-dir-DIylZ8Q6.js.map +0 -1
  253. package/dist/file-lock-B_zi7NQl.js.map +0 -1
  254. package/dist/gmail-sync-DIaxInDT.js.map +0 -1
  255. package/dist/gmail-sync-hHm9gaWd.cjs.map +0 -1
  256. package/dist/gmail-sync-rQaVqKWd.js.map +0 -1
  257. package/dist/goal-engine-KpBftn4V.js.map +0 -1
  258. package/dist/identity-gyfWdrcX.js +0 -2
  259. package/dist/import-hubspot-BaK71U_K.js.map +0 -1
  260. package/dist/index-V8BFaH-b.d.ts.map +0 -1
  261. package/dist/index-YqwMd6aQ.d.cts.map +0 -1
  262. package/dist/interactions-writer-CrPStUll.cjs.map +0 -1
  263. package/dist/interactions-writer-DO3KcSR3.js.map +0 -1
  264. package/dist/interactions-writer-SLHnoEeE.js.map +0 -1
  265. package/dist/interactions-writer-dSPy1XfO.js +0 -2
  266. package/dist/knowledge-base-D0Fh40kc.js.map +0 -1
  267. package/dist/lancedb-rlvWoPwl.js.map +0 -1
  268. package/dist/lead-model-BCFzyktm.js.map +0 -1
  269. package/dist/memory-Bb6ky3kb.js.map +0 -1
  270. package/dist/notification-dispatcher-0vYNngWe.js.map +0 -1
  271. package/dist/pipeline-writer-BvVquKIe.js.map +0 -1
  272. package/dist/pipeline-writer-N2omexxp.cjs.map +0 -1
  273. package/dist/pipeline-writer-eufx_0o1.js.map +0 -1
  274. package/dist/proactive-worker-BrLHNhjH.js.map +0 -1
  275. package/dist/push-manager-CdqIIkuh.js.map +0 -1
  276. package/dist/quote-generator-BfwENXzg.js.map +0 -1
  277. package/dist/rbac-C7c8tcES.js +0 -2
  278. package/dist/rbac-CTIktZaC.js.map +0 -1
  279. package/dist/relationship-health-odxEoQdJ.js.map +0 -1
  280. package/dist/revenue-simulation-Bqf2DLVB.js.map +0 -1
  281. package/dist/segments-BqcD5HIl.js.map +0 -1
  282. package/dist/sequence-engine-CCTHEBgi.js +0 -2
  283. package/dist/sequence-engine-J1lTW_in.js.map +0 -1
  284. package/dist/sequence-store-DaaWr0Os.js.map +0 -1
  285. package/dist/server-Dyva03K8.js.map +0 -1
  286. package/dist/session-B9AilxOE.js.map +0 -1
  287. package/dist/session-D0qFkBla.cjs.map +0 -1
  288. package/dist/session-D9ub6Wl1.js.map +0 -1
  289. package/dist/session-mWHA71Lw.js +0 -2
  290. package/dist/session-store-B0QZE8Bx.cjs.map +0 -1
  291. package/dist/session-store-C8tEvMPw.js.map +0 -1
  292. package/dist/sop-Vp0UPWFW.js.map +0 -1
  293. package/dist/survey-engine-C06hcQt3.js +0 -2
  294. package/dist/survey-engine-DBjCYqCv.js.map +0 -1
  295. package/dist/sync-state-ChaLbamC.js.map +0 -1
  296. package/dist/ticket-writer-j2oX_Wal.js.map +0 -1
  297. package/dist/tone-Bdm5uaht.js.map +0 -1
  298. package/dist/tone-DRKlZgPr.cjs.map +0 -1
  299. package/dist/tone-vNb2DAAD.js.map +0 -1
  300. package/dist/transcript-watcher-CL2QUygI.js.map +0 -1
  301. package/dist/unmatched-transcripts-BsH5bhkU.js +0 -26
  302. package/dist/unmatched-transcripts-BsH5bhkU.js.map +0 -1
  303. package/dist/unmatched-transcripts-D0PrJ9iz.js +0 -2
  304. package/dist/update-deal-BNwPGaTV.js +0 -2
  305. package/dist/usage-D0-TYJkw.js.map +0 -1
  306. package/dist/vault-DXCg29W-.js.map +0 -1
  307. package/dist/webhooks-7EpA05Qr.js.map +0 -1
  308. package/dist/webhooks-BO2UAnmn.js.map +0 -1
  309. package/dist/webhooks-Xn6zO6kd.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks-CwW-3kvG.cjs","names":["readJson"],"sources":["../src/core/webhooks.ts"],"sourcesContent":["import { createHash, createHmac, randomBytes } from \"crypto\";\nimport path from \"path\";\nimport { readJsonArray as readJson, writeJsonArray as writeJson } from \"../fs/json-store.js\";\n\n/**\n * Outbound webhooks (event-driven architecture, N5-2). Subscriptions live in\n * .agentic/webhooks.json; failed deliveries are queued in\n * .agentic/webhook-failures.json (replay store) and re-attempted by\n * retryFailures (e.g. from the daemon) — backoff via periodic replay.\n */\nexport interface WebhookSubscription {\n id: string;\n url: string;\n events: string[];\n secret?: string;\n createdAt: string;\n}\n\nexport interface WebhookFailure {\n id: string;\n subscriptionId: string;\n url: string;\n secret?: string;\n event: string;\n payload: unknown;\n attempts: number;\n lastError: string;\n queuedAt: string;\n}\n\nfunction subsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhooks.json\");\n}\nfunction failuresPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhook-failures.json\");\n}\n\nexport function loadWebhooks(dataDir: string): WebhookSubscription[] {\n return readJson<WebhookSubscription>(subsPath(dataDir), \"subscriptions\");\n}\n\nexport function addWebhook(\n dataDir: string,\n url: string,\n events: string[],\n secret?: string\n): WebhookSubscription {\n const sub: WebhookSubscription = {\n id: `wh_${randomBytes(5).toString(\"hex\")}`,\n url,\n events,\n ...(secret ? { secret } : {}),\n createdAt: new Date().toISOString(),\n };\n writeJson(subsPath(dataDir), \"subscriptions\", [...loadWebhooks(dataDir), sub]);\n return sub;\n}\n\nexport function removeWebhook(dataDir: string, id: string): boolean {\n const subs = loadWebhooks(dataDir);\n const next = subs.filter((s) => s.id !== id);\n if (next.length === subs.length) return false;\n writeJson(subsPath(dataDir), \"subscriptions\", next);\n return true;\n}\n\n/** A subscription matches an event by exact name, \"*\", or a \"prefix.*\" pattern. */\nexport function matchSubscriptions(\n subs: WebhookSubscription[],\n event: string\n): WebhookSubscription[] {\n return subs.filter((s) =>\n s.events.some((pat) => {\n if (pat === \"*\" || pat === event) return true;\n if (pat.endsWith(\".*\")) return event.startsWith(pat.slice(0, -1));\n return false;\n })\n );\n}\n\nexport function signPayload(secret: string, body: string): string {\n return createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n}\n\nexport function loadFailures(dataDir: string): WebhookFailure[] {\n return readJson<WebhookFailure>(failuresPath(dataDir), \"failures\");\n}\n\nasync function deliver(\n sub: WebhookSubscription,\n event: string,\n payload: unknown\n): Promise<{ ok: boolean; error?: string }> {\n const body = JSON.stringify({ event, payload, deliveredAt: new Date().toISOString() });\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-DXCRM-Event\": event,\n };\n if (sub.secret) headers[\"X-DXCRM-Signature\"] = `sha256=${signPayload(sub.secret, body)}`;\n try {\n const res = (await fetch(sub.url, { method: \"POST\", headers, body })) as {\n ok: boolean;\n status: number;\n };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };\n return { ok: true };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\n/** Emit an event to all matching subscriptions; queue failures for replay. */\nexport async function emitEvent(dataDir: string, event: string, payload: unknown): Promise<void> {\n const matched = matchSubscriptions(loadWebhooks(dataDir), event);\n if (matched.length === 0) return;\n const failures = loadFailures(dataDir);\n for (const sub of matched) {\n const r = await deliver(sub, event, payload);\n if (!r.ok) {\n failures.push({\n id: `whf_${createHash(\"sha256\").update(`${sub.id}:${event}:${Date.now()}`).digest(\"hex\").slice(0, 10)}`,\n subscriptionId: sub.id,\n url: sub.url,\n ...(sub.secret ? { secret: sub.secret } : {}),\n event,\n payload,\n attempts: 1,\n lastError: r.error ?? \"unknown\",\n queuedAt: new Date().toISOString(),\n });\n }\n }\n if (failures.length > 0) writeJson(failuresPath(dataDir), \"failures\", failures);\n}\n\n/** Re-attempt queued failures; remove on success, increment attempts on failure. */\nexport async function retryFailures(\n dataDir: string\n): Promise<{ retried: number; stillFailing: number }> {\n const failures = loadFailures(dataDir);\n const remaining: WebhookFailure[] = [];\n let retried = 0;\n for (const f of failures) {\n const sub: WebhookSubscription = {\n id: f.subscriptionId,\n url: f.url,\n events: [f.event],\n ...(f.secret ? { secret: f.secret } : {}),\n createdAt: f.queuedAt,\n };\n const r = await deliver(sub, f.event, f.payload);\n if (r.ok) retried++;\n else remaining.push({ ...f, attempts: f.attempts + 1, lastError: r.error ?? \"unknown\" });\n }\n writeJson(failuresPath(dataDir), \"failures\", remaining);\n return { retried, stillFailing: remaining.length };\n}\n"],"mappings":";;;;;;AA8BA,SAAS,SAAS,SAAyB;CACzC,OAAO,KAAA,QAAK,KAAK,SAAS,YAAY,eAAe;AACvD;AACA,SAAS,aAAa,SAAyB;CAC7C,OAAO,KAAA,QAAK,KAAK,SAAS,YAAY,uBAAuB;AAC/D;AAEA,SAAgB,aAAa,SAAwC;CACnE,OAAOA,sBAAAA,cAA8B,SAAS,OAAO,GAAG,eAAe;AACzE;;AA4BA,SAAgB,mBACd,MACA,OACuB;CACvB,OAAO,KAAK,QAAQ,MAClB,EAAE,OAAO,MAAM,QAAQ;EACrB,IAAI,QAAQ,OAAO,QAAQ,OAAO,OAAO;EACzC,IAAI,IAAI,SAAS,IAAI,GAAG,OAAO,MAAM,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC;EAChE,OAAO;CACT,CAAC,CACH;AACF;AAEA,SAAgB,YAAY,QAAgB,MAAsB;CAChE,QAAA,GAAA,OAAA,YAAkB,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAC/D;AAEA,SAAgB,aAAa,SAAmC;CAC9D,OAAOA,sBAAAA,cAAyB,aAAa,OAAO,GAAG,UAAU;AACnE;AAEA,eAAe,QACb,KACA,OACA,SAC0C;CAC1C,MAAM,OAAO,KAAK,UAAU;EAAE;EAAO;EAAS,8BAAa,IAAI,KAAK,GAAE,YAAY;CAAE,CAAC;CACrF,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB;CACnB;CACA,IAAI,IAAI,QAAQ,QAAQ,uBAAuB,UAAU,YAAY,IAAI,QAAQ,IAAI;CACrF,IAAI;EACF,MAAM,MAAO,MAAM,MAAM,IAAI,KAAK;GAAE,QAAQ;GAAQ;GAAS;EAAK,CAAC;EAInE,IAAI,CAAC,IAAI,IAAI,OAAO;GAAE,IAAI;GAAO,OAAO,QAAQ,IAAI;EAAS;EAC7D,OAAO,EAAE,IAAI,KAAK;CACpB,SAAS,KAAK;EACZ,OAAO;GAAE,IAAI;GAAO,OAAQ,IAAc;EAAQ;CACpD;AACF;;AAGA,eAAsB,UAAU,SAAiB,OAAe,SAAiC;CAC/F,MAAM,UAAU,mBAAmB,aAAa,OAAO,GAAG,KAAK;CAC/D,IAAI,QAAQ,WAAW,GAAG;CAC1B,MAAM,WAAW,aAAa,OAAO;CACrC,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,IAAI,MAAM,QAAQ,KAAK,OAAO,OAAO;EAC3C,IAAI,CAAC,EAAE,IACL,SAAS,KAAK;GACZ,IAAI,QAAA,GAAA,OAAA,YAAkB,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;GACpG,gBAAgB,IAAI;GACpB,KAAK,IAAI;GACT,GAAI,IAAI,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;GAC3C;GACA;GACA,UAAU;GACV,WAAW,EAAE,SAAS;GACtB,2BAAU,IAAI,KAAK,GAAE,YAAY;EACnC,CAAC;CAEL;CACA,IAAI,SAAS,SAAS,GAAG,sBAAA,eAAU,aAAa,OAAO,GAAG,YAAY,QAAQ;AAChF"}
@@ -1,5 +1,5 @@
1
+ import { r as writeJsonArray, t as readJsonArray } from "./json-store-WWsFzXub.js";
1
2
  import path from "path";
2
- import fs from "fs";
3
3
  import { createHash, createHmac, randomBytes } from "crypto";
4
4
  //#region src/core/webhooks.ts
5
5
  function subsPath(dataDir) {
@@ -8,21 +8,8 @@ function subsPath(dataDir) {
8
8
  function failuresPath(dataDir) {
9
9
  return path.join(dataDir, ".agentic", "webhook-failures.json");
10
10
  }
11
- function readJson(p, key) {
12
- if (!fs.existsSync(p)) return [];
13
- try {
14
- const data = JSON.parse(fs.readFileSync(p, "utf-8"));
15
- return Array.isArray(data[key]) ? data[key] : [];
16
- } catch {
17
- return [];
18
- }
19
- }
20
- function writeJson(p, key, items) {
21
- fs.mkdirSync(path.dirname(p), { recursive: true });
22
- fs.writeFileSync(p, JSON.stringify({ [key]: items }, null, 2), "utf-8");
23
- }
24
11
  function loadWebhooks(dataDir) {
25
- return readJson(subsPath(dataDir), "subscriptions");
12
+ return readJsonArray(subsPath(dataDir), "subscriptions");
26
13
  }
27
14
  function addWebhook(dataDir, url, events, secret) {
28
15
  const sub = {
@@ -32,14 +19,14 @@ function addWebhook(dataDir, url, events, secret) {
32
19
  ...secret ? { secret } : {},
33
20
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
34
21
  };
35
- writeJson(subsPath(dataDir), "subscriptions", [...loadWebhooks(dataDir), sub]);
22
+ writeJsonArray(subsPath(dataDir), "subscriptions", [...loadWebhooks(dataDir), sub]);
36
23
  return sub;
37
24
  }
38
25
  function removeWebhook(dataDir, id) {
39
26
  const subs = loadWebhooks(dataDir);
40
27
  const next = subs.filter((s) => s.id !== id);
41
28
  if (next.length === subs.length) return false;
42
- writeJson(subsPath(dataDir), "subscriptions", next);
29
+ writeJsonArray(subsPath(dataDir), "subscriptions", next);
43
30
  return true;
44
31
  }
45
32
  /** A subscription matches an event by exact name, "*", or a "prefix.*" pattern. */
@@ -54,7 +41,7 @@ function signPayload(secret, body) {
54
41
  return createHmac("sha256", secret).update(body).digest("hex");
55
42
  }
56
43
  function loadFailures(dataDir) {
57
- return readJson(failuresPath(dataDir), "failures");
44
+ return readJsonArray(failuresPath(dataDir), "failures");
58
45
  }
59
46
  async function deliver(sub, event, payload) {
60
47
  const body = JSON.stringify({
@@ -104,7 +91,7 @@ async function emitEvent(dataDir, event, payload) {
104
91
  queuedAt: (/* @__PURE__ */ new Date()).toISOString()
105
92
  });
106
93
  }
107
- if (failures.length > 0) writeJson(failuresPath(dataDir), "failures", failures);
94
+ if (failures.length > 0) writeJsonArray(failuresPath(dataDir), "failures", failures);
108
95
  }
109
96
  /** Re-attempt queued failures; remove on success, increment attempts on failure. */
110
97
  async function retryFailures(dataDir) {
@@ -126,7 +113,7 @@ async function retryFailures(dataDir) {
126
113
  lastError: r.error ?? "unknown"
127
114
  });
128
115
  }
129
- writeJson(failuresPath(dataDir), "failures", remaining);
116
+ writeJsonArray(failuresPath(dataDir), "failures", remaining);
130
117
  return {
131
118
  retried,
132
119
  stillFailing: remaining.length
@@ -135,4 +122,4 @@ async function retryFailures(dataDir) {
135
122
  //#endregion
136
123
  export { addWebhook, emitEvent, loadWebhooks, removeWebhook, retryFailures };
137
124
 
138
- //# sourceMappingURL=webhooks-7EpA05Qr.js.map
125
+ //# sourceMappingURL=webhooks-DXr1IoKn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks-DXr1IoKn.js","names":["readJson"],"sources":["../src/core/webhooks.ts"],"sourcesContent":["import { createHash, createHmac, randomBytes } from \"crypto\";\nimport path from \"path\";\nimport { readJsonArray as readJson, writeJsonArray as writeJson } from \"../fs/json-store.js\";\n\n/**\n * Outbound webhooks (event-driven architecture, N5-2). Subscriptions live in\n * .agentic/webhooks.json; failed deliveries are queued in\n * .agentic/webhook-failures.json (replay store) and re-attempted by\n * retryFailures (e.g. from the daemon) — backoff via periodic replay.\n */\nexport interface WebhookSubscription {\n id: string;\n url: string;\n events: string[];\n secret?: string;\n createdAt: string;\n}\n\nexport interface WebhookFailure {\n id: string;\n subscriptionId: string;\n url: string;\n secret?: string;\n event: string;\n payload: unknown;\n attempts: number;\n lastError: string;\n queuedAt: string;\n}\n\nfunction subsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhooks.json\");\n}\nfunction failuresPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhook-failures.json\");\n}\n\nexport function loadWebhooks(dataDir: string): WebhookSubscription[] {\n return readJson<WebhookSubscription>(subsPath(dataDir), \"subscriptions\");\n}\n\nexport function addWebhook(\n dataDir: string,\n url: string,\n events: string[],\n secret?: string\n): WebhookSubscription {\n const sub: WebhookSubscription = {\n id: `wh_${randomBytes(5).toString(\"hex\")}`,\n url,\n events,\n ...(secret ? { secret } : {}),\n createdAt: new Date().toISOString(),\n };\n writeJson(subsPath(dataDir), \"subscriptions\", [...loadWebhooks(dataDir), sub]);\n return sub;\n}\n\nexport function removeWebhook(dataDir: string, id: string): boolean {\n const subs = loadWebhooks(dataDir);\n const next = subs.filter((s) => s.id !== id);\n if (next.length === subs.length) return false;\n writeJson(subsPath(dataDir), \"subscriptions\", next);\n return true;\n}\n\n/** A subscription matches an event by exact name, \"*\", or a \"prefix.*\" pattern. */\nexport function matchSubscriptions(\n subs: WebhookSubscription[],\n event: string\n): WebhookSubscription[] {\n return subs.filter((s) =>\n s.events.some((pat) => {\n if (pat === \"*\" || pat === event) return true;\n if (pat.endsWith(\".*\")) return event.startsWith(pat.slice(0, -1));\n return false;\n })\n );\n}\n\nexport function signPayload(secret: string, body: string): string {\n return createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n}\n\nexport function loadFailures(dataDir: string): WebhookFailure[] {\n return readJson<WebhookFailure>(failuresPath(dataDir), \"failures\");\n}\n\nasync function deliver(\n sub: WebhookSubscription,\n event: string,\n payload: unknown\n): Promise<{ ok: boolean; error?: string }> {\n const body = JSON.stringify({ event, payload, deliveredAt: new Date().toISOString() });\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-DXCRM-Event\": event,\n };\n if (sub.secret) headers[\"X-DXCRM-Signature\"] = `sha256=${signPayload(sub.secret, body)}`;\n try {\n const res = (await fetch(sub.url, { method: \"POST\", headers, body })) as {\n ok: boolean;\n status: number;\n };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };\n return { ok: true };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\n/** Emit an event to all matching subscriptions; queue failures for replay. */\nexport async function emitEvent(dataDir: string, event: string, payload: unknown): Promise<void> {\n const matched = matchSubscriptions(loadWebhooks(dataDir), event);\n if (matched.length === 0) return;\n const failures = loadFailures(dataDir);\n for (const sub of matched) {\n const r = await deliver(sub, event, payload);\n if (!r.ok) {\n failures.push({\n id: `whf_${createHash(\"sha256\").update(`${sub.id}:${event}:${Date.now()}`).digest(\"hex\").slice(0, 10)}`,\n subscriptionId: sub.id,\n url: sub.url,\n ...(sub.secret ? { secret: sub.secret } : {}),\n event,\n payload,\n attempts: 1,\n lastError: r.error ?? \"unknown\",\n queuedAt: new Date().toISOString(),\n });\n }\n }\n if (failures.length > 0) writeJson(failuresPath(dataDir), \"failures\", failures);\n}\n\n/** Re-attempt queued failures; remove on success, increment attempts on failure. */\nexport async function retryFailures(\n dataDir: string\n): Promise<{ retried: number; stillFailing: number }> {\n const failures = loadFailures(dataDir);\n const remaining: WebhookFailure[] = [];\n let retried = 0;\n for (const f of failures) {\n const sub: WebhookSubscription = {\n id: f.subscriptionId,\n url: f.url,\n events: [f.event],\n ...(f.secret ? { secret: f.secret } : {}),\n createdAt: f.queuedAt,\n };\n const r = await deliver(sub, f.event, f.payload);\n if (r.ok) retried++;\n else remaining.push({ ...f, attempts: f.attempts + 1, lastError: r.error ?? \"unknown\" });\n }\n writeJson(failuresPath(dataDir), \"failures\", remaining);\n return { retried, stillFailing: remaining.length };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
+ import { C as writeJsonArray, x as readJsonArray } from "./session-store-DWxJ5Pof.js";
1
2
  import path from "path";
2
- import fs from "fs";
3
3
  import { createHash, createHmac } from "crypto";
4
4
  //#region src/core/webhooks.ts
5
5
  function subsPath(dataDir) {
@@ -8,21 +8,8 @@ function subsPath(dataDir) {
8
8
  function failuresPath(dataDir) {
9
9
  return path.join(dataDir, ".agentic", "webhook-failures.json");
10
10
  }
11
- function readJson(p, key) {
12
- if (!fs.existsSync(p)) return [];
13
- try {
14
- const data = JSON.parse(fs.readFileSync(p, "utf-8"));
15
- return Array.isArray(data[key]) ? data[key] : [];
16
- } catch {
17
- return [];
18
- }
19
- }
20
- function writeJson(p, key, items) {
21
- fs.mkdirSync(path.dirname(p), { recursive: true });
22
- fs.writeFileSync(p, JSON.stringify({ [key]: items }, null, 2), "utf-8");
23
- }
24
11
  function loadWebhooks(dataDir) {
25
- return readJson(subsPath(dataDir), "subscriptions");
12
+ return readJsonArray(subsPath(dataDir), "subscriptions");
26
13
  }
27
14
  /** A subscription matches an event by exact name, "*", or a "prefix.*" pattern. */
28
15
  function matchSubscriptions(subs, event) {
@@ -36,7 +23,7 @@ function signPayload(secret, body) {
36
23
  return createHmac("sha256", secret).update(body).digest("hex");
37
24
  }
38
25
  function loadFailures(dataDir) {
39
- return readJson(failuresPath(dataDir), "failures");
26
+ return readJsonArray(failuresPath(dataDir), "failures");
40
27
  }
41
28
  async function deliver(sub, event, payload) {
42
29
  const body = JSON.stringify({
@@ -86,9 +73,9 @@ async function emitEvent(dataDir, event, payload) {
86
73
  queuedAt: (/* @__PURE__ */ new Date()).toISOString()
87
74
  });
88
75
  }
89
- if (failures.length > 0) writeJson(failuresPath(dataDir), "failures", failures);
76
+ if (failures.length > 0) writeJsonArray(failuresPath(dataDir), "failures", failures);
90
77
  }
91
78
  //#endregion
92
79
  export { emitEvent };
93
80
 
94
- //# sourceMappingURL=webhooks-BO2UAnmn.js.map
81
+ //# sourceMappingURL=webhooks-sWZ8CJtR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks-sWZ8CJtR.js","names":["readJson"],"sources":["../src/core/webhooks.ts"],"sourcesContent":["import { createHash, createHmac, randomBytes } from \"crypto\";\nimport path from \"path\";\nimport { readJsonArray as readJson, writeJsonArray as writeJson } from \"../fs/json-store.js\";\n\n/**\n * Outbound webhooks (event-driven architecture, N5-2). Subscriptions live in\n * .agentic/webhooks.json; failed deliveries are queued in\n * .agentic/webhook-failures.json (replay store) and re-attempted by\n * retryFailures (e.g. from the daemon) — backoff via periodic replay.\n */\nexport interface WebhookSubscription {\n id: string;\n url: string;\n events: string[];\n secret?: string;\n createdAt: string;\n}\n\nexport interface WebhookFailure {\n id: string;\n subscriptionId: string;\n url: string;\n secret?: string;\n event: string;\n payload: unknown;\n attempts: number;\n lastError: string;\n queuedAt: string;\n}\n\nfunction subsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhooks.json\");\n}\nfunction failuresPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhook-failures.json\");\n}\n\nexport function loadWebhooks(dataDir: string): WebhookSubscription[] {\n return readJson<WebhookSubscription>(subsPath(dataDir), \"subscriptions\");\n}\n\nexport function addWebhook(\n dataDir: string,\n url: string,\n events: string[],\n secret?: string\n): WebhookSubscription {\n const sub: WebhookSubscription = {\n id: `wh_${randomBytes(5).toString(\"hex\")}`,\n url,\n events,\n ...(secret ? { secret } : {}),\n createdAt: new Date().toISOString(),\n };\n writeJson(subsPath(dataDir), \"subscriptions\", [...loadWebhooks(dataDir), sub]);\n return sub;\n}\n\nexport function removeWebhook(dataDir: string, id: string): boolean {\n const subs = loadWebhooks(dataDir);\n const next = subs.filter((s) => s.id !== id);\n if (next.length === subs.length) return false;\n writeJson(subsPath(dataDir), \"subscriptions\", next);\n return true;\n}\n\n/** A subscription matches an event by exact name, \"*\", or a \"prefix.*\" pattern. */\nexport function matchSubscriptions(\n subs: WebhookSubscription[],\n event: string\n): WebhookSubscription[] {\n return subs.filter((s) =>\n s.events.some((pat) => {\n if (pat === \"*\" || pat === event) return true;\n if (pat.endsWith(\".*\")) return event.startsWith(pat.slice(0, -1));\n return false;\n })\n );\n}\n\nexport function signPayload(secret: string, body: string): string {\n return createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n}\n\nexport function loadFailures(dataDir: string): WebhookFailure[] {\n return readJson<WebhookFailure>(failuresPath(dataDir), \"failures\");\n}\n\nasync function deliver(\n sub: WebhookSubscription,\n event: string,\n payload: unknown\n): Promise<{ ok: boolean; error?: string }> {\n const body = JSON.stringify({ event, payload, deliveredAt: new Date().toISOString() });\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-DXCRM-Event\": event,\n };\n if (sub.secret) headers[\"X-DXCRM-Signature\"] = `sha256=${signPayload(sub.secret, body)}`;\n try {\n const res = (await fetch(sub.url, { method: \"POST\", headers, body })) as {\n ok: boolean;\n status: number;\n };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };\n return { ok: true };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\n/** Emit an event to all matching subscriptions; queue failures for replay. */\nexport async function emitEvent(dataDir: string, event: string, payload: unknown): Promise<void> {\n const matched = matchSubscriptions(loadWebhooks(dataDir), event);\n if (matched.length === 0) return;\n const failures = loadFailures(dataDir);\n for (const sub of matched) {\n const r = await deliver(sub, event, payload);\n if (!r.ok) {\n failures.push({\n id: `whf_${createHash(\"sha256\").update(`${sub.id}:${event}:${Date.now()}`).digest(\"hex\").slice(0, 10)}`,\n subscriptionId: sub.id,\n url: sub.url,\n ...(sub.secret ? { secret: sub.secret } : {}),\n event,\n payload,\n attempts: 1,\n lastError: r.error ?? \"unknown\",\n queuedAt: new Date().toISOString(),\n });\n }\n }\n if (failures.length > 0) writeJson(failuresPath(dataDir), \"failures\", failures);\n}\n\n/** Re-attempt queued failures; remove on success, increment attempts on failure. */\nexport async function retryFailures(\n dataDir: string\n): Promise<{ retried: number; stillFailing: number }> {\n const failures = loadFailures(dataDir);\n const remaining: WebhookFailure[] = [];\n let retried = 0;\n for (const f of failures) {\n const sub: WebhookSubscription = {\n id: f.subscriptionId,\n url: f.url,\n events: [f.event],\n ...(f.secret ? { secret: f.secret } : {}),\n createdAt: f.queuedAt,\n };\n const r = await deliver(sub, f.event, f.payload);\n if (r.ok) retried++;\n else remaining.push({ ...f, attempts: f.attempts + 1, lastError: r.error ?? \"unknown\" });\n }\n writeJson(failuresPath(dataDir), \"failures\", remaining);\n return { retried, stillFailing: remaining.length };\n}\n"],"mappings":";;;;AA8BA,SAAS,SAAS,SAAyB;CACzC,OAAO,KAAK,KAAK,SAAS,YAAY,eAAe;AACvD;AACA,SAAS,aAAa,SAAyB;CAC7C,OAAO,KAAK,KAAK,SAAS,YAAY,uBAAuB;AAC/D;AAEA,SAAgB,aAAa,SAAwC;CACnE,OAAOA,cAA8B,SAAS,OAAO,GAAG,eAAe;AACzE;;AA4BA,SAAgB,mBACd,MACA,OACuB;CACvB,OAAO,KAAK,QAAQ,MAClB,EAAE,OAAO,MAAM,QAAQ;EACrB,IAAI,QAAQ,OAAO,QAAQ,OAAO,OAAO;EACzC,IAAI,IAAI,SAAS,IAAI,GAAG,OAAO,MAAM,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC;EAChE,OAAO;CACT,CAAC,CACH;AACF;AAEA,SAAgB,YAAY,QAAgB,MAAsB;CAChE,OAAO,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAC/D;AAEA,SAAgB,aAAa,SAAmC;CAC9D,OAAOA,cAAyB,aAAa,OAAO,GAAG,UAAU;AACnE;AAEA,eAAe,QACb,KACA,OACA,SAC0C;CAC1C,MAAM,OAAO,KAAK,UAAU;EAAE;EAAO;EAAS,8BAAa,IAAI,KAAK,GAAE,YAAY;CAAE,CAAC;CACrF,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB;CACnB;CACA,IAAI,IAAI,QAAQ,QAAQ,uBAAuB,UAAU,YAAY,IAAI,QAAQ,IAAI;CACrF,IAAI;EACF,MAAM,MAAO,MAAM,MAAM,IAAI,KAAK;GAAE,QAAQ;GAAQ;GAAS;EAAK,CAAC;EAInE,IAAI,CAAC,IAAI,IAAI,OAAO;GAAE,IAAI;GAAO,OAAO,QAAQ,IAAI;EAAS;EAC7D,OAAO,EAAE,IAAI,KAAK;CACpB,SAAS,KAAK;EACZ,OAAO;GAAE,IAAI;GAAO,OAAQ,IAAc;EAAQ;CACpD;AACF;;AAGA,eAAsB,UAAU,SAAiB,OAAe,SAAiC;CAC/F,MAAM,UAAU,mBAAmB,aAAa,OAAO,GAAG,KAAK;CAC/D,IAAI,QAAQ,WAAW,GAAG;CAC1B,MAAM,WAAW,aAAa,OAAO;CACrC,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,IAAI,MAAM,QAAQ,KAAK,OAAO,OAAO;EAC3C,IAAI,CAAC,EAAE,IACL,SAAS,KAAK;GACZ,IAAI,OAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;GACpG,gBAAgB,IAAI;GACpB,KAAK,IAAI;GACT,GAAI,IAAI,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;GAC3C;GACA;GACA,UAAU;GACV,WAAW,EAAE,SAAS;GACtB,2BAAU,IAAI,KAAK,GAAE,YAAY;EACnC,CAAC;CAEL;CACA,IAAI,SAAS,SAAS,GAAG,eAAU,aAAa,OAAO,GAAG,YAAY,QAAQ;AAChF"}
package/package.json CHANGED
@@ -1,19 +1,27 @@
1
1
  {
2
2
  "name": "@datasynx/agentic-crm",
3
- "version": "0.1.0",
4
- "description": "Local-first, MCP-native CRM. One agent per customer. npm install.",
3
+ "version": "1.1.0",
4
+ "description": "The CRM your AI agents actually run — local-first, MCP-native, one autonomous agent per customer. 57 MCP tools for Claude Code, Codex & Cursor.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Datasynx Engineering <eng@datasynx.io>",
8
8
  "keywords": [
9
9
  "crm",
10
+ "agentic-crm",
11
+ "ai-crm",
10
12
  "mcp",
13
+ "mcp-server",
11
14
  "model-context-protocol",
12
15
  "local-first",
13
16
  "ai-agent",
14
17
  "claude",
18
+ "claude-code",
19
+ "codex",
20
+ "cursor",
21
+ "anthropic",
15
22
  "cli",
16
23
  "sales",
24
+ "self-hosted",
17
25
  "lancedb"
18
26
  ],
19
27
  "repository": {
@@ -64,6 +72,7 @@
64
72
  ],
65
73
  "scripts": {
66
74
  "build": "tsdown && node scripts/postbuild.js",
75
+ "docs:generate": "tsx scripts/generate-docs.ts",
67
76
  "dev": "tsx watch src/cli.ts",
68
77
  "typecheck": "tsc --noEmit && tsc --project tsconfig.types.json",
69
78
  "lint": "eslint src --max-warnings 0",
@@ -82,6 +91,7 @@
82
91
  "@googleapis/calendar": "15.0.0",
83
92
  "@googleapis/gmail": "17.0.0",
84
93
  "@huggingface/transformers": "^3.8.1",
94
+ "@joplin/turndown-plugin-gfm": "1.0.67",
85
95
  "@lancedb/lancedb": "^0.29.0",
86
96
  "@modelcontextprotocol/sdk": "^1.10.0",
87
97
  "adm-zip": "^0.5.17",
@@ -91,17 +101,26 @@
91
101
  "cli-table3": "^0.6.3",
92
102
  "commander": "^14.0.0",
93
103
  "cron": "^4.4.0",
104
+ "exceljs": "4.4.0",
94
105
  "google-auth-library": "10.6.2",
95
106
  "gray-matter": "^4.0.3",
96
107
  "js-yaml": "^4.1.1",
108
+ "mammoth": "1.12.0",
97
109
  "slug": "^9.0.0",
110
+ "tesseract.js": "7.0.0",
111
+ "turndown": "7.2.4",
112
+ "unpdf": "1.6.2",
98
113
  "zod": "^3.25.0",
99
114
  "zod-validation-error": "^3.0.0"
100
115
  },
101
116
  "peerDependencies": {
117
+ "@napi-rs/canvas": "^0.1.69",
102
118
  "express": "^4.0.0 || ^5.0.0"
103
119
  },
104
120
  "peerDependenciesMeta": {
121
+ "@napi-rs/canvas": {
122
+ "optional": true
123
+ },
105
124
  "express": {
106
125
  "optional": true
107
126
  }
@@ -119,6 +138,7 @@
119
138
  "@types/iarna__toml": "^2.0.0",
120
139
  "@types/js-yaml": "^4.0.9",
121
140
  "@types/slug": "^5.0.0",
141
+ "@types/turndown": "5.0.6",
122
142
  "@types/which": "^3.0.0",
123
143
  "@vitest/coverage-v8": "^3.0.0",
124
144
  "expect-type": "^1.3.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"approvals-DpjxGHFp.js","names":[],"sources":["../src/core/approvals.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\n\n/**\n * Human-in-the-loop / approval gate (domino D4 / F3). A per-tool, per-customer\n * autonomy policy (auto | approve | block) decides whether an agent action runs\n * immediately, is queued for human approval, or is blocked. The generic gate\n * wraps any write/automation action so later features get HITL for free.\n */\nexport type Policy = \"auto\" | \"approve\" | \"block\";\n\ninterface PolicyConfig {\n default?: Policy;\n tools?: Record<string, Policy>;\n customers?: Record<string, Record<string, Policy>>;\n}\n\nexport interface Approval {\n id: string;\n tool: string;\n slug?: string;\n payload: unknown;\n status: \"pending\" | \"approved\" | \"rejected\";\n requestedAt: string;\n decidedAt?: string;\n}\n\nfunction policyPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"policy.json\");\n}\nfunction approvalsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"approvals.json\");\n}\n\nfunction loadPolicyConfig(dataDir: string): PolicyConfig {\n const p = policyPath(dataDir);\n if (!fs.existsSync(p)) return {};\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as PolicyConfig;\n } catch {\n return {};\n }\n}\n\n/** Resolve the effective policy: customer-specific → global tool → default (auto). */\nexport function getPolicy(dataDir: string, tool: string, slug?: string): Policy {\n const cfg = loadPolicyConfig(dataDir);\n if (slug && cfg.customers?.[slug]?.[tool]) return cfg.customers[slug]![tool]!;\n if (cfg.tools?.[tool]) return cfg.tools[tool]!;\n return cfg.default ?? \"auto\";\n}\n\nexport function setPolicy(dataDir: string, tool: string, policy: Policy, slug?: string): void {\n const cfg = loadPolicyConfig(dataDir);\n if (slug) {\n cfg.customers = cfg.customers ?? {};\n cfg.customers[slug] = { ...(cfg.customers[slug] ?? {}), [tool]: policy };\n } else {\n cfg.tools = { ...(cfg.tools ?? {}), [tool]: policy };\n }\n const p = policyPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify(cfg, null, 2), \"utf-8\");\n}\n\nexport function listApprovals(dataDir: string, status?: Approval[\"status\"]): Approval[] {\n const p = approvalsPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n const all = (JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as { approvals?: Approval[] })\n .approvals;\n const list = Array.isArray(all) ? all : [];\n return status ? list.filter((a) => a.status === status) : list;\n } catch {\n return [];\n }\n}\n\nfunction writeApprovals(dataDir: string, approvals: Approval[]): void {\n const p = approvalsPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ approvals }, null, 2), \"utf-8\");\n}\n\nexport function requestApproval(\n dataDir: string,\n req: { tool: string; slug?: string; payload: unknown }\n): Approval {\n const approval: Approval = {\n id: `apr_${randomBytes(5).toString(\"hex\")}`,\n tool: req.tool,\n ...(req.slug ? { slug: req.slug } : {}),\n payload: req.payload,\n status: \"pending\",\n requestedAt: new Date().toISOString(),\n };\n writeApprovals(dataDir, [...listApprovals(dataDir), approval]);\n return approval;\n}\n\nexport function decideApproval(\n dataDir: string,\n id: string,\n decision: \"approved\" | \"rejected\"\n): boolean {\n const all = listApprovals(dataDir);\n const idx = all.findIndex((a) => a.id === id);\n if (idx < 0) return false;\n all[idx] = { ...all[idx]!, status: decision, decidedAt: new Date().toISOString() };\n writeApprovals(dataDir, all);\n return true;\n}\n\nexport interface GateResult<T> {\n status: \"executed\" | \"pending\" | \"blocked\";\n result?: T;\n approvalId?: string;\n}\n\n/** Gate an action by the effective policy: run, queue for approval, or block. */\nexport async function gateAction<T>(\n dataDir: string,\n action: { tool: string; slug?: string; payload: unknown },\n execute: () => T | Promise<T>\n): Promise<GateResult<T>> {\n const policy = getPolicy(dataDir, action.tool, action.slug);\n if (policy === \"block\") return { status: \"blocked\" };\n if (policy === \"approve\") {\n const approval = requestApproval(dataDir, action);\n return { status: \"pending\", approvalId: approval.id };\n }\n return { status: \"executed\", result: await execute() };\n}\n"],"mappings":";;;;AA4BA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,aAAa;AACrD;AACA,SAAS,cAAc,SAAyB;CAC9C,OAAO,KAAK,KAAK,SAAS,YAAY,gBAAgB;AACxD;AAEA,SAAS,iBAAiB,SAA+B;CACvD,MAAM,IAAI,WAAW,OAAO;CAC5B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;CACzD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAUA,SAAgB,UAAU,SAAiB,MAAc,QAAgB,MAAqB;CAC5F,MAAM,MAAM,iBAAiB,OAAO;CACpC,IAAI,MAAM;EACR,IAAI,YAAY,IAAI,aAAa,CAAC;EAClC,IAAI,UAAU,QAAQ;GAAE,GAAI,IAAI,UAAU,SAAS,CAAC;IAAK,OAAO;EAAO;CACzE,OACE,IAAI,QAAQ;EAAE,GAAI,IAAI,SAAS,CAAC;GAAK,OAAO;CAAO;CAErD,MAAM,IAAI,WAAW,OAAO;CAC5B,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,GAAG,OAAO;AAC3D;AAEA,SAAgB,cAAc,SAAiB,QAAyC;CACtF,MAAM,IAAI,cAAc,OAAO;CAC/B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,MAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW,EAC1D;EACH,MAAM,OAAO,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC;EACzC,OAAO,SAAS,KAAK,QAAQ,MAAM,EAAE,WAAW,MAAM,IAAI;CAC5D,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,eAAe,SAAiB,WAA6B;CACpE,MAAM,IAAI,cAAc,OAAO;CAC/B,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,EAAE,UAAU,GAAG,MAAM,CAAC,GAAG,OAAO;AACrE;AAkBA,SAAgB,eACd,SACA,IACA,UACS;CACT,MAAM,MAAM,cAAc,OAAO;CACjC,MAAM,MAAM,IAAI,WAAW,MAAM,EAAE,OAAO,EAAE;CAC5C,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,OAAO;EAAE,GAAG,IAAI;EAAO,QAAQ;EAAU,4BAAW,IAAI,KAAK,GAAE,YAAY;CAAE;CACjF,eAAe,SAAS,GAAG;CAC3B,OAAO;AACT"}
@@ -1,2 +0,0 @@
1
- import { t as createMcpToken } from "./auth-DFWwWcYD.js";
2
- export { createMcpToken };
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-DFWwWcYD.js","names":[],"sources":["../src/mcp/auth.ts"],"sourcesContent":["import { createHash, randomBytes, timingSafeEqual } from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\n\nexport type McpRole = \"admin\" | \"manager\" | \"rep\";\n\nexport interface McpTokenRecord {\n hash: string;\n actor: string;\n role: McpRole;\n label?: string;\n createdAt?: string;\n}\n\nexport interface AuthResult {\n ok: boolean;\n actor?: string;\n role?: McpRole;\n}\n\nfunction tokensPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"mcp-tokens.json\");\n}\n\n/** SHA-256 hex of a token. Only hashes are ever persisted. */\nexport function hashToken(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"hex\");\n}\n\nexport function loadMcpTokens(dataDir: string): McpTokenRecord[] {\n const p = tokensPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as { tokens?: McpTokenRecord[] };\n return Array.isArray(data.tokens) ? data.tokens : [];\n } catch {\n return [];\n }\n}\n\n/**\n * Whether the HTTP MCP endpoint must require a bearer token.\n * - `DXCRM_MCP_AUTH=required` forces auth on (even with no tokens yet).\n * - `DXCRM_MCP_AUTH=off` forces it off.\n * - Otherwise: on as soon as at least one token is configured (opt-in by\n * provisioning a token; stays open for local/firewalled dev by default).\n */\nexport function isAuthRequired(dataDir: string): boolean {\n const mode = process.env[\"DXCRM_MCP_AUTH\"];\n if (mode === \"required\") return true;\n if (mode === \"off\") return false;\n return loadMcpTokens(dataDir).length > 0;\n}\n\n/** Validate an `Authorization: Bearer <token>` header against stored hashes. */\nexport function verifyBearer(authHeader: string | undefined, dataDir: string): AuthResult {\n if (!authHeader || !authHeader.startsWith(\"Bearer \")) return { ok: false };\n const token = authHeader.slice(\"Bearer \".length).trim();\n if (!token) return { ok: false };\n\n const candidate = hashToken(token);\n const candidateBuf = Buffer.from(candidate, \"hex\");\n for (const rec of loadMcpTokens(dataDir)) {\n if (rec.hash.length !== candidate.length) continue;\n let recBuf: Buffer;\n try {\n recBuf = Buffer.from(rec.hash, \"hex\");\n } catch {\n continue;\n }\n if (recBuf.length === candidateBuf.length && timingSafeEqual(recBuf, candidateBuf)) {\n return { ok: true, actor: rec.actor, role: rec.role };\n }\n }\n return { ok: false };\n}\n\n/**\n * Mint a new token: generates a random secret, persists only its hash mapped\n * to an actor/role, and returns the plaintext ONCE (never stored).\n */\nexport function createMcpToken(\n dataDir: string,\n actor: string,\n role: McpRole,\n label?: string\n): string {\n const token = randomBytes(24).toString(\"base64url\");\n const records = loadMcpTokens(dataDir);\n records.push({\n hash: hashToken(token),\n actor,\n role,\n ...(label ? { label } : {}),\n createdAt: new Date().toISOString(),\n });\n const p = tokensPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ tokens: records }, null, 2), \"utf-8\");\n return token;\n}\n\n/** RFC 9728 OAuth 2.0 Protected Resource Metadata document. */\nexport function protectedResourceMetadata(resourceUrl: string): Record<string, unknown> {\n return {\n resource: resourceUrl,\n // Self-hosted default: tokens are provisioned out-of-band (createMcpToken).\n // Populate with an external Authorization Server to enable full OAuth flows.\n authorization_servers: [] as string[],\n bearer_methods_supported: [\"header\"],\n scopes_supported: [\"crm:read\", \"crm:write\"],\n };\n}\n\n/** Value for the `WWW-Authenticate` header on a 401 (RFC 9728 §5.1). */\nexport function wwwAuthenticateHeader(metadataUrl: string): string {\n return `Bearer resource_metadata=\"${metadataUrl}\"`;\n}\n"],"mappings":";;;;AAoBA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,iBAAiB;AACzD;;AAGA,SAAgB,UAAU,OAAuB;CAC/C,OAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AACxD;AAEA,SAAgB,cAAc,SAAmC;CAC/D,MAAM,IAAI,WAAW,OAAO;CAC5B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAC7D,OAAO,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;CACrD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;;;;;;;;AASA,SAAgB,eAAe,SAA0B;CACvD,MAAM,OAAO,QAAQ,IAAI;CACzB,IAAI,SAAS,YAAY,OAAO;CAChC,IAAI,SAAS,OAAO,OAAO;CAC3B,OAAO,cAAc,OAAO,EAAE,SAAS;AACzC;;AAGA,SAAgB,aAAa,YAAgC,SAA6B;CACxF,IAAI,CAAC,cAAc,CAAC,WAAW,WAAW,SAAS,GAAG,OAAO,EAAE,IAAI,MAAM;CACzE,MAAM,QAAQ,WAAW,MAAM,CAAgB,EAAE,KAAK;CACtD,IAAI,CAAC,OAAO,OAAO,EAAE,IAAI,MAAM;CAE/B,MAAM,YAAY,UAAU,KAAK;CACjC,MAAM,eAAe,OAAO,KAAK,WAAW,KAAK;CACjD,KAAK,MAAM,OAAO,cAAc,OAAO,GAAG;EACxC,IAAI,IAAI,KAAK,WAAW,UAAU,QAAQ;EAC1C,IAAI;EACJ,IAAI;GACF,SAAS,OAAO,KAAK,IAAI,MAAM,KAAK;EACtC,QAAQ;GACN;EACF;EACA,IAAI,OAAO,WAAW,aAAa,UAAU,gBAAgB,QAAQ,YAAY,GAC/E,OAAO;GAAE,IAAI;GAAM,OAAO,IAAI;GAAO,MAAM,IAAI;EAAK;CAExD;CACA,OAAO,EAAE,IAAI,MAAM;AACrB;;;;;AAMA,SAAgB,eACd,SACA,OACA,MACA,OACQ;CACR,MAAM,QAAQ,YAAY,EAAE,EAAE,SAAS,WAAW;CAClD,MAAM,UAAU,cAAc,OAAO;CACrC,QAAQ,KAAK;EACX,MAAM,UAAU,KAAK;EACrB;EACA;EACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,4BAAW,IAAI,KAAK,GAAE,YAAY;CACpC,CAAC;CACD,MAAM,IAAI,WAAW,OAAO;CAC5B,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,EAAE,QAAQ,QAAQ,GAAG,MAAM,CAAC,GAAG,OAAO;CACzE,OAAO;AACT;;AAGA,SAAgB,0BAA0B,aAA8C;CACtF,OAAO;EACL,UAAU;EAGV,uBAAuB,CAAC;EACxB,0BAA0B,CAAC,QAAQ;EACnC,kBAAkB,CAAC,YAAY,WAAW;CAC5C;AACF;;AAGA,SAAgB,sBAAsB,aAA6B;CACjE,OAAO,6BAA6B,YAAY;AAClD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"backup-CeMk9z86.js","names":[],"sources":["../src/commands/backup.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport { success, error, info, bold } from \"../ui/colors.js\";\n\nexport interface BackupManifest {\n version: \"1\";\n createdAt: string;\n dxcrmVersion: string;\n directories: string[];\n customerCount: number;\n fileCount: number;\n totalBytes: number;\n sha256: string;\n encrypted: boolean;\n retentionTier?: \"daily\" | \"weekly\" | \"monthly\";\n}\n\nexport interface BackupScheduleConfig {\n every: string;\n keep: number;\n weekly?: number;\n monthly?: number;\n lastBackup: string | null;\n remote?: string;\n}\n\nexport interface AgenticConfig {\n backupSchedule?: BackupScheduleConfig;\n}\n\nexport interface BackupEntry {\n filename: string;\n path: string;\n createdAt: string;\n sizeBytes: number;\n verified: boolean;\n encrypted: boolean;\n customerCount: number;\n fileCount: number;\n}\n\n// ─── Config helpers ────────────────────────────────────────────────────────────\n\nfunction getConfigPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"config.json\");\n}\n\nfunction readAgenticConfig(dataDir: string): AgenticConfig {\n const filePath = getConfigPath(dataDir);\n if (!fs.existsSync(filePath)) return {};\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\") as string) as AgenticConfig;\n } catch {\n return {};\n }\n}\n\nfunction writeAgenticConfig(dataDir: string, config: AgenticConfig): void {\n const filePath = getConfigPath(dataDir);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2), \"utf-8\");\n}\n\n// ─── Manifest ─────────────────────────────────────────────────────────────────\n\nfunction countDir(dir: string): { files: number; bytes: number } {\n let files = 0;\n let bytes = 0;\n if (!fs.existsSync(dir)) return { files, bytes };\n const walk = (d: string) => {\n try {\n for (const entry of fs.readdirSync(d)) {\n const full = path.join(d, entry);\n try {\n const stat = fs.statSync(full);\n if (stat.isDirectory()) walk(full);\n else {\n files++;\n bytes += stat.size;\n }\n } catch {\n /* skip */\n }\n }\n } catch {\n /* skip */\n }\n };\n walk(dir);\n return { files, bytes };\n}\n\nfunction countCustomers(dataDir: string): number {\n const dir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(dir)) return 0;\n try {\n return fs.readdirSync(dir).filter((f) => {\n try {\n return fs.statSync(path.join(dir, f)).isDirectory();\n } catch {\n return false;\n }\n }).length;\n } catch {\n return 0;\n }\n}\n\nfunction sha256File(filePath: string): string {\n if (!fs.existsSync(filePath)) return \"\";\n const hash = createHash(\"sha256\");\n hash.update(fs.readFileSync(filePath));\n return hash.digest(\"hex\");\n}\n\nfunction buildManifest(\n dataDir: string,\n dirs: string[],\n zipPath: string,\n encrypted: boolean\n): BackupManifest {\n let totalFiles = 0;\n let totalBytes = 0;\n for (const d of dirs) {\n const full = path.join(dataDir, d);\n const { files, bytes } = countDir(full);\n totalFiles += files;\n totalBytes += bytes;\n }\n return {\n version: \"1\",\n createdAt: new Date().toISOString(),\n dxcrmVersion: \"0.1.0\",\n directories: dirs,\n customerCount: countCustomers(dataDir),\n fileCount: totalFiles,\n totalBytes,\n sha256: sha256File(zipPath),\n encrypted,\n };\n}\n\n// ─── Manifest log ──────────────────────────────────────────────────────────────\n\nfunction appendBackupLog(dataDir: string, entry: BackupEntry): void {\n const logPath = path.join(dataDir, \".agentic\", \"backup-log.json\");\n let entries: BackupEntry[] = [];\n if (fs.existsSync(logPath)) {\n try {\n entries = JSON.parse(fs.readFileSync(logPath, \"utf-8\") as string) as BackupEntry[];\n } catch {\n entries = [];\n }\n }\n // Deduplicate by filename — update existing entry if same file backed up again\n entries = entries.filter((e) => e.filename !== entry.filename);\n entries.unshift(entry);\n // Keep last 100 entries\n if (entries.length > 100) entries = entries.slice(0, 100);\n fs.mkdirSync(path.dirname(logPath), { recursive: true });\n fs.writeFileSync(logPath, JSON.stringify(entries, null, 2), \"utf-8\");\n}\n\nexport function readBackupLog(dataDir: string): BackupEntry[] {\n const logPath = path.join(dataDir, \".agentic\", \"backup-log.json\");\n if (!fs.existsSync(logPath)) return [];\n try {\n return JSON.parse(fs.readFileSync(logPath, \"utf-8\") as string) as BackupEntry[];\n } catch {\n return [];\n }\n}\n\n// ─── runBackup ────────────────────────────────────────────────────────────────\n\nexport async function runBackup(\n output?: string,\n dataDir?: string,\n opts: { encrypt?: boolean; remote?: string } = {}\n): Promise<BackupManifest | null> {\n const dir = dataDir ?? process.cwd();\n const customersDir = path.join(dir, \"customers\");\n\n if (!fs.existsSync(customersDir)) {\n console.error(error(\"✗ No customers directory found.\"));\n process.exit(1);\n }\n\n const zipPath =\n output ?? path.join(dir, `dxcrm-backup-${new Date().toISOString().slice(0, 10)}.zip`);\n\n // Determine which directories to include\n const includeDirs = [\"customers/\"];\n if (fs.existsSync(path.join(dir, \".agentic\"))) {\n includeDirs.push(\".agentic/\");\n }\n\n try {\n execSync(`zip -r \"${zipPath}\" ${includeDirs.join(\" \")}`, { cwd: dir });\n\n // Build manifest and append to zip\n const manifest = buildManifest(dir, includeDirs, zipPath, opts.encrypt ?? false);\n const manifestPath = path.join(dir, \".dxcrm-manifest-tmp.json\");\n fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), \"utf-8\");\n try {\n execSync(`zip -j \"${zipPath}\" \"${manifestPath}\"`, { cwd: dir });\n } catch {\n /* non-fatal */\n }\n fs.unlinkSync(manifestPath);\n\n // Verify integrity\n const verified = verifyBackupFile(zipPath);\n\n const entry: BackupEntry = {\n filename: path.basename(zipPath),\n path: zipPath,\n createdAt: manifest.createdAt,\n sizeBytes: fs.existsSync(zipPath) ? fs.statSync(zipPath).size : 0,\n verified,\n encrypted: opts.encrypt ?? false,\n customerCount: manifest.customerCount,\n fileCount: manifest.fileCount,\n };\n appendBackupLog(dir, entry);\n\n // Remote upload\n if (opts.remote) {\n await uploadBackup(zipPath, opts.remote);\n }\n\n console.log(success(`✓ Backup saved: ${zipPath}`));\n console.log(\n info(\n ` Customers: ${manifest.customerCount} Files: ${manifest.fileCount} Size: ${(manifest.totalBytes / 1024 / 1024).toFixed(1)} MB`\n )\n );\n if (!verified) console.log(info(\" ⚠ Integrity check failed — backup may be incomplete\"));\n\n return manifest;\n } catch (err) {\n console.error(error(`✗ Backup failed: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\n// ─── Verify ───────────────────────────────────────────────────────────────────\n\nexport function verifyBackupFile(zipPath: string): boolean {\n if (!fs.existsSync(zipPath)) return false;\n try {\n execSync(`unzip -t \"${zipPath}\"`, { stdio: \"pipe\" });\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function runVerify(zipPath: string): Promise<void> {\n if (!fs.existsSync(zipPath)) {\n console.error(error(`✗ File not found: ${zipPath}`));\n process.exit(1);\n }\n\n console.log(info(`Verifying ${path.basename(zipPath)}...`));\n const ok = verifyBackupFile(zipPath);\n\n if (ok) {\n const size = fs.statSync(zipPath).size;\n const sha = sha256File(zipPath);\n console.log(success(\"✓ ZIP integrity OK\"));\n console.log(info(` Size: ${(size / 1024 / 1024).toFixed(1)} MB`));\n console.log(info(` SHA-256: ${sha}`));\n } else {\n console.error(error(\"✗ Integrity check failed\"));\n process.exit(1);\n }\n}\n\n// ─── Remote Upload ────────────────────────────────────────────────────────────\n\nexport async function uploadBackup(localPath: string, remote: string): Promise<void> {\n if (remote.startsWith(\"s3://\")) {\n // Requires AWS CLI or @aws-sdk/client-s3 to be installed\n try {\n execSync(`aws s3 cp \"${localPath}\" \"${remote}${path.basename(localPath)}\"`, {\n stdio: \"pipe\",\n });\n console.log(info(` ✓ Uploaded to ${remote}${path.basename(localPath)}`));\n } catch (err) {\n console.error(\n error(\n ` ✗ S3 upload failed (install aws-cli or @aws-sdk/client-s3): ${(err as Error).message}`\n )\n );\n }\n } else if (remote.startsWith(\"rsync://\")) {\n const dest = remote.replace(\"rsync://\", \"\");\n try {\n execSync(`rsync -az \"${localPath}\" \"${dest}\"`, { stdio: \"pipe\" });\n console.log(info(` ✓ Synced to ${dest}`));\n } catch (err) {\n console.error(error(` ✗ rsync failed: ${(err as Error).message}`));\n }\n } else {\n // Local directory copy\n try {\n const destPath = path.join(remote, path.basename(localPath));\n fs.mkdirSync(remote, { recursive: true });\n fs.copyFileSync(localPath, destPath);\n console.log(info(` ✓ Copied to ${destPath}`));\n } catch (err) {\n console.error(error(` ✗ Copy failed: ${(err as Error).message}`));\n }\n }\n}\n\n// ─── List Backups ─────────────────────────────────────────────────────────────\n\nexport function listBackupsInDir(dir: string): BackupEntry[] {\n if (!fs.existsSync(dir)) return [];\n try {\n return fs\n .readdirSync(dir)\n .filter((f) => f.match(/^dxcrm-backup-.*\\.(zip|dxbak)$/))\n .map((f) => {\n const fullPath = path.join(dir, f);\n const stat = fs.statSync(fullPath);\n return {\n filename: f,\n path: fullPath,\n createdAt: stat.mtime.toISOString(),\n sizeBytes: stat.size,\n verified: false,\n encrypted: f.endsWith(\".dxbak\"),\n customerCount: 0,\n fileCount: 0,\n } satisfies BackupEntry;\n })\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n } catch {\n return [];\n }\n}\n\n// ─── Retention Policy ─────────────────────────────────────────────────────────\n\nexport interface RetentionConfig {\n daily?: number;\n weekly?: number;\n monthly?: number;\n}\n\nexport function pruneOldBackups(dir: string, keep: number, retention?: RetentionConfig): void {\n const files = fs\n .readdirSync(dir)\n .filter((f) => f.match(/^dxcrm-backup-\\d{4}-\\d{2}-\\d{2}.*\\.(zip|dxbak)$/))\n .sort();\n\n if (!retention) {\n // Legacy: keep last N\n const toDelete = files.slice(0, Math.max(0, files.length - keep));\n for (const f of toDelete) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n return;\n }\n\n // Grandfathering: daily → weekly → monthly\n const daily = retention.daily ?? keep;\n const weekly = retention.weekly ?? 0;\n const monthly = retention.monthly ?? 0;\n\n const kept = new Set<string>();\n\n // Keep last N daily\n for (const f of files.slice(-daily)) kept.add(f);\n\n // Keep last backup of each week (up to 'weekly' weeks)\n if (weekly > 0) {\n const byWeek = new Map<string, string>();\n for (const f of files) {\n const dateMatch = f.match(/dxcrm-backup-(\\d{4}-\\d{2}-\\d{2})/);\n if (!dateMatch?.[1]) continue;\n const d = new Date(dateMatch[1]);\n // ISO week: year + week number\n const week = `${d.getFullYear()}-W${String(Math.ceil((d.getDate() + new Date(d.getFullYear(), 0, 1).getDay()) / 7)).padStart(2, \"0\")}`;\n byWeek.set(week, f); // last backup of the week wins\n }\n Array.from(byWeek.values())\n .slice(-weekly)\n .forEach((f) => kept.add(f));\n }\n\n // Keep last backup of each month (up to 'monthly' months)\n if (monthly > 0) {\n const byMonth = new Map<string, string>();\n for (const f of files) {\n const dateMatch = f.match(/dxcrm-backup-(\\d{4}-\\d{2})/);\n if (!dateMatch?.[1]) continue;\n byMonth.set(dateMatch[1], f); // last backup of the month wins\n }\n Array.from(byMonth.values())\n .slice(-monthly)\n .forEach((f) => kept.add(f));\n }\n\n for (const f of files) {\n if (!kept.has(f)) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n }\n}\n\n// ─── Schedule ─────────────────────────────────────────────────────────────────\n\nexport async function runBackupSchedule(\n opts: {\n every?: string;\n keep?: string;\n weekly?: string;\n monthly?: string;\n remote?: string;\n status?: boolean;\n clear?: boolean;\n },\n dataDir?: string\n): Promise<void> {\n const dir = dataDir ?? process.cwd();\n\n if (opts.clear) {\n const config = readAgenticConfig(dir);\n delete config.backupSchedule;\n writeAgenticConfig(dir, config);\n console.log(success(\"✓ Backup schedule cleared.\"));\n return;\n }\n\n if (!opts.every && !opts.status) {\n console.error(error(\"✗ --every is required (e.g. --every day)\"));\n process.exit(1);\n return;\n }\n\n if (opts.every) {\n const keep = opts.keep ? parseInt(opts.keep, 10) : 7;\n const config = readAgenticConfig(dir);\n config.backupSchedule = {\n every: opts.every,\n keep,\n ...(opts.weekly ? { weekly: parseInt(opts.weekly, 10) } : {}),\n ...(opts.monthly ? { monthly: parseInt(opts.monthly, 10) } : {}),\n ...(opts.remote ? { remote: opts.remote } : {}),\n lastBackup: null,\n };\n writeAgenticConfig(dir, config);\n if (!opts.status) {\n console.log(\n success(\n `✓ Backup schedule set: every ${opts.every}, keep ${keep} daily${opts.weekly ? ` / ${opts.weekly} weekly` : \"\"}${opts.monthly ? ` / ${opts.monthly} monthly` : \"\"}.`\n )\n );\n }\n }\n\n if (opts.status) {\n const config = readAgenticConfig(dir);\n const sched = config.backupSchedule;\n if (!sched) {\n console.log(info(\"No backup schedule configured.\"));\n } else {\n console.log(bold(\"Backup Schedule:\"));\n console.log(` every: ${sched.every}`);\n console.log(` keep: ${sched.keep} daily backups`);\n if (sched.weekly) console.log(` weekly: ${sched.weekly} weekly backups`);\n if (sched.monthly) console.log(` monthly: ${sched.monthly} monthly backups`);\n if (sched.remote) console.log(` remote: ${sched.remote}`);\n console.log(` lastBackup: ${sched.lastBackup ?? \"never\"}`);\n }\n }\n}\n\nexport function shouldRunScheduledBackup(dataDir: string): boolean {\n const config = readAgenticConfig(dataDir);\n const sched = config.backupSchedule;\n if (!sched) return false;\n if (!sched.lastBackup) return true;\n const last = new Date(sched.lastBackup).getTime();\n const oneDayMs = 24 * 60 * 60 * 1000;\n return Date.now() - last >= oneDayMs;\n}\n\nexport async function runScheduledBackupIfDue(dataDir: string): Promise<void> {\n if (!shouldRunScheduledBackup(dataDir)) return;\n const config = readAgenticConfig(dataDir);\n const sched = config.backupSchedule!;\n const customersDir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(customersDir)) return;\n\n const zipPath = path.join(dataDir, `dxcrm-backup-${new Date().toISOString().slice(0, 10)}.zip`);\n const includeDirs = [\"customers/\"];\n if (fs.existsSync(path.join(dataDir, \".agentic\"))) includeDirs.push(\".agentic/\");\n\n try {\n execSync(`zip -r \"${zipPath}\" ${includeDirs.join(\" \")}`, { cwd: dataDir });\n\n const retention: RetentionConfig | undefined =\n (sched.weekly ?? sched.monthly)\n ? {\n daily: sched.keep,\n ...(sched.weekly ? { weekly: sched.weekly } : {}),\n ...(sched.monthly ? { monthly: sched.monthly } : {}),\n }\n : undefined;\n pruneOldBackups(dataDir, sched.keep, retention);\n\n if (sched.remote) {\n await uploadBackup(zipPath, sched.remote).catch(() => {\n /* non-fatal */\n });\n }\n\n config.backupSchedule!.lastBackup = new Date().toISOString();\n writeAgenticConfig(dataDir, config);\n process.stderr.write(`[daemon] Scheduled backup saved: ${zipPath}\\n`);\n } catch (err) {\n process.stderr.write(`[daemon] Scheduled backup failed: ${(err as Error).message}\\n`);\n }\n}\n\n// ─── Restore ──────────────────────────────────────────────────────────────────\n\nexport async function runRestore(zipPath: string, dataDir?: string): Promise<void> {\n const dir = dataDir ?? process.cwd();\n try {\n execSync(`unzip -o \"${path.resolve(zipPath)}\" -d \"${dir}\"`, { cwd: dir });\n console.log(success(\"✓ Restore complete.\"));\n } catch (err) {\n console.error(error(`✗ Restore failed: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\nexport interface RestoreDrillReport {\n ok: boolean;\n verified: boolean;\n hasCustomers: boolean;\n hasAgentic: boolean;\n reason?: string;\n}\n\n/**\n * Restore-drill: verify a backup is actually restorable WITHOUT touching live\n * data — checks integrity (unzip -t) and that the archive contains the expected\n * top-level state (customers/, .agentic/). Returns a report for monitoring.\n */\nexport async function runRestoreDrill(\n zipPath: string,\n opts: { silent?: boolean } = {}\n): Promise<RestoreDrillReport> {\n const resolved = path.resolve(zipPath);\n if (!fs.existsSync(resolved)) {\n if (!opts.silent) console.error(error(`✗ File not found: ${zipPath}`));\n return {\n ok: false,\n verified: false,\n hasCustomers: false,\n hasAgentic: false,\n reason: \"not_found\",\n };\n }\n\n const verified = verifyBackupFile(resolved);\n let hasCustomers = false;\n let hasAgentic = false;\n if (verified) {\n try {\n const listing = execSync(`unzip -l \"${resolved}\"`, { stdio: \"pipe\" }).toString();\n hasCustomers = listing.includes(\"customers/\");\n hasAgentic = listing.includes(\".agentic/\");\n } catch {\n /* listing failed — treated as incomplete */\n }\n }\n\n const ok = verified && hasCustomers;\n if (!opts.silent) {\n if (ok) {\n console.log(\n success(\n `✓ Restore drill OK — integrity verified; customers/${hasAgentic ? \" + .agentic/\" : \"\"} present`\n )\n );\n } else {\n console.error(\n error(`✗ Restore drill failed (verified=${verified}, customers=${hasCustomers})`)\n );\n }\n }\n return { ok, verified, hasCustomers, hasAgentic };\n}\n\n// ─── Commands ─────────────────────────────────────────────────────────────────\n\nconst scheduleSubCommand = new Command(\"schedule\")\n .description(\"Configure automatic backup schedule\")\n .option(\"--every <interval>\", \"Backup interval (e.g. day)\")\n .option(\"--keep <n>\", \"Daily backups to keep (default: 7)\")\n .option(\"--weekly <n>\", \"Weekly backups to keep (e.g. 4)\")\n .option(\"--monthly <n>\", \"Monthly backups to keep (e.g. 12)\")\n .option(\"--remote <url>\", \"Remote destination (s3://, rsync://, or local path)\")\n .option(\"--status\", \"Show current schedule\")\n .option(\"--clear\", \"Remove backup schedule\")\n .action((opts) => runBackupSchedule(opts, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd()));\n\nconst verifySubCommand = new Command(\"verify\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Verify backup integrity (SHA-256 + zip test)\")\n .action((zipPath: string) => runVerify(zipPath));\n\nconst drillSubCommand = new Command(\"drill\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Restore-drill: verify a backup is restorable without touching live data\")\n .action(async (zipPath: string) => {\n const report = await runRestoreDrill(zipPath);\n if (!report.ok) process.exitCode = 1;\n });\n\nconst listSubCommand = new Command(\"list\").description(\"List available backups\").action(() => {\n const dir = process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd();\n const entries = readBackupLog(dir);\n const fileEntries = listBackupsInDir(dir);\n const combined = entries.length > 0 ? entries : fileEntries;\n if (combined.length === 0) {\n console.log(info(\"No backups found.\"));\n return;\n }\n for (const e of combined) {\n const enc = e.encrypted ? \" [encrypted]\" : \"\";\n const ver = e.verified ? \" ✓\" : \"\";\n const mb = e.sizeBytes > 0 ? ` ${(e.sizeBytes / 1024 / 1024).toFixed(1)} MB` : \"\";\n console.log(` ${bold(e.filename)}${enc}${ver}${mb} ${e.createdAt.slice(0, 10)}`);\n }\n});\n\nexport const backupCommand = new Command(\"backup\")\n .argument(\"[output]\", \"Output path for backup zip\")\n .description(\"Backup customers/ + .agentic/ directories\")\n .option(\"--encrypt\", \"Encrypt the backup (AES-256-GCM)\")\n .option(\"--remote <url>\", \"Also upload to remote (s3://, rsync://, or path)\")\n .action((output?: string, opts?: { encrypt?: boolean; remote?: string }) => {\n void runBackup(output, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd(), opts ?? {});\n });\n\nbackupCommand.addCommand(scheduleSubCommand);\nbackupCommand.addCommand(verifySubCommand);\nbackupCommand.addCommand(drillSubCommand);\nbackupCommand.addCommand(listSubCommand);\n\nexport const restoreCommand = new Command(\"restore\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Restore from backup zip\")\n .action((zipPath: string) => runRestore(zipPath, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd()));\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import { u as runScheduledBackupIfDue } from "./backup-CeMk9z86.js";
2
- export { runScheduledBackupIfDue };
@@ -1 +0,0 @@
1
- {"version":3,"file":"context-builder-BzWAp3Zs.js","names":[],"sources":["../src/core/context-builder.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\n\nconst MAX_INTERACTIONS = 10;\n\nfunction estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nfunction parseRecentInteractions(filePath: string, limit: number): string {\n if (!fs.existsSync(filePath)) return \"\";\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n\n // Split on ## date headings\n const entries = content.split(/(?=^## \\d{4}-\\d{2}-\\d{2})/m).filter((e) => e.trim());\n const recent = entries.slice(0, limit);\n return recent.join(\"\\n\").trim();\n}\n\nfunction parsePipelineContent(filePath: string): string {\n if (!fs.existsSync(filePath)) return \"\";\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n return content.trim();\n}\n\nfunction extractSection(content: string, sectionName: string): string {\n const regex = new RegExp(`## ${sectionName}([\\\\s\\\\S]*?)(?=^## |$)`, \"m\");\n const match = regex.exec(content);\n return match ? (match[1] ?? \"\").trim() : \"\";\n}\n\nexport async function buildContext(dataDir: string, slug: string): Promise<string> {\n const customerDir = path.join(dataDir, \"customers\", slug);\n\n if (!fs.existsSync(customerDir)) {\n throw new Error(`Customer '${slug}' not found`);\n }\n\n const mainFactsPath = path.join(customerDir, \"main_facts.md\");\n const interactionsPath = path.join(customerDir, \"interactions.md\");\n const pipelinePath = path.join(customerDir, \"pipeline.md\");\n\n // Read main_facts.md\n let mainContent = \"\";\n let frontmatterStr = \"\";\n if (fs.existsSync(mainFactsPath)) {\n const fileContent = fs.readFileSync(mainFactsPath, \"utf-8\") as string;\n const raw = matter(fileContent);\n mainContent = raw.content ?? \"\";\n frontmatterStr = Object.entries(raw.data as Record<string, unknown>)\n .map(([k, v]) => `${k}: ${JSON.stringify(v)}`)\n .join(\"\\n\");\n }\n\n const quickRef = extractSection(mainContent, \"Quick Reference\");\n const contacts = extractSection(mainContent, \"Contacts\");\n const criticalContext = extractSection(mainContent, \"Critical Context\");\n const openQuestions = extractSection(mainContent, \"Open Questions\");\n\n const recentActivity = parseRecentInteractions(interactionsPath, MAX_INTERACTIONS);\n const pipelineContent = parsePipelineContent(pipelinePath);\n\n const sections: string[] = [\n `# Customer Context: ${slug}`,\n \"\",\n \"## Metadata\",\n frontmatterStr || \"(no metadata)\",\n \"\",\n \"## Quick Reference\",\n quickRef || \"(not set)\",\n \"\",\n \"## Contacts\",\n contacts || \"(not set)\",\n \"\",\n \"## Critical Context\",\n criticalContext || \"(not set)\",\n \"\",\n \"## Recent Activity (last 10 interactions)\",\n recentActivity || \"(no interactions yet)\",\n \"\",\n \"## Pipeline\",\n pipelineContent || \"(no deals)\",\n \"\",\n \"## Open Questions\",\n openQuestions || \"(none)\",\n ];\n\n const raw = sections.join(\"\\n\");\n const tokenEstimate = estimateTokens(raw);\n\n // If over 3000 tokens, trim interactions\n if (tokenEstimate > 3000) {\n const trimmedActivity = parseRecentInteractions(interactionsPath, 5);\n const trimmedSections: string[] = [\n `# Customer Context: ${slug}`,\n \"\",\n \"## Metadata\",\n frontmatterStr || \"(no metadata)\",\n \"\",\n \"## Quick Reference\",\n quickRef || \"(not set)\",\n \"\",\n \"## Contacts\",\n contacts || \"(not set)\",\n \"\",\n \"## Critical Context\",\n criticalContext || \"(not set)\",\n \"\",\n \"## Recent Activity (last 5 interactions — trimmed for token budget)\",\n trimmedActivity || \"(no interactions yet)\",\n \"\",\n \"## Pipeline\",\n pipelineContent || \"(no deals)\",\n \"\",\n \"## Open Questions\",\n openQuestions || \"(none)\",\n ];\n return trimmedSections.join(\"\\n\");\n }\n\n return raw;\n}\n\n/** Robust section-body extractor: from a `## Name` heading to the next `## ` heading. */\nfunction sectionBody(content: string, name: string): string {\n const lines = content.split(\"\\n\");\n const start = lines.findIndex((l) => l.trim() === `## ${name}`);\n if (start < 0) return \"\";\n const body: string[] = [];\n for (let i = start + 1; i < lines.length; i++) {\n if (lines[i]!.startsWith(\"## \")) break;\n body.push(lines[i]!);\n }\n return body.join(\"\\n\").trim();\n}\n\nexport interface ContextBlock {\n slug: string;\n metadata: Record<string, unknown>;\n quickReference: string;\n contacts: string;\n criticalContext: string;\n openQuestions: string;\n recentActivity: string;\n pipeline: string;\n}\n\n/**\n * Structured variant of buildContext (REF-2): returns a typed object instead of\n * a markdown string, for callers that need fields programmatically (e.g. MCP\n * responses, SDK consumers). buildContext remains the token-budgeted string form.\n */\nexport async function buildContextBlock(\n dataDir: string,\n slug: string,\n role?: \"admin\" | \"manager\" | \"rep\"\n): Promise<ContextBlock> {\n const customerDir = path.join(dataDir, \"customers\", slug);\n if (!fs.existsSync(customerDir)) {\n throw new Error(`Customer '${slug}' not found`);\n }\n\n const mainFactsPath = path.join(customerDir, \"main_facts.md\");\n const interactionsPath = path.join(customerDir, \"interactions.md\");\n const pipelinePath = path.join(customerDir, \"pipeline.md\");\n\n let mainContent = \"\";\n let metadata: Record<string, unknown> = {};\n if (fs.existsSync(mainFactsPath)) {\n const raw = matter(fs.readFileSync(mainFactsPath, \"utf-8\") as string);\n mainContent = raw.content ?? \"\";\n metadata = raw.data as Record<string, unknown>;\n }\n\n // Field-level security: redact metadata fields the role may not see.\n if (role) {\n const { loadFieldAcl, redactFields } = await import(\"./rbac.js\");\n metadata = redactFields(metadata, role, loadFieldAcl(dataDir));\n }\n\n return {\n slug,\n metadata,\n quickReference: sectionBody(mainContent, \"Quick Reference\"),\n contacts: sectionBody(mainContent, \"Contacts\"),\n criticalContext: sectionBody(mainContent, \"Critical Context\"),\n openQuestions: sectionBody(mainContent, \"Open Questions\"),\n recentActivity: parseRecentInteractions(interactionsPath, MAX_INTERACTIONS),\n pipeline: parsePipelineContent(pipelinePath),\n };\n}\n"],"mappings":";;;;AAIA,MAAM,mBAAmB;AAEzB,SAAS,eAAe,MAAsB;CAC5C,OAAO,KAAK,KAAK,KAAK,SAAS,CAAC;AAClC;AAEA,SAAS,wBAAwB,UAAkB,OAAuB;CACxE,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,OAAO;CAMrC,OALgB,GAAG,aAAa,UAAU,OAGpB,EAAE,MAAM,4BAA4B,EAAE,QAAQ,MAAM,EAAE,KAAK,CAC5D,EAAE,MAAM,GAAG,KACpB,EAAE,KAAK,IAAI,EAAE,KAAK;AAChC;AAEA,SAAS,qBAAqB,UAA0B;CACtD,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,OAAO;CAErC,OADgB,GAAG,aAAa,UAAU,OAC7B,EAAE,KAAK;AACtB;AAEA,SAAS,eAAe,SAAiB,aAA6B;CAEpE,MAAM,QAAQ,IADI,OAAO,MAAM,YAAY,yBAAyB,GAClD,EAAE,KAAK,OAAO;CAChC,OAAO,SAAS,MAAM,MAAM,IAAI,KAAK,IAAI;AAC3C;AAEA,eAAsB,aAAa,SAAiB,MAA+B;CACjF,MAAM,cAAc,KAAK,KAAK,SAAS,aAAa,IAAI;CAExD,IAAI,CAAC,GAAG,WAAW,WAAW,GAC5B,MAAM,IAAI,MAAM,aAAa,KAAK,YAAY;CAGhD,MAAM,gBAAgB,KAAK,KAAK,aAAa,eAAe;CAC5D,MAAM,mBAAmB,KAAK,KAAK,aAAa,iBAAiB;CACjE,MAAM,eAAe,KAAK,KAAK,aAAa,aAAa;CAGzD,IAAI,cAAc;CAClB,IAAI,iBAAiB;CACrB,IAAI,GAAG,WAAW,aAAa,GAAG;EAEhC,MAAM,MAAM,OADQ,GAAG,aAAa,eAAe,OACtB,CAAC;EAC9B,cAAc,IAAI,WAAW;EAC7B,iBAAiB,OAAO,QAAQ,IAAI,IAA+B,EAChE,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,IAAI,KAAK,UAAU,CAAC,GAAG,EAC5C,KAAK,IAAI;CACd;CAEA,MAAM,WAAW,eAAe,aAAa,iBAAiB;CAC9D,MAAM,WAAW,eAAe,aAAa,UAAU;CACvD,MAAM,kBAAkB,eAAe,aAAa,kBAAkB;CACtE,MAAM,gBAAgB,eAAe,aAAa,gBAAgB;CAElE,MAAM,iBAAiB,wBAAwB,kBAAkB,gBAAgB;CACjF,MAAM,kBAAkB,qBAAqB,YAAY;CA2BzD,MAAM,MAAM;EAxBV,uBAAuB;EACvB;EACA;EACA,kBAAkB;EAClB;EACA;EACA,YAAY;EACZ;EACA;EACA,YAAY;EACZ;EACA;EACA,mBAAmB;EACnB;EACA;EACA,kBAAkB;EAClB;EACA;EACA,mBAAmB;EACnB;EACA;EACA,iBAAiB;CAGA,EAAE,KAAK,IAAI;CAI9B,IAHsB,eAAe,GAGrB,IAAI,KAAM;EACxB,MAAM,kBAAkB,wBAAwB,kBAAkB,CAAC;EAyBnE,OAAO;GAvBL,uBAAuB;GACvB;GACA;GACA,kBAAkB;GAClB;GACA;GACA,YAAY;GACZ;GACA;GACA,YAAY;GACZ;GACA;GACA,mBAAmB;GACnB;GACA;GACA,mBAAmB;GACnB;GACA;GACA,mBAAmB;GACnB;GACA;GACA,iBAAiB;EAEE,EAAE,KAAK,IAAI;CAClC;CAEA,OAAO;AACT"}
@@ -1,2 +0,0 @@
1
- import { t as buildContext } from "./context-builder-BzWAp3Zs.js";
2
- export { buildContext };
@@ -1 +0,0 @@
1
- {"version":3,"file":"custom-fields-Pl2t9xzp.js","names":[],"sources":["../src/core/custom-fields.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\n/**\n * Metadata-driven custom fields — the first increment of the metadata model\n * (next-plan N1-7). Definitions live in .agentic/schema/custom-fields.json and\n * extend customers without code changes. Core schemas stay strict; custom\n * fields are validated separately against this registry.\n */\nexport type CustomFieldType = \"text\" | \"number\" | \"boolean\" | \"date\" | \"select\";\n\nexport interface FieldDefinition {\n name: string;\n type: CustomFieldType;\n label?: string;\n options?: string[];\n}\n\nfunction schemaPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"schema\", \"custom-fields.json\");\n}\n\nexport function loadFieldDefinitions(dataDir: string): FieldDefinition[] {\n const p = schemaPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as {\n fields?: FieldDefinition[];\n };\n return Array.isArray(data.fields) ? data.fields : [];\n } catch {\n return [];\n }\n}\n\n/** Add or update (by name) a custom field definition. */\nexport function defineCustomField(dataDir: string, def: FieldDefinition): FieldDefinition[] {\n const defs = loadFieldDefinitions(dataDir);\n const idx = defs.findIndex((d) => d.name === def.name);\n if (idx >= 0) defs[idx] = def;\n else defs.push(def);\n const p = schemaPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ fields: defs }, null, 2), \"utf-8\");\n return defs;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n values: Record<string, string | number | boolean>;\n errors: string[];\n}\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Validate + coerce a record of raw values against custom field definitions. */\nexport function validateCustomFields(\n input: Record<string, unknown>,\n defs: FieldDefinition[]\n): ValidationResult {\n const byName = new Map(defs.map((d) => [d.name, d]));\n const values: Record<string, string | number | boolean> = {};\n const errors: string[] = [];\n\n for (const [key, raw] of Object.entries(input)) {\n const def = byName.get(key);\n if (!def) {\n errors.push(`Unknown custom field: ${key}`);\n continue;\n }\n const str = String(raw).trim();\n switch (def.type) {\n case \"number\": {\n const n = Number(str);\n if (!Number.isFinite(n)) errors.push(`${key}: not a number`);\n else values[key] = n;\n break;\n }\n case \"boolean\": {\n if (/^(true|yes|1)$/i.test(str)) values[key] = true;\n else if (/^(false|no|0)$/i.test(str)) values[key] = false;\n else errors.push(`${key}: not a boolean`);\n break;\n }\n case \"date\": {\n if (DATE_RE.test(str)) values[key] = str;\n else errors.push(`${key}: expected YYYY-MM-DD`);\n break;\n }\n case \"select\": {\n if (def.options && def.options.includes(str)) values[key] = str;\n else errors.push(`${key}: must be one of ${(def.options ?? []).join(\", \")}`);\n break;\n }\n default:\n values[key] = str;\n }\n }\n\n return { valid: errors.length === 0, values, errors };\n}\n"],"mappings":";;;AAkBA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,UAAU,oBAAoB;AACtE;AAEA,SAAgB,qBAAqB,SAAoC;CACvE,MAAM,IAAI,WAAW,OAAO;CAC5B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAG7D,OAAO,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;CACrD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;;AAGA,SAAgB,kBAAkB,SAAiB,KAAyC;CAC1F,MAAM,OAAO,qBAAqB,OAAO;CACzC,MAAM,MAAM,KAAK,WAAW,MAAM,EAAE,SAAS,IAAI,IAAI;CACrD,IAAI,OAAO,GAAG,KAAK,OAAO;MACrB,KAAK,KAAK,GAAG;CAClB,MAAM,IAAI,WAAW,OAAO;CAC5B,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,EAAE,QAAQ,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO;CACtE,OAAO;AACT;AAQA,MAAM,UAAU;;AAGhB,SAAgB,qBACd,OACA,MACkB;CAClB,MAAM,SAAS,IAAI,IAAI,KAAK,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CACnD,MAAM,SAAoD,CAAC;CAC3D,MAAM,SAAmB,CAAC;CAE1B,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,KAAK,GAAG;EAC9C,MAAM,MAAM,OAAO,IAAI,GAAG;EAC1B,IAAI,CAAC,KAAK;GACR,OAAO,KAAK,yBAAyB,KAAK;GAC1C;EACF;EACA,MAAM,MAAM,OAAO,GAAG,EAAE,KAAK;EAC7B,QAAQ,IAAI,MAAZ;GACE,KAAK,UAAU;IACb,MAAM,IAAI,OAAO,GAAG;IACpB,IAAI,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO,KAAK,GAAG,IAAI,eAAe;SACtD,OAAO,OAAO;IACnB;GACF;GACA,KAAK;IACH,IAAI,kBAAkB,KAAK,GAAG,GAAG,OAAO,OAAO;SAC1C,IAAI,kBAAkB,KAAK,GAAG,GAAG,OAAO,OAAO;SAC/C,OAAO,KAAK,GAAG,IAAI,gBAAgB;IACxC;GAEF,KAAK;IACH,IAAI,QAAQ,KAAK,GAAG,GAAG,OAAO,OAAO;SAChC,OAAO,KAAK,GAAG,IAAI,sBAAsB;IAC9C;GAEF,KAAK;IACH,IAAI,IAAI,WAAW,IAAI,QAAQ,SAAS,GAAG,GAAG,OAAO,OAAO;SACvD,OAAO,KAAK,GAAG,IAAI,oBAAoB,IAAI,WAAW,CAAC,GAAG,KAAK,IAAI,GAAG;IAC3E;GAEF,SACE,OAAO,OAAO;EAClB;CACF;CAEA,OAAO;EAAE,OAAO,OAAO,WAAW;EAAG;EAAQ;CAAO;AACtD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"custom-objects-BHgn1GEX.js","names":[],"sources":["../src/core/custom-objects.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { validateCustomFields, type FieldDefinition } from \"./custom-fields.js\";\n\n/**\n * Custom objects — runtime-defined entity types with their own fields, stored\n * as JSON without code migrations (Twenty-style \"no-migration\" model, the\n * N5-1 increment of the metadata layer). Definitions live in\n * .agentic/schema/custom-objects.json; records in .agentic/objects/<name>.json.\n */\nexport interface ObjectDefinition {\n name: string;\n label?: string;\n fields: FieldDefinition[];\n}\n\nexport interface ObjectRecord {\n id: string;\n createdAt: string;\n updatedAt: string;\n values: Record<string, string | number | boolean>;\n}\n\nexport interface RecordResult {\n ok: boolean;\n record?: ObjectRecord;\n errors?: string[];\n}\n\nfunction objectsSchemaPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"schema\", \"custom-objects.json\");\n}\nfunction recordsPath(dataDir: string, name: string): string {\n return path.join(dataDir, \".agentic\", \"objects\", `${name}.json`);\n}\n\nexport function loadCustomObjects(dataDir: string): ObjectDefinition[] {\n const p = objectsSchemaPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as {\n objects?: ObjectDefinition[];\n };\n return Array.isArray(data.objects) ? data.objects : [];\n } catch {\n return [];\n }\n}\n\nexport function getObjectDefinition(dataDir: string, name: string): ObjectDefinition | undefined {\n return loadCustomObjects(dataDir).find((o) => o.name === name);\n}\n\n/** Add or update (by name) a custom object definition. */\nexport function defineCustomObject(dataDir: string, def: ObjectDefinition): ObjectDefinition[] {\n const objs = loadCustomObjects(dataDir);\n const idx = objs.findIndex((o) => o.name === def.name);\n if (idx >= 0) objs[idx] = def;\n else objs.push(def);\n const p = objectsSchemaPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ objects: objs }, null, 2), \"utf-8\");\n return objs;\n}\n\nexport function listRecords(dataDir: string, name: string): ObjectRecord[] {\n const p = recordsPath(dataDir, name);\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as { records?: ObjectRecord[] };\n return Array.isArray(data.records) ? data.records : [];\n } catch {\n return [];\n }\n}\n\nfunction writeRecords(dataDir: string, name: string, records: ObjectRecord[]): void {\n const p = recordsPath(dataDir, name);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ records }, null, 2), \"utf-8\");\n}\n\nexport function getRecord(dataDir: string, name: string, id: string): ObjectRecord | undefined {\n return listRecords(dataDir, name).find((r) => r.id === id);\n}\n\nexport function createRecord(\n dataDir: string,\n name: string,\n values: Record<string, unknown>\n): RecordResult {\n const def = getObjectDefinition(dataDir, name);\n if (!def) return { ok: false, errors: [`Unknown object: ${name}`] };\n\n const validation = validateCustomFields(values, def.fields);\n if (!validation.valid) return { ok: false, errors: validation.errors };\n\n const now = new Date().toISOString();\n const record: ObjectRecord = {\n id: `${name}_${randomBytes(6).toString(\"hex\")}`,\n createdAt: now,\n updatedAt: now,\n values: validation.values,\n };\n writeRecords(dataDir, name, [...listRecords(dataDir, name), record]);\n return { ok: true, record };\n}\n\nexport function updateRecord(\n dataDir: string,\n name: string,\n id: string,\n values: Record<string, unknown>\n): RecordResult {\n const def = getObjectDefinition(dataDir, name);\n if (!def) return { ok: false, errors: [`Unknown object: ${name}`] };\n\n const records = listRecords(dataDir, name);\n const idx = records.findIndex((r) => r.id === id);\n if (idx < 0) return { ok: false, errors: [`Record not found: ${id}`] };\n\n const validation = validateCustomFields(values, def.fields);\n if (!validation.valid) return { ok: false, errors: validation.errors };\n\n const updated: ObjectRecord = {\n ...records[idx]!,\n updatedAt: new Date().toISOString(),\n values: { ...records[idx]!.values, ...validation.values },\n };\n records[idx] = updated;\n writeRecords(dataDir, name, records);\n return { ok: true, record: updated };\n}\n\nexport function deleteRecord(dataDir: string, name: string, id: string): boolean {\n const records = listRecords(dataDir, name);\n const next = records.filter((r) => r.id !== id);\n if (next.length === records.length) return false;\n writeRecords(dataDir, name, next);\n return true;\n}\n"],"mappings":";;;;;AA8BA,SAAS,kBAAkB,SAAyB;CAClD,OAAO,KAAK,KAAK,SAAS,YAAY,UAAU,qBAAqB;AACvE;AACA,SAAS,YAAY,SAAiB,MAAsB;CAC1D,OAAO,KAAK,KAAK,SAAS,YAAY,WAAW,GAAG,KAAK,MAAM;AACjE;AAEA,SAAgB,kBAAkB,SAAqC;CACrE,MAAM,IAAI,kBAAkB,OAAO;CACnC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAG7D,OAAO,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC;CACvD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAgB,oBAAoB,SAAiB,MAA4C;CAC/F,OAAO,kBAAkB,OAAO,EAAE,MAAM,MAAM,EAAE,SAAS,IAAI;AAC/D;;AAGA,SAAgB,mBAAmB,SAAiB,KAA2C;CAC7F,MAAM,OAAO,kBAAkB,OAAO;CACtC,MAAM,MAAM,KAAK,WAAW,MAAM,EAAE,SAAS,IAAI,IAAI;CACrD,IAAI,OAAO,GAAG,KAAK,OAAO;MACrB,KAAK,KAAK,GAAG;CAClB,MAAM,IAAI,kBAAkB,OAAO;CACnC,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,EAAE,SAAS,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO;CACvE,OAAO;AACT;AAEA,SAAgB,YAAY,SAAiB,MAA8B;CACzE,MAAM,IAAI,YAAY,SAAS,IAAI;CACnC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAC7D,OAAO,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC;CACvD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,aAAa,SAAiB,MAAc,SAA+B;CAClF,MAAM,IAAI,YAAY,SAAS,IAAI;CACnC,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,EAAE,QAAQ,GAAG,MAAM,CAAC,GAAG,OAAO;AACnE;AAMA,SAAgB,aACd,SACA,MACA,QACc;CACd,MAAM,MAAM,oBAAoB,SAAS,IAAI;CAC7C,IAAI,CAAC,KAAK,OAAO;EAAE,IAAI;EAAO,QAAQ,CAAC,mBAAmB,MAAM;CAAE;CAElE,MAAM,aAAa,qBAAqB,QAAQ,IAAI,MAAM;CAC1D,IAAI,CAAC,WAAW,OAAO,OAAO;EAAE,IAAI;EAAO,QAAQ,WAAW;CAAO;CAErE,MAAM,uBAAM,IAAI,KAAK,GAAE,YAAY;CACnC,MAAM,SAAuB;EAC3B,IAAI,GAAG,KAAK,GAAG,YAAY,CAAC,EAAE,SAAS,KAAK;EAC5C,WAAW;EACX,WAAW;EACX,QAAQ,WAAW;CACrB;CACA,aAAa,SAAS,MAAM,CAAC,GAAG,YAAY,SAAS,IAAI,GAAG,MAAM,CAAC;CACnE,OAAO;EAAE,IAAI;EAAM;CAAO;AAC5B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"customer-dir-DIylZ8Q6.js","names":[],"sources":["../src/schemas/main-facts.ts","../src/fs/customer-dir.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const MainFactsSchema = z.object({\n name: z.string().min(1),\n domain: z.string().optional(),\n email: z.string().optional(),\n phone: z.string().optional(),\n industry: z.string().optional(),\n relationship_stage: z.enum([\"prospect\", \"active\", \"churned\", \"paused\"]),\n deal_value: z.number().optional(),\n currency: z.string().default(\"EUR\"),\n primary_contact: z.string().optional(),\n timezone: z.string().optional(),\n tags: z.array(z.string()).default([]),\n created: z.preprocess(\n (v) => (v instanceof Date ? v.toISOString().slice(0, 10) : v),\n z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\")\n ),\n updated: z.preprocess(\n (v) => (v instanceof Date ? v.toISOString().slice(0, 10) : v),\n z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\")\n ),\n});\n\nexport type MainFacts = z.infer<typeof MainFactsSchema>;\n","import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\nimport { fromZodError } from \"zod-validation-error\";\nimport { MainFactsSchema, type MainFacts } from \"../schemas/main-facts.js\";\n\nexport function getCustomerDir(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug);\n}\n\nexport function customerExists(dataDir: string, slug: string): boolean {\n return fs.existsSync(getCustomerDir(dataDir, slug));\n}\n\n/** List all customer slugs (immediate subdirectories of customers/). */\nexport function listCustomerSlugs(dataDir: string): string[] {\n const dir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(dir)) return [];\n return fs.readdirSync(dir).filter((s) => {\n try {\n return fs.statSync(path.join(dir, s)).isDirectory();\n } catch {\n return false;\n }\n });\n}\n\nexport async function ensureCustomerDir(dataDir: string, slug: string): Promise<void> {\n const customerDir = getCustomerDir(dataDir, slug);\n fs.mkdirSync(customerDir, { recursive: true });\n fs.mkdirSync(path.join(customerDir, \"attachments\"), { recursive: true });\n fs.mkdirSync(path.join(customerDir, \"transcripts\"), { recursive: true });\n}\n\nexport async function writeMainFacts(\n dataDir: string,\n slug: string,\n facts: MainFacts\n): Promise<void> {\n const filePath = path.join(getCustomerDir(dataDir, slug), \"main_facts.md\");\n // Strip undefined values — gray-matter YAML serializer rejects them\n const clean = Object.fromEntries(\n Object.entries(facts as Record<string, unknown>).filter(([, v]) => v !== undefined)\n );\n const content = matter.stringify(\"\", clean);\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n\nexport async function readMainFacts(dataDir: string, slug: string): Promise<MainFacts> {\n const filePath = path.join(getCustomerDir(dataDir, slug), \"main_facts.md\");\n if (!fs.existsSync(filePath)) {\n throw new Error(`main_facts.md not found for customer '${slug}'`);\n }\n // Use fs.readFileSync so the memfs mock is respected in tests,\n // then parse the string with matter.\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n const raw = matter(content);\n // gray-matter parses YYYY-MM-DD as Date objects; coerce back to strings for Zod\n const data = raw.data as Record<string, unknown>;\n for (const key of [\"created\", \"updated\"] as const) {\n if (data[key] instanceof Date) {\n data[key] = (data[key] as Date).toISOString().slice(0, 10);\n }\n }\n const result = MainFactsSchema.safeParse(data);\n if (!result.success) {\n throw new Error(\n fromZodError(result.error, {\n prefix: `Schema error in ${filePath}`,\n prefixSeparator: \":\\n - \",\n issueSeparator: \"\\n - \",\n }).message\n );\n }\n return result.data;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-lock-B_zi7NQl.js","names":[],"sources":["../src/core/file-lock.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { withFileQueue } from \"../fs/write-queue.js\";\n\nexport async function withJsonFile<T>(\n filePath: string,\n updater: (current: T | null) => T | Promise<T>\n): Promise<T> {\n return withFileQueue(filePath, async () => {\n // Read current state\n let current: T | null = null;\n if (fs.existsSync(filePath)) {\n try {\n current = JSON.parse(fs.readFileSync(filePath, \"utf-8\") as string) as T;\n } catch {\n current = null;\n }\n }\n\n // Apply updater — may throw, in which case we do NOT write\n const next = await updater(current);\n\n // Write atomically (within the queue lock)\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(next, null, 2), \"utf-8\");\n\n return next;\n });\n}\n"],"mappings":";;;;AAIA,eAAsB,aACpB,UACA,SACY;CACZ,OAAO,cAAc,UAAU,YAAY;EAEzC,IAAI,UAAoB;EACxB,IAAI,GAAG,WAAW,QAAQ,GACxB,IAAI;GACF,UAAU,KAAK,MAAM,GAAG,aAAa,UAAU,OAAO,CAAW;EACnE,QAAQ;GACN,UAAU;EACZ;EAIF,MAAM,OAAO,MAAM,QAAQ,OAAO;EAGlC,GAAG,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EACxD,GAAG,cAAc,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;EAEjE,OAAO;CACT,CAAC;AACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"gmail-sync-DIaxInDT.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 { 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 fs.writeFileSync(p, JSON.stringify(updated, null, 2), \"utf-8\");\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\";\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 process.stderr.write(\n `[gmail-sync] Skipping message ${msg.id} after retries: ${(err as Error).message}\\n`\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 process.stderr.write(`[gmail-sync] LanceDB index failed: ${(err as Error).message}\\n`);\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 +0,0 @@
1
- {"version":3,"file":"gmail-sync-hHm9gaWd.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 { 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 fs.writeFileSync(p, JSON.stringify(updated, null, 2), \"utf-8\");\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\";\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 process.stderr.write(\n `[gmail-sync] Skipping message ${msg.id} after retries: ${(err as Error).message}\\n`\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 process.stderr.write(`[gmail-sync] LanceDB index failed: ${(err as Error).message}\\n`);\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 +0,0 @@
1
- {"version":3,"file":"gmail-sync-rQaVqKWd.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 { 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 fs.writeFileSync(p, JSON.stringify(updated, null, 2), \"utf-8\");\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\";\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 process.stderr.write(\n `[gmail-sync] Skipping message ${msg.id} after retries: ${(err as Error).message}\\n`\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 process.stderr.write(`[gmail-sync] LanceDB index failed: ${(err as Error).message}\\n`);\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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"}