@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.
- package/README.md +270 -669
- package/dist/{approvals-DpjxGHFp.js → approvals-CmDT2eUg.js} +7 -24
- package/dist/approvals-CmDT2eUg.js.map +1 -0
- package/dist/{ask-CID3jnuL.js → ask-CDysGnRg.js} +6 -6
- package/dist/{ask-CID3jnuL.js.map → ask-CDysGnRg.js.map} +1 -1
- package/dist/atomic-write-8yjqqLtS.js +29 -0
- package/dist/atomic-write-8yjqqLtS.js.map +1 -0
- package/dist/atomic-write-BYmF-ThH.cjs +37 -0
- package/dist/atomic-write-BYmF-ThH.cjs.map +1 -0
- package/dist/attachments-CX2GAtsw.cjs +517 -0
- package/dist/attachments-CX2GAtsw.cjs.map +1 -0
- package/dist/attachments-D207gXfN.js +514 -0
- package/dist/attachments-D207gXfN.js.map +1 -0
- package/dist/attachments-rLa96rOK.js +514 -0
- package/dist/attachments-rLa96rOK.js.map +1 -0
- package/dist/auth-B5DcjJ_6.js +2 -0
- package/dist/{auth-DFWwWcYD.js → auth-DDXZTwS0.js} +4 -13
- package/dist/auth-DDXZTwS0.js.map +1 -0
- package/dist/{autofill-Di_-SP7t.js → autofill-B9VtlR2j.js} +2 -2
- package/dist/{autofill-Di_-SP7t.js.map → autofill-B9VtlR2j.js.map} +1 -1
- package/dist/{backup-CeMk9z86.js → backup-CTlIxUdO.js} +5 -7
- package/dist/backup-CTlIxUdO.js.map +1 -0
- package/dist/backup-LFnC09oV.js +2 -0
- package/dist/chunk-BfDYWZQ8.cjs +32 -0
- package/dist/chunk-BfDYWZQ8.cjs.map +1 -0
- package/dist/chunk-BhUZmQg5.js +32 -0
- package/dist/chunk-BhUZmQg5.js.map +1 -0
- package/dist/chunk-ChC83jai.js +2 -0
- package/dist/chunk-e_w8qqtP.js +32 -0
- package/dist/chunk-e_w8qqtP.js.map +1 -0
- package/dist/{churn-C28IgnAj.js → churn-DN9WDGNM.js} +3 -3
- package/dist/{churn-C28IgnAj.js.map → churn-DN9WDGNM.js.map} +1 -1
- package/dist/cli.js +285 -186
- package/dist/cli.js.map +1 -1
- package/dist/{compliance-CKSBoQUe.js → compliance-Bc12Hn9a.js} +3 -3
- package/dist/{compliance-CKSBoQUe.js.map → compliance-Bc12Hn9a.js.map} +1 -1
- package/dist/{compliance-CujOqAKk.js → compliance-TqYQXhBj.js} +1 -1
- package/dist/{compliance-B1kk5-YS.js → compliance-kq0xHRw3.js} +3 -3
- package/dist/{compliance-B1kk5-YS.js.map → compliance-kq0xHRw3.js.map} +1 -1
- package/dist/{compliance-B91zNvCR.cjs → compliance-pAj9FcGI.cjs} +3 -3
- package/dist/{compliance-B91zNvCR.cjs.map → compliance-pAj9FcGI.cjs.map} +1 -1
- package/dist/{context-builder-BzWAp3Zs.js → context-builder-7Uab5-G4.js} +3 -2
- package/dist/context-builder-7Uab5-G4.js.map +1 -0
- package/dist/context-builder-hmOPvgso.js +2 -0
- package/dist/{custom-fields-CzNeD3_v.js → custom-fields-BMyz5Ruh.js} +1 -1
- package/dist/{custom-fields-Pl2t9xzp.js → custom-fields-GzpOHW_2.js} +4 -13
- package/dist/custom-fields-GzpOHW_2.js.map +1 -0
- package/dist/{custom-objects-CIFrmQ2V.js → custom-objects-BNy-ayR-.js} +1 -1
- package/dist/{custom-objects-BHgn1GEX.js → custom-objects-CxW1gHwJ.js} +10 -25
- package/dist/custom-objects-CxW1gHwJ.js.map +1 -0
- package/dist/{customer-dir-DIylZ8Q6.js → customer-dir-CkMMXhb0.js} +9 -4
- package/dist/customer-dir-CkMMXhb0.js.map +1 -0
- package/dist/daemon/worker.js +66 -40
- package/dist/daemon/worker.js.map +1 -1
- package/dist/doctor-C14-vnJ1.js +103 -0
- package/dist/doctor-C14-vnJ1.js.map +1 -0
- package/dist/email-body-BFSRa0AW.cjs +42 -0
- package/dist/email-body-BFSRa0AW.cjs.map +1 -0
- package/dist/email-body-BOd7U-D2.js +42 -0
- package/dist/email-body-BOd7U-D2.js.map +1 -0
- package/dist/{enrichment-3XvgGDfB.js → enrichment-CDFdWmvD.js} +3 -3
- package/dist/{enrichment-3XvgGDfB.js.map → enrichment-CDFdWmvD.js.map} +1 -1
- package/dist/{file-lock-B_zi7NQl.js → file-lock-CcHotQkZ.js} +3 -4
- package/dist/file-lock-CcHotQkZ.js.map +1 -0
- package/dist/{gmail-sync-DIaxInDT.js → gmail-sync-B4Iu3AQb.js} +56 -22
- package/dist/gmail-sync-B4Iu3AQb.js.map +1 -0
- package/dist/{gmail-sync-hHm9gaWd.cjs → gmail-sync-BpSVESSe.cjs} +55 -21
- package/dist/gmail-sync-BpSVESSe.cjs.map +1 -0
- package/dist/{gmail-sync-rQaVqKWd.js → gmail-sync-DIbrPnTK.js} +55 -21
- package/dist/gmail-sync-DIbrPnTK.js.map +1 -0
- package/dist/{gmail-webhook-handler-e5Od25FX.js → gmail-webhook-handler-BzOFbvgh.js} +4 -4
- package/dist/{gmail-webhook-handler-e5Od25FX.js.map → gmail-webhook-handler-BzOFbvgh.js.map} +1 -1
- package/dist/{gmail-webhook-handler-DS7OlRPX.js → gmail-webhook-handler-CvSDW_Js.js} +2 -2
- package/dist/{goal-engine-KpBftn4V.js → goal-engine-BbroPhqm.js} +10 -11
- package/dist/goal-engine-BbroPhqm.js.map +1 -0
- package/dist/{goal-engine-CUZSpERI.js → goal-engine-CfDAJTFt.js} +1 -1
- package/dist/{google-drive-sync-DEPcqFca.js → google-drive-sync-B_I1d54Y.js} +3 -3
- package/dist/{google-drive-sync-DEPcqFca.js.map → google-drive-sync-B_I1d54Y.js.map} +1 -1
- package/dist/html-BaeOCZKE.js +36 -0
- package/dist/html-BaeOCZKE.js.map +1 -0
- package/dist/html-CmOku6jS.cjs +47 -0
- package/dist/html-CmOku6jS.cjs.map +1 -0
- package/dist/{hygiene-DZqfYpFf.js → hygiene-DzQPnc6P.js} +3 -3
- package/dist/{hygiene-DZqfYpFf.js.map → hygiene-DzQPnc6P.js.map} +1 -1
- package/dist/identity-CB7j-Zr1.js +2 -0
- package/dist/{identity-CI6olMNm.js → identity-_uZ3Lbr2.js} +2 -2
- package/dist/{identity-CI6olMNm.js.map → identity-_uZ3Lbr2.js.map} +1 -1
- package/dist/{import-hubspot-BaK71U_K.js → import-hubspot-CTId9IGV.js} +51 -45
- package/dist/import-hubspot-CTId9IGV.js.map +1 -0
- package/dist/{index-YqwMd6aQ.d.cts → index-BAutNcAT.d.cts} +20 -12
- package/dist/index-BAutNcAT.d.cts.map +1 -0
- package/dist/{index-V8BFaH-b.d.ts → index-FzDsNSSb.d.ts} +12 -4
- package/dist/index-FzDsNSSb.d.ts.map +1 -0
- package/dist/index.cjs +19 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -12
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -21
- package/dist/index.js.map +1 -1
- package/dist/interactions-writer-B2y-73lh.js +2 -0
- package/dist/{interactions-writer-SLHnoEeE.js → interactions-writer-B8XAzdqR.js} +34 -4
- package/dist/interactions-writer-B8XAzdqR.js.map +1 -0
- package/dist/{interactions-writer-CrPStUll.cjs → interactions-writer-BRJNrefF.cjs} +7 -3
- package/dist/interactions-writer-BRJNrefF.cjs.map +1 -0
- package/dist/{interactions-writer-DO3KcSR3.js → interactions-writer-ZQcpFOh9.js} +7 -3
- package/dist/interactions-writer-ZQcpFOh9.js.map +1 -0
- package/dist/json-store-WWsFzXub.js +43 -0
- package/dist/json-store-WWsFzXub.js.map +1 -0
- package/dist/{knowledge-base-D0Fh40kc.js → knowledge-base--063Kpa3.js} +51 -22
- package/dist/knowledge-base--063Kpa3.js.map +1 -0
- package/dist/{lancedb-CCBbpulq.js → lancedb-CswQEE5K.js} +1 -1
- package/dist/{lancedb-rlvWoPwl.js → lancedb-CuHKNsNZ.js} +4 -3
- package/dist/lancedb-CuHKNsNZ.js.map +1 -0
- package/dist/{lead-model-BCFzyktm.js → lead-model-CEmx7te7.js} +6 -14
- package/dist/lead-model-CEmx7te7.js.map +1 -0
- package/dist/{llm-Z8RIYkpF.js → llm-BnSUBisu.js} +2 -2
- package/dist/{llm-Z8RIYkpF.js.map → llm-BnSUBisu.js.map} +1 -1
- package/dist/{llm-iijeXmgq.cjs → llm-CXycmEl9.cjs} +2 -2
- package/dist/{llm-iijeXmgq.cjs.map → llm-CXycmEl9.cjs.map} +1 -1
- package/dist/{llm-DEjWcqmW.js → llm-DSX1-wFu.js} +1 -1
- package/dist/{llm-DvzZqva0.js → llm-PZzgPphl.js} +3 -3
- package/dist/{llm-DvzZqva0.js.map → llm-PZzgPphl.js.map} +1 -1
- package/dist/logger-BkInaGoV.cjs +167 -0
- package/dist/logger-BkInaGoV.cjs.map +1 -0
- package/dist/logger-Dyl4VcLO.js +147 -0
- package/dist/logger-Dyl4VcLO.js.map +1 -0
- package/dist/logger-UaF5p9d1.js +147 -0
- package/dist/logger-UaF5p9d1.js.map +1 -0
- package/dist/logger-vKQS34w9.js +2 -0
- package/dist/mcp-CdTJWTJf.d.cts.map +1 -1
- package/dist/mcp-CdTJWTJf.d.ts.map +1 -1
- package/dist/mcp.cjs +365 -319
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +365 -319
- package/dist/mcp.js.map +1 -1
- package/dist/{memory-Cy6-Tbyl.js → memory-D8hmgD9d.js} +1 -1
- package/dist/{memory-Bb6ky3kb.js → memory-Dzr9dXSM.js} +4 -11
- package/dist/memory-Dzr9dXSM.js.map +1 -0
- package/dist/{microsoft-calendar-B6MMtUQK.js → microsoft-calendar-BgVR8GDv.js} +4 -4
- package/dist/{microsoft-calendar-B6MMtUQK.js.map → microsoft-calendar-BgVR8GDv.js.map} +1 -1
- package/dist/{microsoft-sync-CpZVoSuq.js → microsoft-sync-D30_XksI.js} +5 -5
- package/dist/{microsoft-sync-CpZVoSuq.js.map → microsoft-sync-D30_XksI.js.map} +1 -1
- package/dist/{nba-3wanmJ0U.js → nba-DwdfM93s.js} +3 -3
- package/dist/{nba-3wanmJ0U.js.map → nba-DwdfM93s.js.map} +1 -1
- package/dist/{notification-dispatcher-0vYNngWe.js → notification-dispatcher-inpKyuBz.js} +7 -3
- package/dist/notification-dispatcher-inpKyuBz.js.map +1 -0
- package/dist/{pipeline-writer-BqBrYrQc.js → pipeline-writer-0LJ6Qkat.js} +1 -1
- package/dist/{pipeline-writer-N2omexxp.cjs → pipeline-writer-B1tRAhuD.cjs} +11 -3
- package/dist/pipeline-writer-B1tRAhuD.cjs.map +1 -0
- package/dist/{pipeline-writer-BvVquKIe.js → pipeline-writer-CIllfnZl.js} +5 -3
- package/dist/pipeline-writer-CIllfnZl.js.map +1 -0
- package/dist/{pipeline-writer-eufx_0o1.js → pipeline-writer-rDj-ni6q.js} +6 -4
- package/dist/pipeline-writer-rDj-ni6q.js.map +1 -0
- package/dist/{proactive-agent-BgQXw3ac.js → proactive-agent-B7u3Bj_l.js} +6 -6
- package/dist/{proactive-agent-BgQXw3ac.js.map → proactive-agent-B7u3Bj_l.js.map} +1 -1
- package/dist/{proactive-worker-BrLHNhjH.js → proactive-worker-1zkm6aJD.js} +7 -8
- package/dist/proactive-worker-1zkm6aJD.js.map +1 -0
- package/dist/{push-manager-CowY-0IK.js → push-manager-BXM-IHfP.js} +1 -1
- package/dist/{push-manager-CdqIIkuh.js → push-manager-C0ECQgva.js} +4 -4
- package/dist/push-manager-C0ECQgva.js.map +1 -0
- package/dist/{quote-generator-OhSFsi3x.js → quote-generator-ByUyIYtw.js} +1 -1
- package/dist/{quote-generator-BfwENXzg.js → quote-generator-CTdR8eEI.js} +5 -5
- package/dist/quote-generator-CTdR8eEI.js.map +1 -0
- package/dist/rbac-DzbyFhVH.js +2 -0
- package/dist/{rbac-CTIktZaC.js → rbac-msmBc_tK.js} +19 -12
- package/dist/rbac-msmBc_tK.js.map +1 -0
- package/dist/regex-Jt5DatPi.js +13 -0
- package/dist/regex-Jt5DatPi.js.map +1 -0
- package/dist/{relationship-health-odxEoQdJ.js → relationship-health-ZZNXR1RZ.js} +8 -16
- package/dist/relationship-health-ZZNXR1RZ.js.map +1 -0
- package/dist/{revenue-simulation-Bqf2DLVB.js → revenue-simulation-D8f_YkUY.js} +9 -19
- package/dist/revenue-simulation-D8f_YkUY.js.map +1 -0
- package/dist/{revenue-simulation-BJdRTEHc.js → revenue-simulation-njJZlTqm.js} +1 -1
- package/dist/safe-path-mpp0dKtO.js +18 -0
- package/dist/safe-path-mpp0dKtO.js.map +1 -0
- package/dist/{segments-BqcD5HIl.js → segments-DI3LOQNe.js} +5 -14
- package/dist/segments-DI3LOQNe.js.map +1 -0
- package/dist/sequence-engine-C6nnewHX.js +2 -0
- package/dist/{sequence-engine-J1lTW_in.js → sequence-engine-DNTVLq7o.js} +15 -8
- package/dist/sequence-engine-DNTVLq7o.js.map +1 -0
- package/dist/{sequence-store-DaaWr0Os.js → sequence-store-CmYb6s0g.js} +6 -5
- package/dist/sequence-store-CmYb6s0g.js.map +1 -0
- package/dist/{server-Dyva03K8.js → server-DoRPPOeR.js} +308 -230
- package/dist/server-DoRPPOeR.js.map +1 -0
- package/dist/{session-D9ub6Wl1.js → session-B6XaP83h.js} +3 -3
- package/dist/session-B6XaP83h.js.map +1 -0
- package/dist/{session-B9AilxOE.js → session-BgGDyP2C.js} +3 -3
- package/dist/session-BgGDyP2C.js.map +1 -0
- package/dist/session-Bp4zTh4l.js +2 -0
- package/dist/{session-D0qFkBla.cjs → session-Mm7GQbSH.cjs} +3 -3
- package/dist/session-Mm7GQbSH.cjs.map +1 -0
- package/dist/{session-store-C8tEvMPw.js → session-store-DWxJ5Pof.js} +79 -17
- package/dist/session-store-DWxJ5Pof.js.map +1 -0
- package/dist/{session-store-B0QZE8Bx.cjs → session-store-yfwnj0OC.cjs} +126 -16
- package/dist/session-store-yfwnj0OC.cjs.map +1 -0
- package/dist/{sla-engine-5IhTsBUR.js → sla-engine-CP2KiKDS.js} +1 -1
- package/dist/{sla-engine-BqX-7u-7.js → sla-engine-O-A1ntu_.js} +2 -2
- package/dist/{sla-engine-BqX-7u-7.js.map → sla-engine-O-A1ntu_.js.map} +1 -1
- package/dist/{sop-Vp0UPWFW.js → sop-BV7ICAFR.js} +4 -11
- package/dist/sop-BV7ICAFR.js.map +1 -0
- package/dist/{sop-DkhVChGy.js → sop-D33qTHUb.js} +1 -1
- package/dist/survey-engine-DKctGcLQ.js +2 -0
- package/dist/{survey-engine-DBjCYqCv.js → survey-engine-DngXBv47.js} +5 -4
- package/dist/survey-engine-DngXBv47.js.map +1 -0
- package/dist/{sync-state-CwLSt_1m.js → sync-state-BaA8LbTI.js} +1 -1
- package/dist/{sync-state-ChaLbamC.js → sync-state-DMZgzpez.js} +4 -12
- package/dist/sync-state-DMZgzpez.js.map +1 -0
- package/dist/{ticket-writer-CjqKeIRD.js → ticket-writer-DsfpeLGZ.js} +1 -1
- package/dist/{ticket-writer-j2oX_Wal.js → ticket-writer-a9on36Wb.js} +12 -24
- package/dist/ticket-writer-a9on36Wb.js.map +1 -0
- package/dist/{tone-Bdm5uaht.js → tone-C7bqK69y.js} +5 -12
- package/dist/tone-C7bqK69y.js.map +1 -0
- package/dist/{tone-DRKlZgPr.cjs → tone-Cmc7O2Fx.cjs} +3 -9
- package/dist/tone-Cmc7O2Fx.cjs.map +1 -0
- package/dist/{tone-vNb2DAAD.js → tone-mXSftvTn.js} +3 -8
- package/dist/tone-mXSftvTn.js.map +1 -0
- package/dist/{transcript-watcher-CL2QUygI.js → transcript-watcher-BoClrJAz.js} +18 -11
- package/dist/transcript-watcher-BoClrJAz.js.map +1 -0
- package/dist/unmatched-transcripts-C92zAoM4.js +2 -0
- package/dist/unmatched-transcripts-DC-VQ9YS.js +16 -0
- package/dist/unmatched-transcripts-DC-VQ9YS.js.map +1 -0
- package/dist/update-deal-CWy1eLJI.js +2 -0
- package/dist/{update-deal-DKC79skb.js → update-deal-DSzr_Aau.js} +3 -3
- package/dist/{update-deal-DKC79skb.js.map → update-deal-DSzr_Aau.js.map} +1 -1
- package/dist/{usage-D0-TYJkw.js → usage-BVlFlKW_.js} +8 -6
- package/dist/usage-BVlFlKW_.js.map +1 -0
- package/dist/usage-CClTf5e6.cjs.map +1 -1
- package/dist/usage-D0u9a-lV.js.map +1 -1
- package/dist/{vault-DXCg29W-.js → vault-CfwZdNzC.js} +3 -4
- package/dist/vault-CfwZdNzC.js.map +1 -0
- package/dist/{vault-C1D3zScD.js → vault-DxKP4_R2.js} +1 -1
- package/dist/{webhooks-Xn6zO6kd.cjs → webhooks-CwW-3kvG.cjs} +5 -19
- package/dist/webhooks-CwW-3kvG.cjs.map +1 -0
- package/dist/{webhooks-7EpA05Qr.js → webhooks-DXr1IoKn.js} +8 -21
- package/dist/webhooks-DXr1IoKn.js.map +1 -0
- package/dist/{webhooks-BO2UAnmn.js → webhooks-sWZ8CJtR.js} +5 -18
- package/dist/webhooks-sWZ8CJtR.js.map +1 -0
- package/package.json +22 -2
- package/dist/approvals-DpjxGHFp.js.map +0 -1
- package/dist/auth-CyFuu9X_.js +0 -2
- package/dist/auth-DFWwWcYD.js.map +0 -1
- package/dist/backup-CeMk9z86.js.map +0 -1
- package/dist/backup-f_hC7rBV.js +0 -2
- package/dist/context-builder-BzWAp3Zs.js.map +0 -1
- package/dist/context-builder-DlrRcqmJ.js +0 -2
- package/dist/custom-fields-Pl2t9xzp.js.map +0 -1
- package/dist/custom-objects-BHgn1GEX.js.map +0 -1
- package/dist/customer-dir-DIylZ8Q6.js.map +0 -1
- package/dist/file-lock-B_zi7NQl.js.map +0 -1
- package/dist/gmail-sync-DIaxInDT.js.map +0 -1
- package/dist/gmail-sync-hHm9gaWd.cjs.map +0 -1
- package/dist/gmail-sync-rQaVqKWd.js.map +0 -1
- package/dist/goal-engine-KpBftn4V.js.map +0 -1
- package/dist/identity-gyfWdrcX.js +0 -2
- package/dist/import-hubspot-BaK71U_K.js.map +0 -1
- package/dist/index-V8BFaH-b.d.ts.map +0 -1
- package/dist/index-YqwMd6aQ.d.cts.map +0 -1
- package/dist/interactions-writer-CrPStUll.cjs.map +0 -1
- package/dist/interactions-writer-DO3KcSR3.js.map +0 -1
- package/dist/interactions-writer-SLHnoEeE.js.map +0 -1
- package/dist/interactions-writer-dSPy1XfO.js +0 -2
- package/dist/knowledge-base-D0Fh40kc.js.map +0 -1
- package/dist/lancedb-rlvWoPwl.js.map +0 -1
- package/dist/lead-model-BCFzyktm.js.map +0 -1
- package/dist/memory-Bb6ky3kb.js.map +0 -1
- package/dist/notification-dispatcher-0vYNngWe.js.map +0 -1
- package/dist/pipeline-writer-BvVquKIe.js.map +0 -1
- package/dist/pipeline-writer-N2omexxp.cjs.map +0 -1
- package/dist/pipeline-writer-eufx_0o1.js.map +0 -1
- package/dist/proactive-worker-BrLHNhjH.js.map +0 -1
- package/dist/push-manager-CdqIIkuh.js.map +0 -1
- package/dist/quote-generator-BfwENXzg.js.map +0 -1
- package/dist/rbac-C7c8tcES.js +0 -2
- package/dist/rbac-CTIktZaC.js.map +0 -1
- package/dist/relationship-health-odxEoQdJ.js.map +0 -1
- package/dist/revenue-simulation-Bqf2DLVB.js.map +0 -1
- package/dist/segments-BqcD5HIl.js.map +0 -1
- package/dist/sequence-engine-CCTHEBgi.js +0 -2
- package/dist/sequence-engine-J1lTW_in.js.map +0 -1
- package/dist/sequence-store-DaaWr0Os.js.map +0 -1
- package/dist/server-Dyva03K8.js.map +0 -1
- package/dist/session-B9AilxOE.js.map +0 -1
- package/dist/session-D0qFkBla.cjs.map +0 -1
- package/dist/session-D9ub6Wl1.js.map +0 -1
- package/dist/session-mWHA71Lw.js +0 -2
- package/dist/session-store-B0QZE8Bx.cjs.map +0 -1
- package/dist/session-store-C8tEvMPw.js.map +0 -1
- package/dist/sop-Vp0UPWFW.js.map +0 -1
- package/dist/survey-engine-C06hcQt3.js +0 -2
- package/dist/survey-engine-DBjCYqCv.js.map +0 -1
- package/dist/sync-state-ChaLbamC.js.map +0 -1
- package/dist/ticket-writer-j2oX_Wal.js.map +0 -1
- package/dist/tone-Bdm5uaht.js.map +0 -1
- package/dist/tone-DRKlZgPr.cjs.map +0 -1
- package/dist/tone-vNb2DAAD.js.map +0 -1
- package/dist/transcript-watcher-CL2QUygI.js.map +0 -1
- package/dist/unmatched-transcripts-BsH5bhkU.js +0 -26
- package/dist/unmatched-transcripts-BsH5bhkU.js.map +0 -1
- package/dist/unmatched-transcripts-D0PrJ9iz.js +0 -2
- package/dist/update-deal-BNwPGaTV.js +0 -2
- package/dist/usage-D0-TYJkw.js.map +0 -1
- package/dist/vault-DXCg29W-.js.map +0 -1
- package/dist/webhooks-7EpA05Qr.js.map +0 -1
- package/dist/webhooks-BO2UAnmn.js.map +0 -1
- package/dist/webhooks-Xn6zO6kd.cjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"goal-engine-KpBftn4V.js","names":[],"sources":["../src/core/goal-engine.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { runSimulation } from \"./revenue-simulation.js\";\nimport { callLlm } from \"./llm.js\";\nimport { getActor } from \"../fs/audit-log.js\";\nimport { withJsonFile } from \"./file-lock.js\";\nimport { guardIsoDate } from \"./input-guard.js\";\nimport type { DealSnapshot, SimulationInput } from \"./revenue-simulation.js\";\nimport { readPipeline } from \"../fs/pipeline-writer.js\";\nimport { listCustomerSlugs } from \"../fs/customer-dir.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type GoalMetric = \"revenue\" | \"deals_closed\" | \"meetings_booked\" | \"pipeline_created\";\nexport type GoalType = \"revenue\" | \"pipeline\" | \"relationship\" | \"churn_prevention\";\nexport type GoalStatus = \"active\" | \"completed\" | \"cancelled\" | \"blocked\";\n\nexport interface GoalSubGoal {\n priority: number;\n action: string;\n slug: string;\n dealName?: string;\n why: string;\n nextStep: string;\n targetDelta: number;\n playbookName?: string;\n}\n\nexport interface GoalDecomposition {\n analysis: string;\n currentPipeline: number;\n gap: number;\n subGoals: GoalSubGoal[];\n probabilisticOutcome: string;\n decomposedAt: string;\n}\n\nexport interface Goal {\n id: string;\n description: string;\n type: GoalType;\n target: number;\n metric: GoalMetric;\n deadline: string;\n decomposition: GoalDecomposition;\n progress: number;\n status: GoalStatus;\n createdAt: string;\n updatedAt: string;\n actor: string;\n}\n\nexport type BuildInputFn = (\n dataDir: string,\n horizon: \"quarter\" | \"year\",\n today: string\n) => Promise<SimulationInput>;\n\n// ─── Persistence ──────────────────────────────────────────────────────────────\n\nexport function goalsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"goals.json\");\n}\n\nexport function readGoals(dataDir: string): Goal[] {\n const p = goalsPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n if (Array.isArray(raw)) return raw as Goal[];\n return (raw as { goals?: Goal[] }).goals ?? [];\n } catch {\n return [];\n }\n}\n\nexport function writeGoals(dataDir: string, goals: Goal[]): void {\n const p = goalsPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(\n p,\n JSON.stringify({ goals, updatedAt: new Date().toISOString() }, null, 2),\n \"utf-8\"\n );\n}\n\nexport function makeGoalId(): string {\n return `goal_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;\n}\n\n// ─── Parsing ──────────────────────────────────────────────────────────────────\n\nexport function parseTargetFromDescription(desc: string): number {\n // Try millions first: \"1.5M\", \"1.5 million\", \"$1.5M\"\n const millionMatch = desc.match(/[\\$€£]?\\s*(\\d+(?:\\.\\d+)?)\\s*(?:M\\b|million)/i);\n if (millionMatch) return Math.round(parseFloat(millionMatch[1]!) * 1_000_000);\n\n // Then thousands: \"500k\", \"€500k\"\n const kMatch = desc.match(/[\\$€£]?\\s*(\\d+(?:\\.\\d+)?)\\s*k\\b/i);\n if (kMatch) return Math.round(parseFloat(kMatch[1]!) * 1_000);\n\n // Then raw numbers with optional currency: \"€75000\"\n const rawMatch = desc.match(/[\\$€£]\\s*(\\d{4,}(?:[,.\\d]*\\d)?)/);\n if (rawMatch) return parseInt(rawMatch[1]!.replace(/[,. ]/g, \"\"), 10);\n\n return 0;\n}\n\nexport function inferGoalType(desc: string): GoalType {\n const lower = desc.toLowerCase();\n if (/churn|retain|renewal|renew/.test(lower)) return \"churn_prevention\";\n if (/meeting|call|book|relationship|contact/.test(lower)) return \"relationship\";\n if (/pipeline|prospect|lead|qualify/.test(lower)) return \"pipeline\";\n return \"revenue\";\n}\n\nexport function inferMetric(type: GoalType): GoalMetric {\n switch (type) {\n case \"pipeline\":\n return \"pipeline_created\";\n case \"relationship\":\n return \"meetings_booked\";\n case \"revenue\":\n return \"revenue\";\n case \"churn_prevention\":\n return \"revenue\";\n }\n}\n\n// ─── Rule-based decomposition ─────────────────────────────────────────────────\n\nexport function rankDealsByLeverage(deals: DealSnapshot[]): DealSnapshot[] {\n return deals\n .filter((d) => d.stage !== \"won\" && d.stage !== \"lost\")\n .sort((a, b) => {\n const leverageA = a.value * (a.probability / 100) * (a.healthScore / 100);\n const leverageB = b.value * (b.probability / 100) * (b.healthScore / 100);\n return leverageB - leverageA;\n });\n}\n\nfunction generateNextStep(deal: DealSnapshot): string {\n if (deal.healthScore < 40 && !deal.championPresent)\n return \"Re-engage urgently and identify a champion\";\n if (deal.healthScore < 60) return \"Schedule an urgent check-in call\";\n if (deal.daysSinceContact > 14) return \"Reach out — contact is overdue\";\n if (!deal.championPresent) return \"Identify a champion or economic buyer\";\n return \"Push to next pipeline stage\";\n}\n\nexport function decomposeGoalRuleBased(\n deals: DealSnapshot[],\n target: number,\n currentP50: number,\n today: string,\n playbookLookup?: (slug: string, deal: DealSnapshot) => string | undefined\n): GoalDecomposition {\n const gap = Math.max(0, target - currentP50);\n const decomposedAt = new Date(today + \"T00:00:00Z\").toISOString();\n\n if (gap === 0) {\n return {\n analysis: `Current pipeline (P50: €${currentP50.toLocaleString()}) already meets or exceeds the target of €${target.toLocaleString()}.`,\n currentPipeline: currentP50,\n gap: 0,\n subGoals: [],\n probabilisticOutcome: `Pipeline P50 (€${currentP50.toLocaleString()}) ≥ target (€${target.toLocaleString()}).`,\n decomposedAt,\n };\n }\n\n const ranked = rankDealsByLeverage(deals);\n\n if (ranked.length === 0) {\n return {\n analysis: `No active deals found. Gap to close: €${gap.toLocaleString()}. Focus on building pipeline.`,\n currentPipeline: currentP50,\n gap,\n subGoals: [\n {\n priority: 1,\n action: \"Build pipeline from scratch\",\n slug: \"_all\",\n why: `No active deals. Need €${gap.toLocaleString()} to reach target.`,\n nextStep:\n \"Use list_customers() to find prospects and log_interaction to initiate outreach\",\n targetDelta: target,\n },\n ],\n probabilisticOutcome: `Insufficient pipeline. Need €${gap.toLocaleString()} in new deals.`,\n decomposedAt,\n };\n }\n\n const subGoals: GoalSubGoal[] = [];\n let cumulative = 0;\n\n for (const deal of ranked.slice(0, 5)) {\n if (subGoals.length >= 5) break;\n const playbookName = playbookLookup?.(deal.slug, deal);\n const subGoal: GoalSubGoal = {\n priority: subGoals.length + 1,\n action: `Accelerate ${deal.slug}/${deal.name}`,\n slug: deal.slug,\n ...(deal.name ? { dealName: deal.name } : {}),\n why: `€${deal.value.toLocaleString()} deal in ${deal.stage} — health ${deal.healthScore}/100`,\n nextStep: generateNextStep(deal),\n targetDelta: deal.value,\n ...(playbookName ? { playbookName } : {}),\n };\n subGoals.push(subGoal);\n cumulative += deal.value;\n if (cumulative >= gap) break;\n }\n\n const projectedTotal = currentP50 + cumulative;\n return {\n analysis: `Current pipeline P50: €${currentP50.toLocaleString()}. Gap to target: €${gap.toLocaleString()}. Top ${subGoals.length} deal(s) identified.`,\n currentPipeline: currentP50,\n gap,\n subGoals,\n probabilisticOutcome: `If all recommended deals close: ~€${projectedTotal.toLocaleString()} (target: €${target.toLocaleString()}).`,\n decomposedAt,\n };\n}\n\n// ─── LLM path ─────────────────────────────────────────────────────────────────\n\nexport function buildDecompositionPrompt(\n description: string,\n target: number,\n deadline: string,\n currentP50: number,\n deals: DealSnapshot[],\n today: string\n): string {\n const gap = Math.max(0, target - currentP50);\n const dealLines = deals\n .filter((d) => d.stage !== \"won\" && d.stage !== \"lost\")\n .slice(0, 8)\n .map(\n (d, i) =>\n `${i + 1}. ${d.slug}/${d.name} — €${d.value.toLocaleString()}, stage: ${d.stage}, health: ${d.healthScore}/100, probability: ${d.probability}%${d.championPresent ? \", champion ✓\" : \"\"}`\n )\n .join(\"\\n\");\n\n return `You are a sales strategy AI helping decompose a revenue goal into actionable sub-goals.\n\nGoal: ${description}\nTarget: €${target.toLocaleString()}\nDeadline: ${deadline}\nCurrent date: ${today}\nCurrent weighted pipeline (P50): €${currentP50.toLocaleString()}\nGap to close: €${gap.toLocaleString()}\n\nActive deals (sorted by weighted value):\n${dealLines || \"(no active deals)\"}\n\nReturn JSON only (no markdown wrapper):\n{\n \"analysis\": \"<1-2 sentence summary of the situation>\",\n \"subGoals\": [\n {\n \"priority\": 1,\n \"action\": \"<what to do>\",\n \"slug\": \"<customer-slug>\",\n \"dealName\": \"<deal name>\",\n \"why\": \"<why this deal matters for the goal>\",\n \"nextStep\": \"<concrete next action with deadline>\",\n \"targetDelta\": <expected revenue contribution in euros>\n }\n ],\n \"probabilisticOutcome\": \"<P50 forecast summary after actions>\"\n}`;\n}\n\nexport function parseLlmDecomposition(\n response: string,\n fallback: GoalDecomposition\n): GoalDecomposition {\n try {\n const match = response.match(/\\{[\\s\\S]*\\}/);\n if (!match) return fallback;\n const parsed = JSON.parse(match[0]) as Partial<{\n analysis: string;\n subGoals: unknown[];\n probabilisticOutcome: string;\n }>;\n if (!parsed.analysis || !Array.isArray(parsed.subGoals)) return fallback;\n return {\n analysis: parsed.analysis,\n currentPipeline: fallback.currentPipeline,\n gap: fallback.gap,\n subGoals: (parsed.subGoals as Partial<GoalSubGoal>[]).map((s, i) => ({\n priority: s.priority ?? i + 1,\n action: s.action ?? \"\",\n slug: s.slug ?? \"_all\",\n ...(s.dealName ? { dealName: s.dealName } : {}),\n why: s.why ?? \"\",\n nextStep: s.nextStep ?? \"\",\n targetDelta: s.targetDelta ?? 0,\n ...(s.playbookName ? { playbookName: s.playbookName } : {}),\n })),\n probabilisticOutcome: parsed.probabilisticOutcome ?? fallback.probabilisticOutcome,\n decomposedAt: fallback.decomposedAt,\n };\n } catch {\n return fallback;\n }\n}\n\n// ─── pursueGoal ───────────────────────────────────────────────────────────────\n\nexport async function pursueGoal(\n dataDir: string,\n input: { description: string; deadline: string; context?: string },\n options: {\n llmFn?: (prompt: string) => Promise<string>;\n buildInputFn?: BuildInputFn;\n today?: string;\n actor?: string;\n } = {}\n): Promise<Goal> {\n guardIsoDate(input.deadline, \"deadline\");\n const today = options.today ?? new Date().toISOString().slice(0, 10);\n const actor = options.actor ?? getActor();\n\n const buildFn =\n options.buildInputFn ??\n ((async (dir, horizon, t) => {\n const { buildSimulationInput } = await import(\"./revenue-simulation.js\");\n return buildSimulationInput(dir, horizon, t);\n }) as BuildInputFn);\n\n const simInput = await buildFn(dataDir, \"quarter\", today);\n const simResult = runSimulation(simInput);\n const currentP50 = simResult.p50;\n const deals = simInput.deals;\n\n const target = parseTargetFromDescription(input.description);\n const type = inferGoalType(input.description);\n const metric = inferMetric(type);\n\n const ruleBasedDecomp = decomposeGoalRuleBased(deals, target, currentP50, today);\n\n let decomposition = ruleBasedDecomp;\n const llmFn = options.llmFn ?? callLlm;\n if (options.llmFn !== undefined) {\n const prompt = buildDecompositionPrompt(\n input.description,\n target,\n input.deadline,\n currentP50,\n deals,\n today\n );\n const response = await llmFn(prompt);\n decomposition = parseLlmDecomposition(response, ruleBasedDecomp);\n }\n\n const now = new Date().toISOString();\n const goal: Goal = {\n id: makeGoalId(),\n description: input.description,\n type,\n target,\n metric,\n deadline: input.deadline,\n decomposition,\n progress: 0,\n status: \"active\",\n createdAt: now,\n updatedAt: now,\n actor,\n };\n\n await withJsonFile<{ goals: Goal[]; updatedAt: string }>(goalsPath(dataDir), (current) => {\n const existing: Goal[] = Array.isArray(current?.goals) ? current.goals : [];\n return { goals: [...existing, goal], updatedAt: new Date().toISOString() };\n });\n return goal;\n}\n\n// ─── Goal management ──────────────────────────────────────────────────────────\n\nexport function getActiveGoals(dataDir: string): Goal[] {\n return readGoals(dataDir).filter((g) => g.status === \"active\");\n}\n\nexport async function updateGoalProgress(\n dataDir: string,\n goalId: string,\n progress: number\n): Promise<Goal | null> {\n let updated: Goal | null = null;\n await withJsonFile<{ goals: Goal[]; updatedAt: string }>(goalsPath(dataDir), (current) => {\n const goals: Goal[] = Array.isArray(current?.goals) ? [...current.goals] : [];\n const idx = goals.findIndex((g) => g.id === goalId);\n if (idx >= 0) {\n updated = { ...goals[idx]!, progress, updatedAt: new Date().toISOString() };\n goals[idx] = updated;\n }\n return { goals, updatedAt: new Date().toISOString() };\n });\n return updated;\n}\n\nexport async function cancelGoal(dataDir: string, goalId: string): Promise<Goal | null> {\n let cancelled: Goal | null = null;\n await withJsonFile<{ goals: Goal[]; updatedAt: string }>(goalsPath(dataDir), (current) => {\n const goals: Goal[] = Array.isArray(current?.goals) ? [...current.goals] : [];\n const idx = goals.findIndex((g) => g.id === goalId);\n if (idx >= 0) {\n cancelled = {\n ...goals[idx]!,\n status: \"cancelled\" as const,\n updatedAt: new Date().toISOString(),\n };\n goals[idx] = cancelled;\n }\n return { goals, updatedAt: new Date().toISOString() };\n });\n return cancelled;\n}\n\n// ─── Pipeline-driven progress sync ────────────────────────────────────────────\n\nexport interface SyncResult {\n updated: string[];\n skipped: number;\n}\n\nexport async function syncGoalProgressFromPipeline(\n dataDir: string,\n _today?: string\n): Promise<SyncResult> {\n const activeGoals = getActiveGoals(dataDir);\n const revenueGoals = activeGoals.filter((g) => g.metric === \"revenue\" && g.target > 0);\n\n if (revenueGoals.length === 0) return { updated: [], skipped: activeGoals.length };\n\n let totalWon = 0;\n for (const slug of listCustomerSlugs(dataDir)) {\n const deals = await readPipeline(dataDir, slug).catch(() => []);\n for (const deal of deals) {\n if (deal.stage === \"won\") totalWon += deal.value ?? 0;\n }\n }\n\n const updated: string[] = [];\n for (const goal of revenueGoals) {\n const progress = Math.min(100, Math.round((totalWon / goal.target) * 100));\n const result = await updateGoalProgress(dataDir, goal.id, progress);\n if (result) updated.push(goal.id);\n }\n\n return { updated, skipped: activeGoals.length - revenueGoals.length };\n}\n"],"mappings":";;;;;;;;;AA4DA,SAAgB,UAAU,SAAyB;CACjD,OAAO,KAAK,KAAK,SAAS,YAAY,YAAY;AACpD;AAEA,SAAgB,UAAU,SAAyB;CACjD,MAAM,IAAI,UAAU,OAAO;CAC3B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,MAAM,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAC5D,IAAI,MAAM,QAAQ,GAAG,GAAG,OAAO;EAC/B,OAAQ,IAA2B,SAAS,CAAC;CAC/C,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAgB,WAAW,SAAiB,OAAqB;CAC/D,MAAM,IAAI,UAAU,OAAO;CAC3B,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cACD,GACA,KAAK,UAAU;EAAE;EAAO,4BAAW,IAAI,KAAK,GAAE,YAAY;CAAE,GAAG,MAAM,CAAC,GACtE,OACF;AACF;AAEA,SAAgB,aAAqB;CACnC,OAAO,QAAQ,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC;AACpE;AAIA,SAAgB,2BAA2B,MAAsB;CAE/D,MAAM,eAAe,KAAK,MAAM,8CAA8C;CAC9E,IAAI,cAAc,OAAO,KAAK,MAAM,WAAW,aAAa,EAAG,IAAI,GAAS;CAG5E,MAAM,SAAS,KAAK,MAAM,kCAAkC;CAC5D,IAAI,QAAQ,OAAO,KAAK,MAAM,WAAW,OAAO,EAAG,IAAI,GAAK;CAG5D,MAAM,WAAW,KAAK,MAAM,iCAAiC;CAC7D,IAAI,UAAU,OAAO,SAAS,SAAS,GAAI,QAAQ,UAAU,EAAE,GAAG,EAAE;CAEpE,OAAO;AACT;AAEA,SAAgB,cAAc,MAAwB;CACpD,MAAM,QAAQ,KAAK,YAAY;CAC/B,IAAI,6BAA6B,KAAK,KAAK,GAAG,OAAO;CACrD,IAAI,yCAAyC,KAAK,KAAK,GAAG,OAAO;CACjE,IAAI,iCAAiC,KAAK,KAAK,GAAG,OAAO;CACzD,OAAO;AACT;AAEA,SAAgB,YAAY,MAA4B;CACtD,QAAQ,MAAR;EACE,KAAK,YACH,OAAO;EACT,KAAK,gBACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,KAAK,oBACH,OAAO;CACX;AACF;AAIA,SAAgB,oBAAoB,OAAuC;CACzE,OAAO,MACJ,QAAQ,MAAM,EAAE,UAAU,SAAS,EAAE,UAAU,MAAM,EACrD,MAAM,GAAG,MAAM;EACd,MAAM,YAAY,EAAE,SAAS,EAAE,cAAc,QAAQ,EAAE,cAAc;EAErE,OADkB,EAAE,SAAS,EAAE,cAAc,QAAQ,EAAE,cAAc,OAClD;CACrB,CAAC;AACL;AAEA,SAAS,iBAAiB,MAA4B;CACpD,IAAI,KAAK,cAAc,MAAM,CAAC,KAAK,iBACjC,OAAO;CACT,IAAI,KAAK,cAAc,IAAI,OAAO;CAClC,IAAI,KAAK,mBAAmB,IAAI,OAAO;CACvC,IAAI,CAAC,KAAK,iBAAiB,OAAO;CAClC,OAAO;AACT;AAEA,SAAgB,uBACd,OACA,QACA,YACA,OACA,gBACmB;CACnB,MAAM,MAAM,KAAK,IAAI,GAAG,SAAS,UAAU;CAC3C,MAAM,gCAAe,IAAI,KAAK,QAAQ,YAAY,GAAE,YAAY;CAEhE,IAAI,QAAQ,GACV,OAAO;EACL,UAAU,2BAA2B,WAAW,eAAe,EAAE,4CAA4C,OAAO,eAAe,EAAE;EACrI,iBAAiB;EACjB,KAAK;EACL,UAAU,CAAC;EACX,sBAAsB,kBAAkB,WAAW,eAAe,EAAE,eAAe,OAAO,eAAe,EAAE;EAC3G;CACF;CAGF,MAAM,SAAS,oBAAoB,KAAK;CAExC,IAAI,OAAO,WAAW,GACpB,OAAO;EACL,UAAU,yCAAyC,IAAI,eAAe,EAAE;EACxE,iBAAiB;EACjB;EACA,UAAU,CACR;GACE,UAAU;GACV,QAAQ;GACR,MAAM;GACN,KAAK,0BAA0B,IAAI,eAAe,EAAE;GACpD,UACE;GACF,aAAa;EACf,CACF;EACA,sBAAsB,gCAAgC,IAAI,eAAe,EAAE;EAC3E;CACF;CAGF,MAAM,WAA0B,CAAC;CACjC,IAAI,aAAa;CAEjB,KAAK,MAAM,QAAQ,OAAO,MAAM,GAAG,CAAC,GAAG;EACrC,IAAI,SAAS,UAAU,GAAG;EAC1B,MAAM,eAAe,iBAAiB,KAAK,MAAM,IAAI;EACrD,MAAM,UAAuB;GAC3B,UAAU,SAAS,SAAS;GAC5B,QAAQ,cAAc,KAAK,KAAK,GAAG,KAAK;GACxC,MAAM,KAAK;GACX,GAAI,KAAK,OAAO,EAAE,UAAU,KAAK,KAAK,IAAI,CAAC;GAC3C,KAAK,IAAI,KAAK,MAAM,eAAe,EAAE,WAAW,KAAK,MAAM,YAAY,KAAK,YAAY;GACxF,UAAU,iBAAiB,IAAI;GAC/B,aAAa,KAAK;GAClB,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;EACzC;EACA,SAAS,KAAK,OAAO;EACrB,cAAc,KAAK;EACnB,IAAI,cAAc,KAAK;CACzB;CAEA,MAAM,iBAAiB,aAAa;CACpC,OAAO;EACL,UAAU,0BAA0B,WAAW,eAAe,EAAE,oBAAoB,IAAI,eAAe,EAAE,QAAQ,SAAS,OAAO;EACjI,iBAAiB;EACjB;EACA;EACA,sBAAsB,qCAAqC,eAAe,eAAe,EAAE,aAAa,OAAO,eAAe,EAAE;EAChI;CACF;AACF;AAIA,SAAgB,yBACd,aACA,QACA,UACA,YACA,OACA,OACQ;CACR,MAAM,MAAM,KAAK,IAAI,GAAG,SAAS,UAAU;CAC3C,MAAM,YAAY,MACf,QAAQ,MAAM,EAAE,UAAU,SAAS,EAAE,UAAU,MAAM,EACrD,MAAM,GAAG,CAAC,EACV,KACE,GAAG,MACF,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,EAAE,YAAY,qBAAqB,EAAE,YAAY,GAAG,EAAE,kBAAkB,iBAAiB,IACzL,EACC,KAAK,IAAI;CAEZ,OAAO;;QAED,YAAY;WACT,OAAO,eAAe,EAAE;YACvB,SAAS;gBACL,MAAM;oCACc,WAAW,eAAe,EAAE;iBAC/C,IAAI,eAAe,EAAE;;;EAGpC,aAAa,oBAAoB;;;;;;;;;;;;;;;;;;AAkBnC;AAEA,SAAgB,sBACd,UACA,UACmB;CACnB,IAAI;EACF,MAAM,QAAQ,SAAS,MAAM,aAAa;EAC1C,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,SAAS,KAAK,MAAM,MAAM,EAAE;EAKlC,IAAI,CAAC,OAAO,YAAY,CAAC,MAAM,QAAQ,OAAO,QAAQ,GAAG,OAAO;EAChE,OAAO;GACL,UAAU,OAAO;GACjB,iBAAiB,SAAS;GAC1B,KAAK,SAAS;GACd,UAAW,OAAO,SAAoC,KAAK,GAAG,OAAO;IACnE,UAAU,EAAE,YAAY,IAAI;IAC5B,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,GAAI,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,IAAI,CAAC;IAC7C,KAAK,EAAE,OAAO;IACd,UAAU,EAAE,YAAY;IACxB,aAAa,EAAE,eAAe;IAC9B,GAAI,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,IAAI,CAAC;GAC3D,EAAE;GACF,sBAAsB,OAAO,wBAAwB,SAAS;GAC9D,cAAc,SAAS;EACzB;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAIA,eAAsB,WACpB,SACA,OACA,UAKI,CAAC,GACU;CACf,aAAa,MAAM,UAAU,UAAU;CACvC,MAAM,QAAQ,QAAQ,0BAAS,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;CACnE,MAAM,QAAQ,QAAQ,SAAS,SAAS;CASxC,MAAM,WAAW,OANf,QAAQ,iBACN,OAAO,KAAK,SAAS,MAAM;EAC3B,MAAM,EAAE,yBAAyB,MAAM,OAAO;EAC9C,OAAO,qBAAqB,KAAK,SAAS,CAAC;CAC7C,IAE6B,SAAS,WAAW,KAAK;CAExD,MAAM,aADY,cAAc,QACL,EAAE;CAC7B,MAAM,QAAQ,SAAS;CAEvB,MAAM,SAAS,2BAA2B,MAAM,WAAW;CAC3D,MAAM,OAAO,cAAc,MAAM,WAAW;CAC5C,MAAM,SAAS,YAAY,IAAI;CAE/B,MAAM,kBAAkB,uBAAuB,OAAO,QAAQ,YAAY,KAAK;CAE/E,IAAI,gBAAgB;CACpB,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,QAAQ,UAAU,KAAA,GAUpB,gBAAgB,sBAAsB,MADf,MARR,yBACb,MAAM,aACN,QACA,MAAM,UACN,YACA,OACA,KAEgC,CAAC,GACa,eAAe;CAGjE,MAAM,uBAAM,IAAI,KAAK,GAAE,YAAY;CACnC,MAAM,OAAa;EACjB,IAAI,WAAW;EACf,aAAa,MAAM;EACnB;EACA;EACA;EACA,UAAU,MAAM;EAChB;EACA,UAAU;EACV,QAAQ;EACR,WAAW;EACX,WAAW;EACX;CACF;CAEA,MAAM,aAAmD,UAAU,OAAO,IAAI,YAAY;EAExF,OAAO;GAAE,OAAO,CAAC,GADQ,MAAM,QAAQ,SAAS,KAAK,IAAI,QAAQ,QAAQ,CAAC,GAC5C,IAAI;GAAG,4BAAW,IAAI,KAAK,GAAE,YAAY;EAAE;CAC3E,CAAC;CACD,OAAO;AACT;AAIA,SAAgB,eAAe,SAAyB;CACtD,OAAO,UAAU,OAAO,EAAE,QAAQ,MAAM,EAAE,WAAW,QAAQ;AAC/D;AAEA,eAAsB,mBACpB,SACA,QACA,UACsB;CACtB,IAAI,UAAuB;CAC3B,MAAM,aAAmD,UAAU,OAAO,IAAI,YAAY;EACxF,MAAM,QAAgB,MAAM,QAAQ,SAAS,KAAK,IAAI,CAAC,GAAG,QAAQ,KAAK,IAAI,CAAC;EAC5E,MAAM,MAAM,MAAM,WAAW,MAAM,EAAE,OAAO,MAAM;EAClD,IAAI,OAAO,GAAG;GACZ,UAAU;IAAE,GAAG,MAAM;IAAO;IAAU,4BAAW,IAAI,KAAK,GAAE,YAAY;GAAE;GAC1E,MAAM,OAAO;EACf;EACA,OAAO;GAAE;GAAO,4BAAW,IAAI,KAAK,GAAE,YAAY;EAAE;CACtD,CAAC;CACD,OAAO;AACT;AAEA,eAAsB,WAAW,SAAiB,QAAsC;CACtF,IAAI,YAAyB;CAC7B,MAAM,aAAmD,UAAU,OAAO,IAAI,YAAY;EACxF,MAAM,QAAgB,MAAM,QAAQ,SAAS,KAAK,IAAI,CAAC,GAAG,QAAQ,KAAK,IAAI,CAAC;EAC5E,MAAM,MAAM,MAAM,WAAW,MAAM,EAAE,OAAO,MAAM;EAClD,IAAI,OAAO,GAAG;GACZ,YAAY;IACV,GAAG,MAAM;IACT,QAAQ;IACR,4BAAW,IAAI,KAAK,GAAE,YAAY;GACpC;GACA,MAAM,OAAO;EACf;EACA,OAAO;GAAE;GAAO,4BAAW,IAAI,KAAK,GAAE,YAAY;EAAE;CACtD,CAAC;CACD,OAAO;AACT;AASA,eAAsB,6BACpB,SACA,QACqB;CACrB,MAAM,cAAc,eAAe,OAAO;CAC1C,MAAM,eAAe,YAAY,QAAQ,MAAM,EAAE,WAAW,aAAa,EAAE,SAAS,CAAC;CAErF,IAAI,aAAa,WAAW,GAAG,OAAO;EAAE,SAAS,CAAC;EAAG,SAAS,YAAY;CAAO;CAEjF,IAAI,WAAW;CACf,KAAK,MAAM,QAAQ,kBAAkB,OAAO,GAAG;EAC7C,MAAM,QAAQ,MAAM,aAAa,SAAS,IAAI,EAAE,YAAY,CAAC,CAAC;EAC9D,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,UAAU,OAAO,YAAY,KAAK,SAAS;CAExD;CAEA,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,WAAW,KAAK,IAAI,KAAK,KAAK,MAAO,WAAW,KAAK,SAAU,GAAG,CAAC;EAEzE,IAAI,MADiB,mBAAmB,SAAS,KAAK,IAAI,QAAQ,GACtD,QAAQ,KAAK,KAAK,EAAE;CAClC;CAEA,OAAO;EAAE;EAAS,SAAS,YAAY,SAAS,aAAa;CAAO;AACtE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"import-hubspot-BaK71U_K.js","names":[],"sources":["../src/core/csv-stream.ts","../src/fs/contacts-writer.ts","../src/commands/import-hubspot.ts"],"sourcesContent":["import fs from \"fs\";\nimport readline from \"readline\";\n\nexport interface CsvStreamOptions {\n delimiter?: string;\n}\n\nfunction parseCSVLine(line: string, delimiter = \",\"): string[] {\n const result: string[] = [];\n let current = \"\";\n let inQuotes = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!;\n if (ch === '\"') {\n if (inQuotes && line[i + 1] === '\"') {\n current += '\"';\n i++;\n } else {\n inQuotes = !inQuotes;\n }\n } else if (ch === delimiter && !inQuotes) {\n result.push(current.trim());\n current = \"\";\n } else {\n current += ch;\n }\n }\n result.push(current.trim());\n return result;\n}\n\n/** Streaming line-by-line CSV parser — O(1) memory for arbitrarily large files. */\nexport async function* streamCSV(\n filePath: string,\n opts: CsvStreamOptions = {}\n): AsyncGenerator<Record<string, string>> {\n const delimiter = opts.delimiter ?? \",\";\n const stream = fs.createReadStream(filePath, { encoding: \"utf-8\" });\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\n\n let headers: string[] | null = null;\n\n for await (const line of rl) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const values = parseCSVLine(trimmed, delimiter);\n if (!headers) {\n headers = values.map((h) => h.replace(/^\"|\"$/g, \"\").trim());\n continue;\n }\n const row: Record<string, string> = {};\n headers.forEach((h, i) => {\n row[h] = values[i] ?? \"\";\n });\n yield row;\n }\n}\n\n/** Synchronous full-load parser — for small files (<10MB). */\nexport function parseCSVSync(content: string, delimiter = \",\"): Array<Record<string, string>> {\n const lines = content.trim().split(\"\\n\");\n if (lines.length < 2) return [];\n const headers = (lines[0] ?? \"\").split(delimiter).map((h) => h.trim().replace(/^\"|\"$/g, \"\"));\n return lines.slice(1).map((line) => {\n const values = parseCSVLine(line, delimiter);\n const row: Record<string, string> = {};\n headers.forEach((h, i) => {\n row[h] = values[i] ?? \"\";\n });\n return row;\n });\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport { z } from \"zod\";\n\nexport const CustomerContactSchema = z.object({\n email: z.string().email(),\n name: z.string().min(1),\n title: z.string().optional(),\n phone: z.string().optional(),\n department: z.string().optional(),\n linkedinUrl: z.string().url().optional(),\n isPrimary: z.boolean().default(false),\n hubspotId: z.string().optional(),\n hubspotOwnerId: z.string().optional(),\n createdAt: z.string().optional(),\n});\n\nexport type CustomerContact = z.infer<typeof CustomerContactSchema>;\n\nfunction contactsPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug, \"contacts.json\");\n}\n\nexport function listContacts(dataDir: string, slug: string): CustomerContact[] {\n const p = contactsPath(dataDir, slug);\n if (!fs.existsSync(p)) return [];\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n if (!Array.isArray(raw)) return [];\n return raw.flatMap((item) => {\n const r = CustomerContactSchema.safeParse(item);\n return r.success ? [r.data] : [];\n });\n } catch {\n return [];\n }\n}\n\nexport function upsertContact(dataDir: string, slug: string, contact: CustomerContact): void {\n const contacts = listContacts(dataDir, slug);\n const idx = contacts.findIndex((c) => c.email.toLowerCase() === contact.email.toLowerCase());\n if (idx >= 0) {\n contacts[idx] = { ...contacts[idx], ...contact };\n } else {\n contacts.push(contact);\n }\n // Ensure only one primary\n if (contact.isPrimary) {\n for (const c of contacts) {\n if (c.email.toLowerCase() !== contact.email.toLowerCase()) {\n c.isPrimary = false;\n }\n }\n }\n const dir = path.dirname(contactsPath(dataDir, slug));\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(contactsPath(dataDir, slug), JSON.stringify(contacts, null, 2), \"utf-8\");\n}\n\nexport function getPrimaryContact(dataDir: string, slug: string): CustomerContact | null {\n const contacts = listContacts(dataDir, slug);\n return contacts.find((c) => c.isPrimary) ?? contacts[0] ?? null;\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport { createHash } from \"crypto\";\nimport { streamCSV } from \"../core/csv-stream.js\";\nimport { upsertContact } from \"../fs/contacts-writer.js\";\nimport type { PipelineDeal } from \"../schemas/pipeline.js\";\nimport type { InteractionEntry } from \"../schemas/interaction.js\";\n\nexport interface HubSpotImportResult {\n companiesProcessed: number;\n contactsImported: number;\n dealsImported: number;\n engagementsImported: number;\n errors: string[];\n customPropertiesSaved: number;\n ownersResolved: number;\n}\n\nexport interface HubSpotImportOptions {\n dryRun?: boolean;\n ownerMap?: Record<string, string>; // hubspot email → dxcrm actor\n resume?: boolean;\n analyzeOnly?: boolean;\n}\n\nexport interface HubSpotAnalysis {\n companiesFound: number;\n contactsFound: number;\n dealsFound: number;\n engagementsFound: number;\n customPropertiesDetected: string[];\n ownersDetected: string[];\n unknownStages: string[];\n unmappedContacts: number;\n estimatedMinutes: number;\n}\n\n// ─── Stage + Type maps ────────────────────────────────────────────────────────\n\nconst STAGE_MAP: Record<string, PipelineDeal[\"stage\"]> = {\n appointmentscheduled: \"qualified\",\n qualifiedtobuy: \"qualified\",\n presentationscheduled: \"proposal\",\n decisionmakerboughtin: \"negotiation\",\n contractsent: \"negotiation\",\n closedwon: \"won\",\n closedlost: \"lost\",\n // Additional HubSpot stages\n prospecting: \"lead\",\n qualification: \"qualified\",\n proposal: \"proposal\",\n negotiation: \"negotiation\",\n closedwon2: \"won\",\n closedlost2: \"lost\",\n};\n\nconst TYPE_MAP: Record<string, InteractionEntry[\"type\"]> = {\n NOTE: \"Note\",\n CALL: \"Call\",\n EMAIL: \"Email\",\n MEETING: \"Meeting\",\n TASK: \"Note\",\n LINKEDIN_MESSAGE: \"Email\",\n WHATSAPP_MESSAGE: \"Email\",\n POSTAL_MAIL: \"Note\",\n};\n\n// Known HubSpot columns → dxcrm main_facts fields\nconst COMPANY_FIELD_MAP: Record<string, string> = {\n hs_annual_revenue: \"annual_revenue\",\n num_associated_contacts: \"contact_count\",\n industry: \"industry\",\n city: \"city\",\n country: \"country\",\n hs_lead_status: \"lead_status\",\n lifecyclestage: \"lifecycle_stage\",\n numberofemployees: \"employee_count\",\n phone: \"phone\",\n address: \"address\",\n zip: \"zip\",\n state: \"state\",\n};\n\nconst KNOWN_COMPANY_COLUMNS = new Set([\n \"name\",\n \"Name\",\n \"domain\",\n \"Domain\",\n \"website\",\n \"Website\",\n \"phone\",\n \"Phone\",\n \"address\",\n \"Address\",\n \"city\",\n \"City\",\n \"country\",\n \"Country\",\n \"state\",\n \"State\",\n \"zip\",\n \"Zip\",\n \"industry\",\n \"Industry\",\n \"numberofemployees\",\n \"Number of Employees\",\n \"hs_annual_revenue\",\n \"Annual Revenue\",\n \"lifecyclestage\",\n \"Lifecycle Stage\",\n \"hubspot_owner_email\",\n \"HubSpot Owner Email\",\n \"create_date\",\n \"createdate\",\n \"hs_lastmodifieddate\",\n \"hs_object_id\",\n \"Record ID\",\n]);\n\n// ─── Utilities ────────────────────────────────────────────────────────────────\n\nfunction slugify(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60);\n}\n\nfunction hashStr(s: string): string {\n return createHash(\"sha256\").update(s).digest(\"hex\").slice(0, 16);\n}\n\nfunction coerceDate(raw: string): string {\n if (!raw) return new Date().toISOString().slice(0, 10);\n // HubSpot timestamps: \"2026-01-15 14:30:00 UTC\", \"1705318200000\" (ms), \"2026-01-15\"\n if (/^\\d{13}$/.test(raw.trim())) {\n return new Date(parseInt(raw, 10)).toISOString().slice(0, 10);\n }\n const d = new Date(raw.trim());\n if (!isNaN(d.getTime())) return d.toISOString().slice(0, 10);\n return new Date().toISOString().slice(0, 10);\n}\n\n// ─── Customer creation ────────────────────────────────────────────────────────\n\nfunction ensureCustomer(\n dataDir: string,\n name: string,\n domain: string,\n email: string,\n dryRun: boolean\n): { slug: string; created: boolean } {\n const slug = slugify(name || \"unknown\");\n const customerDir = path.join(dataDir, \"customers\", slug);\n const mainFactsPath = path.join(customerDir, \"main_facts.md\");\n if (fs.existsSync(mainFactsPath)) return { slug, created: false };\n if (dryRun) return { slug, created: true };\n\n fs.mkdirSync(customerDir, { recursive: true });\n const today = new Date().toISOString().slice(0, 10);\n const lines = [\n \"---\",\n `name: ${name}`,\n domain ? `domain: ${domain}` : null,\n email ? `email: ${email}` : null,\n \"relationship_stage: prospect\",\n `created: ${today}`,\n `updated: ${today}`,\n `last_touchpoint: ${today}`,\n \"tags: []\",\n \"currency: EUR\",\n \"---\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n fs.writeFileSync(mainFactsPath, `${lines}\\n\\n# Customer: ${name}\\n`, \"utf-8\");\n fs.writeFileSync(\n path.join(customerDir, \"interactions.md\"),\n `# Interactions — ${name}\\n\\n`,\n \"utf-8\"\n );\n fs.writeFileSync(path.join(customerDir, \"pipeline.md\"), `# Pipeline — ${name}\\n\\n`, \"utf-8\");\n fs.writeFileSync(\n path.join(customerDir, \"sources.json\"),\n JSON.stringify(\n {\n gmail: {\n query: domain\n ? `from:${domain} OR to:${domain}`\n : email\n ? `from:${email} OR to:${email}`\n : \"\",\n enabled: true,\n },\n transcripts: { paths: [], extensions: [\".txt\", \".vtt\"], enabled: false },\n },\n null,\n 2\n ),\n \"utf-8\"\n );\n return { slug, created: true };\n}\n\nfunction readMainFactsRaw(dataDir: string, slug: string): string {\n const p = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n return fs.existsSync(p) ? (fs.readFileSync(p, \"utf-8\") as string) : \"\";\n}\n\nfunction updateMainFactsField(dataDir: string, slug: string, field: string, value: string): void {\n const p = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n if (!fs.existsSync(p)) return;\n let content = fs.readFileSync(p, \"utf-8\") as string;\n const regex = new RegExp(`^${field}:.*$`, \"m\");\n if (regex.test(content)) {\n content = content.replace(regex, `${field}: ${value}`);\n } else {\n const firstDash = content.indexOf(\"---\");\n const secondDash = content.indexOf(\"---\", firstDash + 3);\n if (secondDash >= 0) {\n content = content.slice(0, secondDash) + `${field}: ${value}\\n` + content.slice(secondDash);\n }\n }\n fs.writeFileSync(p, content, \"utf-8\");\n}\n\n// ─── Custom Properties ────────────────────────────────────────────────────────\n\nfunction saveCustomProperties(dataDir: string, slug: string, props: Record<string, string>): void {\n if (Object.keys(props).length === 0) return;\n const p = path.join(dataDir, \"customers\", slug, \"custom_properties.json\");\n let existing: Record<string, unknown> = {};\n if (fs.existsSync(p)) {\n try {\n existing = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Record<string, unknown>;\n } catch {\n existing = {};\n }\n }\n const merged = {\n source: \"hubspot-import\",\n importedAt: new Date().toISOString(),\n properties: { ...((existing[\"properties\"] as Record<string, string>) ?? {}), ...props },\n };\n fs.writeFileSync(p, JSON.stringify(merged, null, 2), \"utf-8\");\n}\n\n// ─── Progress / Resume ────────────────────────────────────────────────────────\n\ninterface ImportProgress {\n importId: string;\n source: string;\n startedAt: string;\n phases: {\n companies: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n contacts: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n deals: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n engagements: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n };\n}\n\nfunction progressPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"import-progress.json\");\n}\n\nfunction readProgress(dataDir: string): ImportProgress | null {\n const p = progressPath(dataDir);\n if (!fs.existsSync(p)) return null;\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as ImportProgress;\n } catch {\n return null;\n }\n}\n\nfunction writeProgress(dataDir: string, progress: ImportProgress): void {\n fs.mkdirSync(path.dirname(progressPath(dataDir)), { recursive: true });\n fs.writeFileSync(progressPath(dataDir), JSON.stringify(progress, null, 2), \"utf-8\");\n}\n\nfunction clearProgress(dataDir: string): void {\n const p = progressPath(dataDir);\n if (fs.existsSync(p)) fs.unlinkSync(p);\n}\n\n// ─── Analyze ──────────────────────────────────────────────────────────────────\n\nexport async function analyzeHubSpotExport(exportDir: string): Promise<HubSpotAnalysis> {\n const analysis: HubSpotAnalysis = {\n companiesFound: 0,\n contactsFound: 0,\n dealsFound: 0,\n engagementsFound: 0,\n customPropertiesDetected: [],\n ownersDetected: [],\n unknownStages: [],\n unmappedContacts: 0,\n estimatedMinutes: 0,\n };\n\n const customProps = new Set<string>();\n const owners = new Set<string>();\n const unknownStages = new Set<string>();\n const companyNames = new Set<string>();\n\n // Companies\n const companiesPath = path.join(exportDir, \"companies.csv\");\n if (fs.existsSync(companiesPath)) {\n for await (const row of streamCSV(companiesPath)) {\n analysis.companiesFound++;\n const name = (row[\"name\"] ?? row[\"Name\"] ?? \"\").trim();\n if (name) companyNames.add(name.toLowerCase());\n const owner = row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\";\n if (owner) owners.add(owner);\n // Detect custom columns\n for (const key of Object.keys(row)) {\n if (!KNOWN_COMPANY_COLUMNS.has(key) && row[key]) customProps.add(key);\n }\n }\n }\n\n // Contacts\n const contactsPath = path.join(exportDir, \"contacts.csv\");\n if (fs.existsSync(contactsPath)) {\n for await (const row of streamCSV(contactsPath)) {\n analysis.contactsFound++;\n const company = (row[\"company\"] ?? row[\"Company\"] ?? row[\"associated_company\"] ?? \"\").trim();\n if (company && !companyNames.has(company.toLowerCase())) analysis.unmappedContacts++;\n const owner = row[\"contact_owner\"] ?? row[\"Contact Owner\"] ?? \"\";\n if (owner) owners.add(owner);\n }\n }\n\n // Deals\n const dealsPath = path.join(exportDir, \"deals.csv\");\n if (fs.existsSync(dealsPath)) {\n for await (const row of streamCSV(dealsPath)) {\n analysis.dealsFound++;\n const stage = (row[\"dealstage\"] ?? row[\"Deal Stage\"] ?? \"\").trim().toLowerCase();\n if (stage && !STAGE_MAP[stage]) unknownStages.add(stage);\n }\n }\n\n // Engagements\n const engagementsPath = path.join(exportDir, \"engagements.csv\");\n if (fs.existsSync(engagementsPath)) {\n for await (const _row of streamCSV(engagementsPath)) {\n analysis.engagementsFound++;\n }\n }\n\n analysis.customPropertiesDetected = Array.from(customProps).slice(0, 50);\n analysis.ownersDetected = Array.from(owners);\n analysis.unknownStages = Array.from(unknownStages);\n\n const totalRows =\n analysis.companiesFound +\n analysis.contactsFound +\n analysis.dealsFound +\n analysis.engagementsFound;\n analysis.estimatedMinutes = Math.ceil(totalRows / 2000); // ~2000 rows/min\n\n return analysis;\n}\n\n// ─── Main Import ──────────────────────────────────────────────────────────────\n\nexport async function runHubSpotCsvImport(\n exportDir: string,\n dataDir: string,\n opts: HubSpotImportOptions = {}\n): Promise<HubSpotImportResult> {\n const result: HubSpotImportResult = {\n companiesProcessed: 0,\n contactsImported: 0,\n dealsImported: 0,\n engagementsImported: 0,\n errors: [],\n customPropertiesSaved: 0,\n ownersResolved: 0,\n };\n\n const dryRun = opts.dryRun ?? false;\n const ownerMap = opts.ownerMap ?? {};\n\n // Resume handling\n let progress: ImportProgress | null = null;\n if (opts.resume) {\n progress = readProgress(dataDir);\n if (progress) {\n console.error(`[import] Resuming import ${progress.importId}...`);\n }\n }\n\n if (!progress) {\n progress = {\n importId: `hs-import-${new Date().toISOString().replace(/[:.]/g, \"-\").slice(0, 19)}`,\n source: exportDir,\n startedAt: new Date().toISOString(),\n phases: {\n companies: { status: \"pending\", processed: 0 },\n contacts: { status: \"pending\", processed: 0 },\n deals: { status: \"pending\", processed: 0 },\n engagements: { status: \"pending\", processed: 0 },\n },\n };\n }\n\n const companySlugMap = new Map<string, string>(); // name.lower → slug\n const emailSlugMap = new Map<string, string>(); // email.lower → slug\n\n // ── Phase 1: Companies ──────────────────────────────────────────────────────\n const companiesPath = path.join(exportDir, \"companies.csv\");\n if (fs.existsSync(companiesPath) && progress.phases.companies.status !== \"done\") {\n progress.phases.companies.status = \"in-progress\";\n if (!dryRun) writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(companiesPath)) {\n const name = (row[\"name\"] ?? row[\"Name\"] ?? \"\").trim();\n if (!name) continue;\n\n const domain = (\n row[\"domain\"] ??\n row[\"Domain\"] ??\n row[\"website\"] ??\n row[\"Website\"] ??\n \"\"\n ).trim();\n const hubspotId = (row[\"hs_object_id\"] ?? row[\"Record ID\"] ?? \"\").trim();\n\n try {\n const { slug, created } = ensureCustomer(dataDir, name, domain, \"\", dryRun);\n companySlugMap.set(name.toLowerCase(), slug);\n result.companiesProcessed++;\n\n if (!dryRun && created) {\n // Map known fields\n for (const [hsKey, dxKey] of Object.entries(COMPANY_FIELD_MAP)) {\n const val = row[hsKey] ?? \"\";\n if (val) updateMainFactsField(dataDir, slug, dxKey, val);\n }\n\n // Owner mapping\n const ownerEmail = row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\";\n if (ownerEmail && ownerMap[ownerEmail]) {\n updateMainFactsField(dataDir, slug, \"assigned_rep\", ownerMap[ownerEmail]!);\n result.ownersResolved++;\n }\n\n // HubSpot ID reference\n if (hubspotId) {\n updateMainFactsField(dataDir, slug, \"hubspot_company_id\", hubspotId);\n }\n\n // Custom properties — everything not in known columns\n const customProps: Record<string, string> = {};\n for (const [key, val] of Object.entries(row)) {\n if (!KNOWN_COMPANY_COLUMNS.has(key) && val) customProps[key] = val;\n }\n if (Object.keys(customProps).length > 0) {\n saveCustomProperties(dataDir, slug, customProps);\n result.customPropertiesSaved += Object.keys(customProps).length;\n }\n }\n } catch (err) {\n result.errors.push(`Company '${name}': ${(err as Error).message}`);\n }\n\n progress.phases.companies.processed++;\n }\n\n progress.phases.companies.status = \"done\";\n if (!dryRun) writeProgress(dataDir, progress);\n } else if (progress.phases.companies.status === \"done\") {\n // Rebuild maps from disk for resume\n const customersDir = path.join(dataDir, \"customers\");\n if (fs.existsSync(customersDir)) {\n for (const slug of fs.readdirSync(customersDir)) {\n const mf = path.join(customersDir, slug, \"main_facts.md\");\n if (!fs.existsSync(mf)) continue;\n const content = fs.readFileSync(mf, \"utf-8\") as string;\n const nameMatch = content.match(/^name:\\s*(.+)$/m);\n if (nameMatch?.[1]) companySlugMap.set(nameMatch[1].trim().toLowerCase(), slug);\n }\n }\n }\n\n // ── Phase 2: Contacts ───────────────────────────────────────────────────────\n const contactsPath = path.join(exportDir, \"contacts.csv\");\n if (fs.existsSync(contactsPath) && progress.phases.contacts.status !== \"done\") {\n progress.phases.contacts.status = \"in-progress\";\n if (!dryRun) writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(contactsPath)) {\n const firstName = (row[\"firstname\"] ?? row[\"First Name\"] ?? \"\").trim();\n const lastName = (row[\"lastname\"] ?? row[\"Last Name\"] ?? \"\").trim();\n const email = (row[\"email\"] ?? row[\"Email\"] ?? \"\").trim();\n const companyName = (\n row[\"company\"] ??\n row[\"Company\"] ??\n row[\"associated_company\"] ??\n row[\"Associated Company\"] ??\n \"\"\n ).trim();\n const phone = (row[\"phone\"] ?? row[\"Phone\"] ?? row[\"mobilephone\"] ?? \"\").trim();\n const title = (row[\"jobtitle\"] ?? row[\"Job Title\"] ?? \"\").trim();\n const department = (row[\"department\"] ?? row[\"Department\"] ?? \"\").trim();\n const hubspotId = (row[\"vid\"] ?? row[\"Contact ID\"] ?? row[\"hs_object_id\"] ?? \"\").trim();\n\n let slug = companySlugMap.get(companyName.toLowerCase());\n\n if (!slug && companyName) {\n const domain = (row[\"website\"] ?? \"\").trim();\n try {\n const { slug: newSlug, created } = ensureCustomer(\n dataDir,\n companyName,\n domain,\n email,\n dryRun\n );\n slug = newSlug;\n companySlugMap.set(companyName.toLowerCase(), newSlug);\n if (created) result.companiesProcessed++;\n } catch (err) {\n result.errors.push(`Auto-company '${companyName}': ${(err as Error).message}`);\n }\n }\n\n if (!slug) continue;\n\n if (!dryRun) {\n const contactName = [firstName, lastName].filter(Boolean).join(\" \");\n const isFirst = !fs.existsSync(path.join(dataDir, \"customers\", slug, \"contacts.json\"));\n\n // Multi-contact support\n if (email || contactName) {\n const contactEntry = {\n email: email || `${slugify(contactName)}@unknown.local`,\n name: contactName || email,\n ...(title ? { title } : {}),\n ...(phone ? { phone } : {}),\n ...(department ? { department } : {}),\n ...(hubspotId ? { hubspotId } : {}),\n isPrimary: isFirst,\n createdAt: new Date().toISOString(),\n };\n try {\n upsertContact(dataDir, slug, contactEntry);\n } catch {\n /* skip invalid */\n }\n }\n\n // Update main_facts primary contact (first contact only)\n const existing = readMainFactsRaw(dataDir, slug);\n if (email && !existing.includes(\"email:\"))\n updateMainFactsField(dataDir, slug, \"email\", email);\n if (phone && !existing.includes(\"phone:\"))\n updateMainFactsField(dataDir, slug, \"phone\", phone);\n if (contactName && !existing.includes(\"primary_contact:\")) {\n updateMainFactsField(dataDir, slug, \"primary_contact\", contactName);\n }\n\n // Owner mapping\n const ownerEmail = row[\"contact_owner\"] ?? row[\"Contact Owner\"] ?? \"\";\n if (ownerEmail && ownerMap[ownerEmail] && !existing.includes(\"assigned_rep:\")) {\n updateMainFactsField(dataDir, slug, \"assigned_rep\", ownerMap[ownerEmail]!);\n result.ownersResolved++;\n }\n }\n\n if (email) emailSlugMap.set(email.toLowerCase(), slug);\n result.contactsImported++;\n progress.phases.contacts.processed++;\n }\n\n progress.phases.contacts.status = \"done\";\n if (!dryRun) writeProgress(dataDir, progress);\n }\n\n // ── Phase 3: Deals ──────────────────────────────────────────────────────────\n const dealsPath = path.join(exportDir, \"deals.csv\");\n if (fs.existsSync(dealsPath) && progress.phases.deals.status !== \"done\") {\n if (!dryRun) {\n const { upsertDeal } = await import(\"../fs/pipeline-writer.js\");\n progress.phases.deals.status = \"in-progress\";\n writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(dealsPath)) {\n const dealName = (row[\"dealname\"] ?? row[\"Deal Name\"] ?? row[\"name\"] ?? \"\").trim();\n if (!dealName) continue;\n\n const companyName = (\n row[\"associated_company\"] ??\n row[\"Associated Company\"] ??\n row[\"company\"] ??\n \"\"\n ).trim();\n const amountStr = (row[\"amount\"] ?? row[\"Amount\"] ?? \"0\").trim().replace(/[^0-9.]/g, \"\");\n const stageRaw = (row[\"dealstage\"] ?? row[\"Deal Stage\"] ?? \"\").trim().toLowerCase();\n const closeDateRaw = (\n row[\"closedate\"] ??\n row[\"Close Date\"] ??\n row[\"close_date\"] ??\n \"\"\n ).trim();\n const currency = (row[\"deal_currency_code\"] ?? row[\"Currency\"] ?? \"EUR\").trim();\n const dealId = (row[\"hs_deal_id\"] ?? row[\"hs_object_id\"] ?? row[\"Record ID\"] ?? \"\").trim();\n const ownerEmail = (row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\").trim();\n const description = (row[\"description\"] ?? row[\"Description\"] ?? \"\").trim();\n\n const slug =\n companySlugMap.get(companyName.toLowerCase()) ?? slugify(companyName || \"unknown\");\n const stage = STAGE_MAP[stageRaw] ?? \"qualified\";\n const amount = parseFloat(amountStr) || 0;\n const closeDate = coerceDate(closeDateRaw);\n\n const notesParts: string[] = [];\n if (dealId) notesParts.push(`hubspot://deal/${dealId}`);\n if (description) notesParts.push(description.slice(0, 200));\n if (ownerEmail && ownerMap[ownerEmail]) notesParts.push(`owner:${ownerMap[ownerEmail]}`);\n\n const deal: PipelineDeal = {\n name: dealName,\n stage,\n value: amount,\n currency: currency || \"EUR\",\n probability: stage === \"won\" ? 1 : stage === \"lost\" ? 0 : 0.5,\n close_date: closeDate,\n updated: new Date().toISOString().slice(0, 10),\n ...(notesParts.length > 0 ? { notes: notesParts.join(\" | \") } : {}),\n };\n\n try {\n await upsertDeal(dataDir, slug, deal);\n result.dealsImported++;\n } catch (err) {\n result.errors.push(`Deal '${dealName}': ${(err as Error).message}`);\n }\n\n progress.phases.deals.processed++;\n }\n\n progress.phases.deals.status = \"done\";\n writeProgress(dataDir, progress);\n } else {\n // Dry run count\n for await (const row of streamCSV(dealsPath)) {\n if ((row[\"dealname\"] ?? row[\"name\"] ?? \"\").trim()) result.dealsImported++;\n }\n progress.phases.deals.status = \"done\";\n }\n }\n\n // ── Phase 4: Engagements ────────────────────────────────────────────────────\n const engagementsPath = path.join(exportDir, \"engagements.csv\");\n if (fs.existsSync(engagementsPath) && progress.phases.engagements.status !== \"done\") {\n if (!dryRun) {\n const { appendInteraction, readInteractions } = await import(\"../fs/interactions-writer.js\");\n progress.phases.engagements.status = \"in-progress\";\n writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(engagementsPath)) {\n const engType = (\n row[\"engagement_type\"] ??\n row[\"Engagement Type\"] ??\n row[\"type\"] ??\n row[\"Type\"] ??\n \"NOTE\"\n )\n .trim()\n .toUpperCase();\n const timestamp = (\n row[\"hs_timestamp\"] ??\n row[\"Timestamp\"] ??\n row[\"date\"] ??\n row[\"createdate\"] ??\n \"\"\n ).trim();\n const body = (\n row[\"hs_body_preview\"] ??\n row[\"Body\"] ??\n row[\"notes\"] ??\n row[\"Notes\"] ??\n row[\"hs_note_body\"] ??\n \"\"\n ).trim();\n const subject = (row[\"subject\"] ?? row[\"Subject\"] ?? \"\").trim();\n const contactEmail = (\n row[\"associated_contact_email\"] ??\n row[\"Contact Email\"] ??\n row[\"from_email\"] ??\n \"\"\n )\n .trim()\n .toLowerCase();\n const engId = (\n row[\"id\"] ??\n row[\"engagement_id\"] ??\n row[\"hs_object_id\"] ??\n hashStr(timestamp + body)\n ).trim();\n const callDuration = (row[\"call_duration\"] ?? row[\"hs_call_duration\"] ?? \"\").trim();\n const callOutcome = (row[\"call_outcome\"] ?? row[\"hs_call_disposition\"] ?? \"\").trim();\n const callRecording = (\n row[\"call_recording_url\"] ??\n row[\"hs_call_recording_url\"] ??\n \"\"\n ).trim();\n\n const slug =\n emailSlugMap.get(contactEmail) ??\n companySlugMap.get((row[\"associated_company\"] ?? \"\").toLowerCase().trim());\n if (!slug) continue;\n\n const sourceRef = `hubspot://engagement/${engId}`;\n try {\n const existing = await readInteractions(dataDir, slug).catch(() => \"\");\n if (existing.includes(sourceRef)) continue;\n\n const date = coerceDate(timestamp);\n const type = TYPE_MAP[engType] ?? \"Note\";\n\n // Build rich summary\n const summaryParts: string[] = [];\n if (subject) summaryParts.push(`Subject: ${subject}`);\n if (body) summaryParts.push(body.slice(0, 500));\n if (callDuration) summaryParts.push(`Duration: ${callDuration}s`);\n if (callOutcome) summaryParts.push(`Outcome: ${callOutcome}`);\n if (callRecording) summaryParts.push(`Recording: ${callRecording}`);\n\n const summary = summaryParts.join(\" | \") || `${type} imported from HubSpot`;\n\n await appendInteraction(dataDir, slug, {\n date,\n type,\n with: contactEmail || slug,\n summary,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n result.engagementsImported++;\n } catch (err) {\n result.errors.push(`Engagement ${engId}: ${(err as Error).message}`);\n }\n\n progress.phases.engagements.processed++;\n }\n\n progress.phases.engagements.status = \"done\";\n writeProgress(dataDir, progress);\n } else {\n for await (const _row of streamCSV(engagementsPath)) result.engagementsImported++;\n progress.phases.engagements.status = \"done\";\n }\n }\n\n // Done — clear progress file\n if (!dryRun) clearProgress(dataDir);\n\n return result;\n}\n"],"mappings":";;;;;;AAOA,SAAS,aAAa,MAAc,YAAY,KAAe;CAC7D,MAAM,SAAmB,CAAC;CAC1B,IAAI,UAAU;CACd,IAAI,WAAW;CACf,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,KAAK,KAAK;EAChB,IAAI,OAAO,MACT,IAAI,YAAY,KAAK,IAAI,OAAO,MAAK;GACnC,WAAW;GACX;EACF,OACE,WAAW,CAAC;OAET,IAAI,OAAO,aAAa,CAAC,UAAU;GACxC,OAAO,KAAK,QAAQ,KAAK,CAAC;GAC1B,UAAU;EACZ,OACE,WAAW;CAEf;CACA,OAAO,KAAK,QAAQ,KAAK,CAAC;CAC1B,OAAO;AACT;;AAGA,gBAAuB,UACrB,UACA,OAAyB,CAAC,GACc;CACxC,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,SAAS,GAAG,iBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;CAClE,MAAM,KAAK,SAAS,gBAAgB;EAAE,OAAO;EAAQ,WAAW;CAAS,CAAC;CAE1E,IAAI,UAA2B;CAE/B,WAAW,MAAM,QAAQ,IAAI;EAC3B,MAAM,UAAU,KAAK,KAAK;EAC1B,IAAI,CAAC,SAAS;EACd,MAAM,SAAS,aAAa,SAAS,SAAS;EAC9C,IAAI,CAAC,SAAS;GACZ,UAAU,OAAO,KAAK,MAAM,EAAE,QAAQ,UAAU,EAAE,EAAE,KAAK,CAAC;GAC1D;EACF;EACA,MAAM,MAA8B,CAAC;EACrC,QAAQ,SAAS,GAAG,MAAM;GACxB,IAAI,KAAK,OAAO,MAAM;EACxB,CAAC;EACD,MAAM;CACR;AACF;;;ACpDA,MAAa,wBAAwB,EAAE,OAAO;CAC5C,OAAO,EAAE,OAAO,EAAE,MAAM;CACxB,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;CACtB,OAAO,EAAE,OAAO,EAAE,SAAS;CAC3B,OAAO,EAAE,OAAO,EAAE,SAAS;CAC3B,YAAY,EAAE,OAAO,EAAE,SAAS;CAChC,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;CACvC,WAAW,EAAE,QAAQ,EAAE,QAAQ,KAAK;CACpC,WAAW,EAAE,OAAO,EAAE,SAAS;CAC/B,gBAAgB,EAAE,OAAO,EAAE,SAAS;CACpC,WAAW,EAAE,OAAO,EAAE,SAAS;AACjC,CAAC;AAID,SAAS,aAAa,SAAiB,MAAsB;CAC3D,OAAO,KAAK,KAAK,SAAS,aAAa,MAAM,eAAe;AAC9D;AAEA,SAAgB,aAAa,SAAiB,MAAiC;CAC7E,MAAM,IAAI,aAAa,SAAS,IAAI;CACpC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,MAAM,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAC5D,IAAI,CAAC,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC;EACjC,OAAO,IAAI,SAAS,SAAS;GAC3B,MAAM,IAAI,sBAAsB,UAAU,IAAI;GAC9C,OAAO,EAAE,UAAU,CAAC,EAAE,IAAI,IAAI,CAAC;EACjC,CAAC;CACH,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAgB,cAAc,SAAiB,MAAc,SAAgC;CAC3F,MAAM,WAAW,aAAa,SAAS,IAAI;CAC3C,MAAM,MAAM,SAAS,WAAW,MAAM,EAAE,MAAM,YAAY,MAAM,QAAQ,MAAM,YAAY,CAAC;CAC3F,IAAI,OAAO,GACT,SAAS,OAAO;EAAE,GAAG,SAAS;EAAM,GAAG;CAAQ;MAE/C,SAAS,KAAK,OAAO;CAGvB,IAAI,QAAQ;OACL,MAAM,KAAK,UACd,IAAI,EAAE,MAAM,YAAY,MAAM,QAAQ,MAAM,YAAY,GACtD,EAAE,YAAY;CAAA;CAIpB,MAAM,MAAM,KAAK,QAAQ,aAAa,SAAS,IAAI,CAAC;CACpD,GAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;CACrC,GAAG,cAAc,aAAa,SAAS,IAAI,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AAC1F;;;AClBA,MAAM,YAAmD;CACvD,sBAAsB;CACtB,gBAAgB;CAChB,uBAAuB;CACvB,uBAAuB;CACvB,cAAc;CACd,WAAW;CACX,YAAY;CAEZ,aAAa;CACb,eAAe;CACf,UAAU;CACV,aAAa;CACb,YAAY;CACZ,aAAa;AACf;AAEA,MAAM,WAAqD;CACzD,MAAM;CACN,MAAM;CACN,OAAO;CACP,SAAS;CACT,MAAM;CACN,kBAAkB;CAClB,kBAAkB;CAClB,aAAa;AACf;AAGA,MAAM,oBAA4C;CAChD,mBAAmB;CACnB,yBAAyB;CACzB,UAAU;CACV,MAAM;CACN,SAAS;CACT,gBAAgB;CAChB,gBAAgB;CAChB,mBAAmB;CACnB,OAAO;CACP,SAAS;CACT,KAAK;CACL,OAAO;AACT;AAEA,MAAM,wBAAwB,IAAI,IAAI;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAID,SAAS,QAAQ,MAAsB;CACrC,OAAO,KACJ,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE;AAChB;AAEA,SAAS,QAAQ,GAAmB;CAClC,OAAO,WAAW,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACjE;AAEA,SAAS,WAAW,KAAqB;CACvC,IAAI,CAAC,KAAK,wBAAO,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;CAErD,IAAI,WAAW,KAAK,IAAI,KAAK,CAAC,GAC5B,OAAO,IAAI,KAAK,SAAS,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;CAE9D,MAAM,IAAI,IAAI,KAAK,IAAI,KAAK,CAAC;CAC7B,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;CAC3D,wBAAO,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC7C;AAIA,SAAS,eACP,SACA,MACA,QACA,OACA,QACoC;CACpC,MAAM,OAAO,QAAQ,QAAQ,SAAS;CACtC,MAAM,cAAc,KAAK,KAAK,SAAS,aAAa,IAAI;CACxD,MAAM,gBAAgB,KAAK,KAAK,aAAa,eAAe;CAC5D,IAAI,GAAG,WAAW,aAAa,GAAG,OAAO;EAAE;EAAM,SAAS;CAAM;CAChE,IAAI,QAAQ,OAAO;EAAE;EAAM,SAAS;CAAK;CAEzC,GAAG,UAAU,aAAa,EAAE,WAAW,KAAK,CAAC;CAC7C,MAAM,yBAAQ,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;CAClD,MAAM,QAAQ;EACZ;EACA,SAAS;EACT,SAAS,WAAW,WAAW;EAC/B,QAAQ,UAAU,UAAU;EAC5B;EACA,YAAY;EACZ,YAAY;EACZ,oBAAoB;EACpB;EACA;EACA;CACF,EACG,OAAO,OAAO,EACd,KAAK,IAAI;CACZ,GAAG,cAAc,eAAe,GAAG,MAAM,kBAAkB,KAAK,KAAK,OAAO;CAC5E,GAAG,cACD,KAAK,KAAK,aAAa,iBAAiB,GACxC,oBAAoB,KAAK,OACzB,OACF;CACA,GAAG,cAAc,KAAK,KAAK,aAAa,aAAa,GAAG,gBAAgB,KAAK,OAAO,OAAO;CAC3F,GAAG,cACD,KAAK,KAAK,aAAa,cAAc,GACrC,KAAK,UACH;EACE,OAAO;GACL,OAAO,SACH,QAAQ,OAAO,SAAS,WACxB,QACE,QAAQ,MAAM,SAAS,UACvB;GACN,SAAS;EACX;EACA,aAAa;GAAE,OAAO,CAAC;GAAG,YAAY,CAAC,QAAQ,MAAM;GAAG,SAAS;EAAM;CACzE,GACA,MACA,CACF,GACA,OACF;CACA,OAAO;EAAE;EAAM,SAAS;CAAK;AAC/B;AAEA,SAAS,iBAAiB,SAAiB,MAAsB;CAC/D,MAAM,IAAI,KAAK,KAAK,SAAS,aAAa,MAAM,eAAe;CAC/D,OAAO,GAAG,WAAW,CAAC,IAAK,GAAG,aAAa,GAAG,OAAO,IAAe;AACtE;AAEA,SAAS,qBAAqB,SAAiB,MAAc,OAAe,OAAqB;CAC/F,MAAM,IAAI,KAAK,KAAK,SAAS,aAAa,MAAM,eAAe;CAC/D,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG;CACvB,IAAI,UAAU,GAAG,aAAa,GAAG,OAAO;CACxC,MAAM,QAAQ,IAAI,OAAO,IAAI,MAAM,OAAO,GAAG;CAC7C,IAAI,MAAM,KAAK,OAAO,GACpB,UAAU,QAAQ,QAAQ,OAAO,GAAG,MAAM,IAAI,OAAO;MAChD;EACL,MAAM,YAAY,QAAQ,QAAQ,KAAK;EACvC,MAAM,aAAa,QAAQ,QAAQ,OAAO,YAAY,CAAC;EACvD,IAAI,cAAc,GAChB,UAAU,QAAQ,MAAM,GAAG,UAAU,IAAI,GAAG,MAAM,IAAI,MAAM,MAAM,QAAQ,MAAM,UAAU;CAE9F;CACA,GAAG,cAAc,GAAG,SAAS,OAAO;AACtC;AAIA,SAAS,qBAAqB,SAAiB,MAAc,OAAqC;CAChG,IAAI,OAAO,KAAK,KAAK,EAAE,WAAW,GAAG;CACrC,MAAM,IAAI,KAAK,KAAK,SAAS,aAAa,MAAM,wBAAwB;CACxE,IAAI,WAAoC,CAAC;CACzC,IAAI,GAAG,WAAW,CAAC,GACjB,IAAI;EACF,WAAW,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;CAC7D,QAAQ;EACN,WAAW,CAAC;CACd;CAEF,MAAM,SAAS;EACb,QAAQ;EACR,6BAAY,IAAI,KAAK,GAAE,YAAY;EACnC,YAAY;GAAE,GAAK,SAAS,iBAA4C,CAAC;GAAI,GAAG;EAAM;CACxF;CACA,GAAG,cAAc,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;AAC9D;AAgBA,SAAS,aAAa,SAAyB;CAC7C,OAAO,KAAK,KAAK,SAAS,YAAY,sBAAsB;AAC9D;AAEA,SAAS,aAAa,SAAwC;CAC5D,MAAM,IAAI,aAAa,OAAO;CAC9B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO;CAC9B,IAAI;EACF,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;CACzD,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,SAAiB,UAAgC;CACtE,GAAG,UAAU,KAAK,QAAQ,aAAa,OAAO,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACrE,GAAG,cAAc,aAAa,OAAO,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AACpF;AAEA,SAAS,cAAc,SAAuB;CAC5C,MAAM,IAAI,aAAa,OAAO;CAC9B,IAAI,GAAG,WAAW,CAAC,GAAG,GAAG,WAAW,CAAC;AACvC;AAIA,eAAsB,qBAAqB,WAA6C;CACtF,MAAM,WAA4B;EAChC,gBAAgB;EAChB,eAAe;EACf,YAAY;EACZ,kBAAkB;EAClB,0BAA0B,CAAC;EAC3B,gBAAgB,CAAC;EACjB,eAAe,CAAC;EAChB,kBAAkB;EAClB,kBAAkB;CACpB;CAEA,MAAM,8BAAc,IAAI,IAAY;CACpC,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,gCAAgB,IAAI,IAAY;CACtC,MAAM,+BAAe,IAAI,IAAY;CAGrC,MAAM,gBAAgB,KAAK,KAAK,WAAW,eAAe;CAC1D,IAAI,GAAG,WAAW,aAAa,GAC7B,WAAW,MAAM,OAAO,UAAU,aAAa,GAAG;EAChD,SAAS;EACT,MAAM,QAAQ,IAAI,WAAW,IAAI,WAAW,IAAI,KAAK;EACrD,IAAI,MAAM,aAAa,IAAI,KAAK,YAAY,CAAC;EAC7C,MAAM,QAAQ,IAAI,0BAA0B,IAAI,0BAA0B;EAC1E,IAAI,OAAO,OAAO,IAAI,KAAK;EAE3B,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,GAC/B,IAAI,CAAC,sBAAsB,IAAI,GAAG,KAAK,IAAI,MAAM,YAAY,IAAI,GAAG;CAExE;CAIF,MAAM,eAAe,KAAK,KAAK,WAAW,cAAc;CACxD,IAAI,GAAG,WAAW,YAAY,GAC5B,WAAW,MAAM,OAAO,UAAU,YAAY,GAAG;EAC/C,SAAS;EACT,MAAM,WAAW,IAAI,cAAc,IAAI,cAAc,IAAI,yBAAyB,IAAI,KAAK;EAC3F,IAAI,WAAW,CAAC,aAAa,IAAI,QAAQ,YAAY,CAAC,GAAG,SAAS;EAClE,MAAM,QAAQ,IAAI,oBAAoB,IAAI,oBAAoB;EAC9D,IAAI,OAAO,OAAO,IAAI,KAAK;CAC7B;CAIF,MAAM,YAAY,KAAK,KAAK,WAAW,WAAW;CAClD,IAAI,GAAG,WAAW,SAAS,GACzB,WAAW,MAAM,OAAO,UAAU,SAAS,GAAG;EAC5C,SAAS;EACT,MAAM,SAAS,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,KAAK,EAAE,YAAY;EAC/E,IAAI,SAAS,CAAC,UAAU,QAAQ,cAAc,IAAI,KAAK;CACzD;CAIF,MAAM,kBAAkB,KAAK,KAAK,WAAW,iBAAiB;CAC9D,IAAI,GAAG,WAAW,eAAe,GAC/B,WAAW,MAAM,QAAQ,UAAU,eAAe,GAChD,SAAS;CAIb,SAAS,2BAA2B,MAAM,KAAK,WAAW,EAAE,MAAM,GAAG,EAAE;CACvE,SAAS,iBAAiB,MAAM,KAAK,MAAM;CAC3C,SAAS,gBAAgB,MAAM,KAAK,aAAa;CAEjD,MAAM,YACJ,SAAS,iBACT,SAAS,gBACT,SAAS,aACT,SAAS;CACX,SAAS,mBAAmB,KAAK,KAAK,YAAY,GAAI;CAEtD,OAAO;AACT;AAIA,eAAsB,oBACpB,WACA,SACA,OAA6B,CAAC,GACA;CAC9B,MAAM,SAA8B;EAClC,oBAAoB;EACpB,kBAAkB;EAClB,eAAe;EACf,qBAAqB;EACrB,QAAQ,CAAC;EACT,uBAAuB;EACvB,gBAAgB;CAClB;CAEA,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,WAAW,KAAK,YAAY,CAAC;CAGnC,IAAI,WAAkC;CACtC,IAAI,KAAK,QAAQ;EACf,WAAW,aAAa,OAAO;EAC/B,IAAI,UACF,QAAQ,MAAM,4BAA4B,SAAS,SAAS,IAAI;CAEpE;CAEA,IAAI,CAAC,UACH,WAAW;EACT,UAAU,8BAAa,IAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,SAAS,GAAG,EAAE,MAAM,GAAG,EAAE;EACjF,QAAQ;EACR,4BAAW,IAAI,KAAK,GAAE,YAAY;EAClC,QAAQ;GACN,WAAW;IAAE,QAAQ;IAAW,WAAW;GAAE;GAC7C,UAAU;IAAE,QAAQ;IAAW,WAAW;GAAE;GAC5C,OAAO;IAAE,QAAQ;IAAW,WAAW;GAAE;GACzC,aAAa;IAAE,QAAQ;IAAW,WAAW;GAAE;EACjD;CACF;CAGF,MAAM,iCAAiB,IAAI,IAAoB;CAC/C,MAAM,+BAAe,IAAI,IAAoB;CAG7C,MAAM,gBAAgB,KAAK,KAAK,WAAW,eAAe;CAC1D,IAAI,GAAG,WAAW,aAAa,KAAK,SAAS,OAAO,UAAU,WAAW,QAAQ;EAC/E,SAAS,OAAO,UAAU,SAAS;EACnC,IAAI,CAAC,QAAQ,cAAc,SAAS,QAAQ;EAE5C,WAAW,MAAM,OAAO,UAAU,aAAa,GAAG;GAChD,MAAM,QAAQ,IAAI,WAAW,IAAI,WAAW,IAAI,KAAK;GACrD,IAAI,CAAC,MAAM;GAEX,MAAM,UACJ,IAAI,aACJ,IAAI,aACJ,IAAI,cACJ,IAAI,cACJ,IACA,KAAK;GACP,MAAM,aAAa,IAAI,mBAAmB,IAAI,gBAAgB,IAAI,KAAK;GAEvE,IAAI;IACF,MAAM,EAAE,MAAM,YAAY,eAAe,SAAS,MAAM,QAAQ,IAAI,MAAM;IAC1E,eAAe,IAAI,KAAK,YAAY,GAAG,IAAI;IAC3C,OAAO;IAEP,IAAI,CAAC,UAAU,SAAS;KAEtB,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,iBAAiB,GAAG;MAC9D,MAAM,MAAM,IAAI,UAAU;MAC1B,IAAI,KAAK,qBAAqB,SAAS,MAAM,OAAO,GAAG;KACzD;KAGA,MAAM,aAAa,IAAI,0BAA0B,IAAI,0BAA0B;KAC/E,IAAI,cAAc,SAAS,aAAa;MACtC,qBAAqB,SAAS,MAAM,gBAAgB,SAAS,WAAY;MACzE,OAAO;KACT;KAGA,IAAI,WACF,qBAAqB,SAAS,MAAM,sBAAsB,SAAS;KAIrE,MAAM,cAAsC,CAAC;KAC7C,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,GAAG,GACzC,IAAI,CAAC,sBAAsB,IAAI,GAAG,KAAK,KAAK,YAAY,OAAO;KAEjE,IAAI,OAAO,KAAK,WAAW,EAAE,SAAS,GAAG;MACvC,qBAAqB,SAAS,MAAM,WAAW;MAC/C,OAAO,yBAAyB,OAAO,KAAK,WAAW,EAAE;KAC3D;IACF;GACF,SAAS,KAAK;IACZ,OAAO,OAAO,KAAK,YAAY,KAAK,KAAM,IAAc,SAAS;GACnE;GAEA,SAAS,OAAO,UAAU;EAC5B;EAEA,SAAS,OAAO,UAAU,SAAS;EACnC,IAAI,CAAC,QAAQ,cAAc,SAAS,QAAQ;CAC9C,OAAO,IAAI,SAAS,OAAO,UAAU,WAAW,QAAQ;EAEtD,MAAM,eAAe,KAAK,KAAK,SAAS,WAAW;EACnD,IAAI,GAAG,WAAW,YAAY,GAC5B,KAAK,MAAM,QAAQ,GAAG,YAAY,YAAY,GAAG;GAC/C,MAAM,KAAK,KAAK,KAAK,cAAc,MAAM,eAAe;GACxD,IAAI,CAAC,GAAG,WAAW,EAAE,GAAG;GAExB,MAAM,YADU,GAAG,aAAa,IAAI,OACZ,EAAE,MAAM,iBAAiB;GACjD,IAAI,YAAY,IAAI,eAAe,IAAI,UAAU,GAAG,KAAK,EAAE,YAAY,GAAG,IAAI;EAChF;CAEJ;CAGA,MAAM,eAAe,KAAK,KAAK,WAAW,cAAc;CACxD,IAAI,GAAG,WAAW,YAAY,KAAK,SAAS,OAAO,SAAS,WAAW,QAAQ;EAC7E,SAAS,OAAO,SAAS,SAAS;EAClC,IAAI,CAAC,QAAQ,cAAc,SAAS,QAAQ;EAE5C,WAAW,MAAM,OAAO,UAAU,YAAY,GAAG;GAC/C,MAAM,aAAa,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,KAAK;GACrE,MAAM,YAAY,IAAI,eAAe,IAAI,gBAAgB,IAAI,KAAK;GAClE,MAAM,SAAS,IAAI,YAAY,IAAI,YAAY,IAAI,KAAK;GACxD,MAAM,eACJ,IAAI,cACJ,IAAI,cACJ,IAAI,yBACJ,IAAI,yBACJ,IACA,KAAK;GACP,MAAM,SAAS,IAAI,YAAY,IAAI,YAAY,IAAI,kBAAkB,IAAI,KAAK;GAC9E,MAAM,SAAS,IAAI,eAAe,IAAI,gBAAgB,IAAI,KAAK;GAC/D,MAAM,cAAc,IAAI,iBAAiB,IAAI,iBAAiB,IAAI,KAAK;GACvE,MAAM,aAAa,IAAI,UAAU,IAAI,iBAAiB,IAAI,mBAAmB,IAAI,KAAK;GAEtF,IAAI,OAAO,eAAe,IAAI,YAAY,YAAY,CAAC;GAEvD,IAAI,CAAC,QAAQ,aAAa;IACxB,MAAM,UAAU,IAAI,cAAc,IAAI,KAAK;IAC3C,IAAI;KACF,MAAM,EAAE,MAAM,SAAS,YAAY,eACjC,SACA,aACA,QACA,OACA,MACF;KACA,OAAO;KACP,eAAe,IAAI,YAAY,YAAY,GAAG,OAAO;KACrD,IAAI,SAAS,OAAO;IACtB,SAAS,KAAK;KACZ,OAAO,OAAO,KAAK,iBAAiB,YAAY,KAAM,IAAc,SAAS;IAC/E;GACF;GAEA,IAAI,CAAC,MAAM;GAEX,IAAI,CAAC,QAAQ;IACX,MAAM,cAAc,CAAC,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;IAClE,MAAM,UAAU,CAAC,GAAG,WAAW,KAAK,KAAK,SAAS,aAAa,MAAM,eAAe,CAAC;IAGrF,IAAI,SAAS,aAAa;KACxB,MAAM,eAAe;MACnB,OAAO,SAAS,GAAG,QAAQ,WAAW,EAAE;MACxC,MAAM,eAAe;MACrB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;MACzB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;MACzB,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;MACnC,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;MACjC,WAAW;MACX,4BAAW,IAAI,KAAK,GAAE,YAAY;KACpC;KACA,IAAI;MACF,cAAc,SAAS,MAAM,YAAY;KAC3C,QAAQ,CAER;IACF;IAGA,MAAM,WAAW,iBAAiB,SAAS,IAAI;IAC/C,IAAI,SAAS,CAAC,SAAS,SAAS,QAAQ,GACtC,qBAAqB,SAAS,MAAM,SAAS,KAAK;IACpD,IAAI,SAAS,CAAC,SAAS,SAAS,QAAQ,GACtC,qBAAqB,SAAS,MAAM,SAAS,KAAK;IACpD,IAAI,eAAe,CAAC,SAAS,SAAS,kBAAkB,GACtD,qBAAqB,SAAS,MAAM,mBAAmB,WAAW;IAIpE,MAAM,aAAa,IAAI,oBAAoB,IAAI,oBAAoB;IACnE,IAAI,cAAc,SAAS,eAAe,CAAC,SAAS,SAAS,eAAe,GAAG;KAC7E,qBAAqB,SAAS,MAAM,gBAAgB,SAAS,WAAY;KACzE,OAAO;IACT;GACF;GAEA,IAAI,OAAO,aAAa,IAAI,MAAM,YAAY,GAAG,IAAI;GACrD,OAAO;GACP,SAAS,OAAO,SAAS;EAC3B;EAEA,SAAS,OAAO,SAAS,SAAS;EAClC,IAAI,CAAC,QAAQ,cAAc,SAAS,QAAQ;CAC9C;CAGA,MAAM,YAAY,KAAK,KAAK,WAAW,WAAW;CAClD,IAAI,GAAG,WAAW,SAAS,KAAK,SAAS,OAAO,MAAM,WAAW,QAC/D,IAAI,CAAC,QAAQ;EACX,MAAM,EAAE,eAAe,MAAM,OAAO;EACpC,SAAS,OAAO,MAAM,SAAS;EAC/B,cAAc,SAAS,QAAQ;EAE/B,WAAW,MAAM,OAAO,UAAU,SAAS,GAAG;GAC5C,MAAM,YAAY,IAAI,eAAe,IAAI,gBAAgB,IAAI,WAAW,IAAI,KAAK;GACjF,IAAI,CAAC,UAAU;GAEf,MAAM,eACJ,IAAI,yBACJ,IAAI,yBACJ,IAAI,cACJ,IACA,KAAK;GACP,MAAM,aAAa,IAAI,aAAa,IAAI,aAAa,KAAK,KAAK,EAAE,QAAQ,YAAY,EAAE;GACvF,MAAM,YAAY,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,KAAK,EAAE,YAAY;GAClF,MAAM,gBACJ,IAAI,gBACJ,IAAI,iBACJ,IAAI,iBACJ,IACA,KAAK;GACP,MAAM,YAAY,IAAI,yBAAyB,IAAI,eAAe,OAAO,KAAK;GAC9E,MAAM,UAAU,IAAI,iBAAiB,IAAI,mBAAmB,IAAI,gBAAgB,IAAI,KAAK;GACzF,MAAM,cAAc,IAAI,0BAA0B,IAAI,0BAA0B,IAAI,KAAK;GACzF,MAAM,eAAe,IAAI,kBAAkB,IAAI,kBAAkB,IAAI,KAAK;GAE1E,MAAM,OACJ,eAAe,IAAI,YAAY,YAAY,CAAC,KAAK,QAAQ,eAAe,SAAS;GACnF,MAAM,QAAQ,UAAU,aAAa;GACrC,MAAM,SAAS,WAAW,SAAS,KAAK;GACxC,MAAM,YAAY,WAAW,YAAY;GAEzC,MAAM,aAAuB,CAAC;GAC9B,IAAI,QAAQ,WAAW,KAAK,kBAAkB,QAAQ;GACtD,IAAI,aAAa,WAAW,KAAK,YAAY,MAAM,GAAG,GAAG,CAAC;GAC1D,IAAI,cAAc,SAAS,aAAa,WAAW,KAAK,SAAS,SAAS,aAAa;GAEvF,MAAM,OAAqB;IACzB,MAAM;IACN;IACA,OAAO;IACP,UAAU,YAAY;IACtB,aAAa,UAAU,QAAQ,IAAI,UAAU,SAAS,IAAI;IAC1D,YAAY;IACZ,0BAAS,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;IAC7C,GAAI,WAAW,SAAS,IAAI,EAAE,OAAO,WAAW,KAAK,KAAK,EAAE,IAAI,CAAC;GACnE;GAEA,IAAI;IACF,MAAM,WAAW,SAAS,MAAM,IAAI;IACpC,OAAO;GACT,SAAS,KAAK;IACZ,OAAO,OAAO,KAAK,SAAS,SAAS,KAAM,IAAc,SAAS;GACpE;GAEA,SAAS,OAAO,MAAM;EACxB;EAEA,SAAS,OAAO,MAAM,SAAS;EAC/B,cAAc,SAAS,QAAQ;CACjC,OAAO;EAEL,WAAW,MAAM,OAAO,UAAU,SAAS,GACzC,KAAK,IAAI,eAAe,IAAI,WAAW,IAAI,KAAK,GAAG,OAAO;EAE5D,SAAS,OAAO,MAAM,SAAS;CACjC;CAIF,MAAM,kBAAkB,KAAK,KAAK,WAAW,iBAAiB;CAC9D,IAAI,GAAG,WAAW,eAAe,KAAK,SAAS,OAAO,YAAY,WAAW,QAC3E,IAAI,CAAC,QAAQ;EACX,MAAM,EAAE,mBAAmB,qBAAqB,MAAM,OAAO;EAC7D,SAAS,OAAO,YAAY,SAAS;EACrC,cAAc,SAAS,QAAQ;EAE/B,WAAW,MAAM,OAAO,UAAU,eAAe,GAAG;GAClD,MAAM,WACJ,IAAI,sBACJ,IAAI,sBACJ,IAAI,WACJ,IAAI,WACJ,QAEC,KAAK,EACL,YAAY;GACf,MAAM,aACJ,IAAI,mBACJ,IAAI,gBACJ,IAAI,WACJ,IAAI,iBACJ,IACA,KAAK;GACP,MAAM,QACJ,IAAI,sBACJ,IAAI,WACJ,IAAI,YACJ,IAAI,YACJ,IAAI,mBACJ,IACA,KAAK;GACP,MAAM,WAAW,IAAI,cAAc,IAAI,cAAc,IAAI,KAAK;GAC9D,MAAM,gBACJ,IAAI,+BACJ,IAAI,oBACJ,IAAI,iBACJ,IAEC,KAAK,EACL,YAAY;GACf,MAAM,SACJ,IAAI,SACJ,IAAI,oBACJ,IAAI,mBACJ,QAAQ,YAAY,IAAI,GACxB,KAAK;GACP,MAAM,gBAAgB,IAAI,oBAAoB,IAAI,uBAAuB,IAAI,KAAK;GAClF,MAAM,eAAe,IAAI,mBAAmB,IAAI,0BAA0B,IAAI,KAAK;GACnF,MAAM,iBACJ,IAAI,yBACJ,IAAI,4BACJ,IACA,KAAK;GAEP,MAAM,OACJ,aAAa,IAAI,YAAY,KAC7B,eAAe,KAAK,IAAI,yBAAyB,IAAI,YAAY,EAAE,KAAK,CAAC;GAC3E,IAAI,CAAC,MAAM;GAEX,MAAM,YAAY,wBAAwB;GAC1C,IAAI;IAEF,KAAI,MADmB,iBAAiB,SAAS,IAAI,EAAE,YAAY,EAAE,GACxD,SAAS,SAAS,GAAG;IAElC,MAAM,OAAO,WAAW,SAAS;IACjC,MAAM,OAAO,SAAS,YAAY;IAGlC,MAAM,eAAyB,CAAC;IAChC,IAAI,SAAS,aAAa,KAAK,YAAY,SAAS;IACpD,IAAI,MAAM,aAAa,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;IAC9C,IAAI,cAAc,aAAa,KAAK,aAAa,aAAa,EAAE;IAChE,IAAI,aAAa,aAAa,KAAK,YAAY,aAAa;IAC5D,IAAI,eAAe,aAAa,KAAK,cAAc,eAAe;IAElE,MAAM,UAAU,aAAa,KAAK,KAAK,KAAK,GAAG,KAAK;IAEpD,MAAM,kBAAkB,SAAS,MAAM;KACrC;KACA;KACA,MAAM,gBAAgB;KACtB;KACA,WAAW,CAAC;KACZ;KACA,yBAAQ,IAAI,KAAK,GAAE,YAAY;IACjC,CAAC;IACD,OAAO;GACT,SAAS,KAAK;IACZ,OAAO,OAAO,KAAK,cAAc,MAAM,IAAK,IAAc,SAAS;GACrE;GAEA,SAAS,OAAO,YAAY;EAC9B;EAEA,SAAS,OAAO,YAAY,SAAS;EACrC,cAAc,SAAS,QAAQ;CACjC,OAAO;EACL,WAAW,MAAM,QAAQ,UAAU,eAAe,GAAG,OAAO;EAC5D,SAAS,OAAO,YAAY,SAAS;CACvC;CAIF,IAAI,CAAC,QAAQ,cAAc,OAAO;CAElC,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-V8BFaH-b.d.ts","names":[],"sources":["../src/schemas/sources.ts","../src/schemas/main-facts.ts","../src/schemas/interaction.ts","../src/schemas/pipeline.ts","../src/schemas/ticket.ts","../src/schemas/quote.ts","../src/schemas/kb-article.ts","../src/schemas/survey.ts","../src/commands/create.ts","../src/commands/backup.ts","../src/commands/audit.ts","../src/commands/validate.ts","../src/fs/customer-dir.ts","../src/fs/audit-log.ts","../src/core/rbac.ts","../src/core/session-store.ts","../src/version.ts"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-YqwMd6aQ.d.cts","names":[],"sources":["../src/schemas/sources.ts","../src/schemas/main-facts.ts","../src/schemas/interaction.ts","../src/schemas/pipeline.ts","../src/schemas/ticket.ts","../src/schemas/quote.ts","../src/schemas/kb-article.ts","../src/schemas/survey.ts","../src/commands/create.ts","../src/commands/backup.ts","../src/commands/audit.ts","../src/commands/validate.ts","../src/fs/customer-dir.ts","../src/fs/audit-log.ts","../src/core/rbac.ts","../src/core/session-store.ts","../src/version.ts"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"interactions-writer-CrPStUll.cjs","names":["withFileQueue"],"sources":["../src/fs/interactions-writer.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport type { InteractionEntry } from \"../schemas/interaction.js\";\nimport { withFileQueue } from \"./write-queue.js\";\n\nconst INTERACTION_SEPARATOR = \"---\";\n\nexport function formatInteractionEntry(entry: InteractionEntry): string {\n const header = `## ${entry.date} · ${entry.type}${entry.direction ? ` · ${entry.direction}` : \"\"}`;\n const withLabel = entry.type === \"Email\" ? \"Subject\" : \"With\";\n const nextStepsBlock =\n entry.nextSteps.length > 0 ? entry.nextSteps.map((s) => `- [ ] ${s}`).join(\"\\n\") : \"- [ ] —\";\n\n return `${header}\n**${withLabel}:** ${entry.with}\n**Summary:** ${entry.summary}\n**Next Steps:**\n${nextStepsBlock}\n**Source:** ${entry.sourceRef}\n**Synced:** ${entry.synced}\n${INTERACTION_SEPARATOR}\n`;\n}\n\nexport async function readInteractions(dataDir: string, slug: string): Promise<string> {\n const filePath = path.join(dataDir, \"customers\", slug, \"interactions.md\");\n if (!fs.existsSync(filePath)) {\n return \"\";\n }\n return fs.readFileSync(filePath, \"utf-8\");\n}\n\nexport async function appendInteraction(\n dataDir: string,\n slug: string,\n entry: InteractionEntry\n): Promise<void> {\n const filePath = path.join(dataDir, \"customers\", slug, \"interactions.md\");\n return withFileQueue(filePath, async () => {\n const existing = fs.existsSync(filePath) ? (fs.readFileSync(filePath, \"utf-8\") as string) : \"\";\n\n const formatted = formatInteractionEntry(entry);\n\n let newContent: string;\n if (existing === \"\") {\n newContent = formatted;\n } else {\n const headerEnd = existing.indexOf(\"\\n\\n\");\n if (headerEnd > -1) {\n const header = existing.slice(0, headerEnd + 2);\n const body = existing.slice(headerEnd + 2);\n newContent = header + formatted + \"\\n\" + body;\n } else {\n newContent = existing + \"\\n\" + formatted;\n }\n }\n\n fs.writeFileSync(filePath, newContent, \"utf-8\");\n });\n}\n"],"mappings":";;;;;;;;;;;;AAKA,MAAM,wBAAwB;AAE9B,SAAgB,uBAAuB,OAAiC;CACtE,MAAM,SAAS,MAAM,MAAM,KAAK,KAAK,MAAM,OAAO,MAAM,YAAY,MAAM,MAAM,cAAc;CAC9F,MAAM,YAAY,MAAM,SAAS,UAAU,YAAY;CACvD,MAAM,iBACJ,MAAM,UAAU,SAAS,IAAI,MAAM,UAAU,KAAK,MAAM,SAAS,GAAG,EAAE,KAAK,IAAI,IAAI;CAErF,OAAO,GAAG,OAAO;IACf,UAAU,MAAM,MAAM,KAAK;eAChB,MAAM,QAAQ;;EAE3B,eAAe;cACH,MAAM,UAAU;cAChB,MAAM,OAAO;EACzB,sBAAsB;;AAExB;AAEA,eAAsB,iBAAiB,SAAiB,MAA+B;CACrF,MAAM,WAAW,KAAA,QAAK,KAAK,SAAS,aAAa,MAAM,iBAAiB;CACxE,IAAI,CAAC,GAAA,QAAG,WAAW,QAAQ,GACzB,OAAO;CAET,OAAO,GAAA,QAAG,aAAa,UAAU,OAAO;AAC1C;AAEA,eAAsB,kBACpB,SACA,MACA,OACe;CACf,MAAM,WAAW,KAAA,QAAK,KAAK,SAAS,aAAa,MAAM,iBAAiB;CACxE,OAAOA,oBAAAA,cAAc,UAAU,YAAY;EACzC,MAAM,WAAW,GAAA,QAAG,WAAW,QAAQ,IAAK,GAAA,QAAG,aAAa,UAAU,OAAO,IAAe;EAE5F,MAAM,YAAY,uBAAuB,KAAK;EAE9C,IAAI;EACJ,IAAI,aAAa,IACf,aAAa;OACR;GACL,MAAM,YAAY,SAAS,QAAQ,MAAM;GACzC,IAAI,YAAY,IAAI;IAClB,MAAM,SAAS,SAAS,MAAM,GAAG,YAAY,CAAC;IAC9C,MAAM,OAAO,SAAS,MAAM,YAAY,CAAC;IACzC,aAAa,SAAS,YAAY,OAAO;GAC3C,OACE,aAAa,WAAW,OAAO;EAEnC;EAEA,GAAA,QAAG,cAAc,UAAU,YAAY,OAAO;CAChD,CAAC;AACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"interactions-writer-DO3KcSR3.js","names":[],"sources":["../src/fs/interactions-writer.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport type { InteractionEntry } from \"../schemas/interaction.js\";\nimport { withFileQueue } from \"./write-queue.js\";\n\nconst INTERACTION_SEPARATOR = \"---\";\n\nexport function formatInteractionEntry(entry: InteractionEntry): string {\n const header = `## ${entry.date} · ${entry.type}${entry.direction ? ` · ${entry.direction}` : \"\"}`;\n const withLabel = entry.type === \"Email\" ? \"Subject\" : \"With\";\n const nextStepsBlock =\n entry.nextSteps.length > 0 ? entry.nextSteps.map((s) => `- [ ] ${s}`).join(\"\\n\") : \"- [ ] —\";\n\n return `${header}\n**${withLabel}:** ${entry.with}\n**Summary:** ${entry.summary}\n**Next Steps:**\n${nextStepsBlock}\n**Source:** ${entry.sourceRef}\n**Synced:** ${entry.synced}\n${INTERACTION_SEPARATOR}\n`;\n}\n\nexport async function readInteractions(dataDir: string, slug: string): Promise<string> {\n const filePath = path.join(dataDir, \"customers\", slug, \"interactions.md\");\n if (!fs.existsSync(filePath)) {\n return \"\";\n }\n return fs.readFileSync(filePath, \"utf-8\");\n}\n\nexport async function appendInteraction(\n dataDir: string,\n slug: string,\n entry: InteractionEntry\n): Promise<void> {\n const filePath = path.join(dataDir, \"customers\", slug, \"interactions.md\");\n return withFileQueue(filePath, async () => {\n const existing = fs.existsSync(filePath) ? (fs.readFileSync(filePath, \"utf-8\") as string) : \"\";\n\n const formatted = formatInteractionEntry(entry);\n\n let newContent: string;\n if (existing === \"\") {\n newContent = formatted;\n } else {\n const headerEnd = existing.indexOf(\"\\n\\n\");\n if (headerEnd > -1) {\n const header = existing.slice(0, headerEnd + 2);\n const body = existing.slice(headerEnd + 2);\n newContent = header + formatted + \"\\n\" + body;\n } else {\n newContent = existing + \"\\n\" + formatted;\n }\n }\n\n fs.writeFileSync(filePath, newContent, \"utf-8\");\n });\n}\n"],"mappings":";;;;;;;;;;AAKA,MAAM,wBAAwB;AAE9B,SAAgB,uBAAuB,OAAiC;CACtE,MAAM,SAAS,MAAM,MAAM,KAAK,KAAK,MAAM,OAAO,MAAM,YAAY,MAAM,MAAM,cAAc;CAC9F,MAAM,YAAY,MAAM,SAAS,UAAU,YAAY;CACvD,MAAM,iBACJ,MAAM,UAAU,SAAS,IAAI,MAAM,UAAU,KAAK,MAAM,SAAS,GAAG,EAAE,KAAK,IAAI,IAAI;CAErF,OAAO,GAAG,OAAO;IACf,UAAU,MAAM,MAAM,KAAK;eAChB,MAAM,QAAQ;;EAE3B,eAAe;cACH,MAAM,UAAU;cAChB,MAAM,OAAO;EACzB,sBAAsB;;AAExB;AAEA,eAAsB,iBAAiB,SAAiB,MAA+B;CACrF,MAAM,WAAW,KAAK,KAAK,SAAS,aAAa,MAAM,iBAAiB;CACxE,IAAI,CAAC,GAAG,WAAW,QAAQ,GACzB,OAAO;CAET,OAAO,GAAG,aAAa,UAAU,OAAO;AAC1C;AAEA,eAAsB,kBACpB,SACA,MACA,OACe;CACf,MAAM,WAAW,KAAK,KAAK,SAAS,aAAa,MAAM,iBAAiB;CACxE,OAAO,cAAc,UAAU,YAAY;EACzC,MAAM,WAAW,GAAG,WAAW,QAAQ,IAAK,GAAG,aAAa,UAAU,OAAO,IAAe;EAE5F,MAAM,YAAY,uBAAuB,KAAK;EAE9C,IAAI;EACJ,IAAI,aAAa,IACf,aAAa;OACR;GACL,MAAM,YAAY,SAAS,QAAQ,MAAM;GACzC,IAAI,YAAY,IAAI;IAClB,MAAM,SAAS,SAAS,MAAM,GAAG,YAAY,CAAC;IAC9C,MAAM,OAAO,SAAS,MAAM,YAAY,CAAC;IACzC,aAAa,SAAS,YAAY,OAAO;GAC3C,OACE,aAAa,WAAW,OAAO;EAEnC;EAEA,GAAG,cAAc,UAAU,YAAY,OAAO;CAChD,CAAC;AACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"interactions-writer-SLHnoEeE.js","names":[],"sources":["../src/fs/interactions-writer.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport type { InteractionEntry } from \"../schemas/interaction.js\";\nimport { withFileQueue } from \"./write-queue.js\";\n\nconst INTERACTION_SEPARATOR = \"---\";\n\nexport function formatInteractionEntry(entry: InteractionEntry): string {\n const header = `## ${entry.date} · ${entry.type}${entry.direction ? ` · ${entry.direction}` : \"\"}`;\n const withLabel = entry.type === \"Email\" ? \"Subject\" : \"With\";\n const nextStepsBlock =\n entry.nextSteps.length > 0 ? entry.nextSteps.map((s) => `- [ ] ${s}`).join(\"\\n\") : \"- [ ] —\";\n\n return `${header}\n**${withLabel}:** ${entry.with}\n**Summary:** ${entry.summary}\n**Next Steps:**\n${nextStepsBlock}\n**Source:** ${entry.sourceRef}\n**Synced:** ${entry.synced}\n${INTERACTION_SEPARATOR}\n`;\n}\n\nexport async function readInteractions(dataDir: string, slug: string): Promise<string> {\n const filePath = path.join(dataDir, \"customers\", slug, \"interactions.md\");\n if (!fs.existsSync(filePath)) {\n return \"\";\n }\n return fs.readFileSync(filePath, \"utf-8\");\n}\n\nexport async function appendInteraction(\n dataDir: string,\n slug: string,\n entry: InteractionEntry\n): Promise<void> {\n const filePath = path.join(dataDir, \"customers\", slug, \"interactions.md\");\n return withFileQueue(filePath, async () => {\n const existing = fs.existsSync(filePath) ? (fs.readFileSync(filePath, \"utf-8\") as string) : \"\";\n\n const formatted = formatInteractionEntry(entry);\n\n let newContent: string;\n if (existing === \"\") {\n newContent = formatted;\n } else {\n const headerEnd = existing.indexOf(\"\\n\\n\");\n if (headerEnd > -1) {\n const header = existing.slice(0, headerEnd + 2);\n const body = existing.slice(headerEnd + 2);\n newContent = header + formatted + \"\\n\" + body;\n } else {\n newContent = existing + \"\\n\" + formatted;\n }\n }\n\n fs.writeFileSync(filePath, newContent, \"utf-8\");\n });\n}\n"],"mappings":";;;;AAKA,MAAM,wBAAwB;AAE9B,SAAgB,uBAAuB,OAAiC;CACtE,MAAM,SAAS,MAAM,MAAM,KAAK,KAAK,MAAM,OAAO,MAAM,YAAY,MAAM,MAAM,cAAc;CAC9F,MAAM,YAAY,MAAM,SAAS,UAAU,YAAY;CACvD,MAAM,iBACJ,MAAM,UAAU,SAAS,IAAI,MAAM,UAAU,KAAK,MAAM,SAAS,GAAG,EAAE,KAAK,IAAI,IAAI;CAErF,OAAO,GAAG,OAAO;IACf,UAAU,MAAM,MAAM,KAAK;eAChB,MAAM,QAAQ;;EAE3B,eAAe;cACH,MAAM,UAAU;cAChB,MAAM,OAAO;EACzB,sBAAsB;;AAExB;AAEA,eAAsB,iBAAiB,SAAiB,MAA+B;CACrF,MAAM,WAAW,KAAK,KAAK,SAAS,aAAa,MAAM,iBAAiB;CACxE,IAAI,CAAC,GAAG,WAAW,QAAQ,GACzB,OAAO;CAET,OAAO,GAAG,aAAa,UAAU,OAAO;AAC1C;AAEA,eAAsB,kBACpB,SACA,MACA,OACe;CACf,MAAM,WAAW,KAAK,KAAK,SAAS,aAAa,MAAM,iBAAiB;CACxE,OAAO,cAAc,UAAU,YAAY;EACzC,MAAM,WAAW,GAAG,WAAW,QAAQ,IAAK,GAAG,aAAa,UAAU,OAAO,IAAe;EAE5F,MAAM,YAAY,uBAAuB,KAAK;EAE9C,IAAI;EACJ,IAAI,aAAa,IACf,aAAa;OACR;GACL,MAAM,YAAY,SAAS,QAAQ,MAAM;GACzC,IAAI,YAAY,IAAI;IAClB,MAAM,SAAS,SAAS,MAAM,GAAG,YAAY,CAAC;IAC9C,MAAM,OAAO,SAAS,MAAM,YAAY,CAAC;IACzC,aAAa,SAAS,YAAY,OAAO;GAC3C,OACE,aAAa,WAAW,OAAO;EAEnC;EAEA,GAAG,cAAc,UAAU,YAAY,OAAO;CAChD,CAAC;AACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge-base-D0Fh40kc.js","names":[],"sources":["../src/mcp/capabilities.ts","../src/schemas/kb-article.ts","../src/fs/knowledge-base.ts"],"sourcesContent":["// src/mcp/capabilities.ts\n// Single Source of Truth for capability documentation.\n// Used by get_capabilities() MCP tool AND dxcrm guide CLI command.\n\nexport const CAPABILITIES_TEXT = `\n# DatasynxOpenCRM — Agent Guide\n\n## Product\nDatasynxOpenCRM is a local-first, MCP-native CRM. All customer data lives in markdown\nfiles on your machine. No cloud, no HubSpot, no per-seat pricing.\n\n## Agent Wake (Telegram Notifications)\n\\`dxcrm agent spawn\\` enables a wake-triggered agent for a customer. When new emails arrive,\nthe agent sends a Telegram notification so you never miss an inbound message.\n\\`\\`\\`\ndxcrm agent spawn acme-corp --channel telegram\n\\`\\`\\`\nRequires: \\`TELEGRAM_BOT_TOKEN\\` + \\`TELEGRAM_CHAT_ID\\` env vars.\n\n## Golden Path — Agent Session Workflow\n\nThe recommended sequence for a productive agent session:\n\n1. \\`get_capabilities()\\` — understand available tools (this guide)\n2. \\`get_active_session()\\` — check for an active customer session\n3. \\`get_customer_context({ slug })\\` — load full briefing for the customer\n4. \\`search_customer_knowledge({ slug, query })\\` — find specific historical information\n5. \\`log_interaction()\\` / \\`update_deal()\\` — write back what happened\n\n## RBAC — Role-Based Access Control\n\nTools enforce the \\`DXCRM_ACTOR\\` environment variable for identity. Configure roles with \\`dxcrm rbac set\\`.\n\n| Role | Permissions |\n|---|---|\n| admin | All tools, all customers |\n| manager | log_interaction, update_deal, pursue_goal + all read tools |\n| rep | log_interaction, update_deal (own customers only) + all read tools |\n\nDefault role: rep (when DXCRM_ACTOR is not set or has no assigned role).\n\nConfig: \\`.agentic/rbac.json\\` | Actor: \\`DXCRM_ACTOR\\` env var\n\n## Available Tools\n\n| Tool | Purpose | RBAC |\n|---|---|---|\n| get_capabilities | Returns this guide — understand what the CRM can do | any |\n| get_active_session | Check which customer session is currently active | any |\n| get_customer_context | Full LLM-ready briefing for a customer (last 10 interactions, pipeline, contacts) | any (rep: own only) |\n| search_customer_knowledge | Hybrid vector + full-text search across emails and transcripts for a customer | any |\n| list_customers | List all customers with stage, last interaction date, and deal value | any (rep: own only) |\n| log_interaction | Write a new interaction entry (call, email, meeting, note) — immediately searchable | rep+ |\n| update_deal | Create or update a deal in pipeline.md — upserts by deal name | rep+ |\n| update_customer_facts | Update fields in customer profile (domain, contact, stage, tags) | admin |\n| export_customer | Export all customer data as JSON or Markdown | admin |\n| get_deal_health | Score deal health 0–100 (A–F grade) based on activity, velocity, close date, probability | any |\n| get_pipeline_forecast | Aggregate weighted pipeline revenue across all customers grouped by stage | any |\n| get_pipeline_stages | List all configured pipeline stages (defaults: lead, qualified, proposal, negotiation, won, lost) | any |\n| summarize_meeting | LLM-summarize a transcript and log it as a Meeting interaction | rep+ |\n| get_market_intelligence | Semantic search across all customers for patterns and common topics | any |\n| get_relationship_graph | Stakeholder map: champions, blockers, economic buyers, warm intro paths | any |\n| get_relationship_health | Health scores (0–100, A–F, trend) per contact with decay detection and risk flags | any |\n| run_deal_agent | Analyze deal and generate prioritized action plan (observe/suggest/act autonomy levels) | rep+ |\n| approve_agent_action | Approve or reject a pending action from the deal agent queue | rep+ |\n| simulate_revenue | Monte Carlo pipeline forecast — P10/P50/P90, sensitivity map, at-risk revenue | any |\n| get_playbook | Retrieve playbooks matching current deal situation (trigger-matched, sorted by success rate) | any |\n| create_playbook | Create or update a playbook with trigger DSL encoding proven tactics | rep+ |\n| list_playbooks | List all playbooks for a customer (metadata only, no body) | any |\n| distill_playbook | LLM-extract a reusable playbook from a won or lost deal's interaction history | rep+ |\n| pursue_goal | Set a revenue/pipeline goal and get an AI-decomposed action plan with sub-goals | manager+ |\n| get_goal_status | Get all active goals or a specific goal with progress, days remaining, sub-goals | any |\n| register_push_subscription | Register real-time push subscription (Gmail Pub/Sub, MS Graph, Slack Events) | admin |\n| get_push_status | Show all push subscriptions: expiry, events processed, renewal needs | any |\n| get_org_intelligence | Stakeholder map with champions, buyers, blockers, health scores, risk flags, recommendation | any |\n| open_deal_room | Multi-agent deal brief: graph + health + deal health + simulation + playbook in one call | any |\n| get_proactive_briefing | Daily briefing: urgent alerts, opportunities, P50/P90 forecast, top action | any |\n| list_email_templates | List all saved email templates with id, name, category, subject | any |\n| get_email_template | Retrieve a single email template with full body and detected variables | any |\n| draft_email | Draft a personalized email from a template with auto-filled customer variables | rep+ |\n| enroll_in_sequence | Enroll a contact in a multi-step email sequence | rep+ |\n| list_sequence_enrollments | List active sequence enrollments filtered by customer or status | any |\n| unenroll_from_sequence | Pause (soft-unenroll) a contact from an active sequence | rep+ |\n| list_sequences | List all defined email sequences with step count and enrollment count | any |\n| generate_quote | Generate a professional HTML quote with line items, VAT, subtotal, total | rep+ |\n| get_quote_status | Retrieve a generated quote by number or list all quotes for a customer | any |\n| get_booking_link | Get a Calendly booking link for a customer — optionally pre-fills name/email | rep+ |\n| create_ticket | Create a support ticket with auto-calculated SLA due date based on priority | rep+ |\n| update_ticket | Update ticket status or assignee (resolved auto-sets resolution date) | rep+ |\n| list_tickets | List tickets filtered by customer, status, priority, or assignee | any |\n| close_ticket | Close a ticket and optionally log resolution as an interaction | rep+ |\n| send_nps_survey | Generate NPS/CSAT survey token + HTML email draft (does not send automatically) | rep+ |\n| get_survey_results | NPS score, promoter/passive/detractor breakdown, all responses for a survey | any |\n| search_knowledge_base | Full-text search across KB articles (title, body, tags) with category and public filters | any |\n| create_kb_article | Create a new knowledge base article stored as Markdown in .agentic/knowledge-base/ | rep+ |\n| backup_now | Trigger immediate backup of customers/ + .agentic/ with SHA-256 integrity check | admin |\n| list_backups | List available backups with date, size, verification status, and customer count | any |\n| trigger_sync | Force immediate Gmail sync for one or all customers | rep+ |\n| get_audit_log | Read audit log — all write operations with actor, tool, customer | admin |\n| define_custom_object | Define a runtime custom object type with typed fields (no migration) | admin |\n| create_record | Create a record of a custom object (validated against its schema) | rep+ |\n| list_records | List records of a custom object | any |\n| list_custom_objects | List all defined custom objects and their schemas | any |\n\n## MCP Resources (read-only)\n\nBesides Tools, the server exposes read-only Resources you can fetch via resources/read:\n- crm://customers — list of all customer slugs (JSON)\n- crm://customer/{slug} — LLM-ready briefing (main facts, recent interactions, pipeline)\n- crm://pipeline/{slug} — deals for a customer (JSON)\n- crm://timeline/{slug} — newest-first interaction history (Markdown)\n\n## MCP Prompts (playbooks)\n\nReusable playbook prompts via prompts/get (argument: slug):\n- deal_risk_review — assess deal health and risk, recommend next steps\n- draft_follow_up — draft a personalized follow-up email\n- account_brief — concise executive account brief\n- pipeline_summary — pipeline + forecast summary\n\n## Tool Reference\n\n### get_capabilities()\nReturns all available MCP tools, their inputs, and the CRM workflow guide.\n- Input: none\n- Returns: This guide text\n\n### get_active_session()\nCheck which customer is currently active in the session store.\n- Input: none\n- Returns: { hasSession: boolean, customerSlug?, customerName?, startedAt?, owner? }\n\n### get_customer_context({ slug? })\nLoad complete briefing for a customer. Reads main_facts.md, last 10 interactions,\nand pipeline deals. Returns a structured markdown context block.\nAutomatically triggers a background Gmail sync if last sync was >30 minutes ago.\n- Input: { slug?: string } — Customer ID (e.g. \"acme-corp\"). Leave empty for active session.\n- Returns: Formatted markdown with Quick Reference, Contacts, Critical Context,\n Recent Activity, Pipeline, and Open Questions\n- Performance: <3 seconds. Token budget: <3000 tokens.\n\n### search_customer_knowledge({ slug, query, limit? })\nHybrid vector + full-text search across all emails and transcripts for a customer.\nSearches the LanceDB docs table for the given customer.\n- Input: { slug: string, query: string, limit?: number (default 5, max 50) }\n- Returns: { results: Array<{ content, score, source }> }\n\n### list_customers({ filter? })\nList all customers with their stage, last interaction date, and deal value.\nRBAC: rep role only sees owned customers.\n- Input: { filter?: string } — Optional substring filter on name or slug (case-insensitive)\n- Returns: Array of { slug, name, stage, lastInteraction?, dealValue? }\n\n### log_interaction({ slug, type, summary, with, nextSteps?, direction?, source?, date? })\nWrite a new interaction entry to interactions.md. Immediately searchable.\nAlso auto-updates the relationship graph and health scores (fire-and-forget).\nUse after every call, meeting, or email.\nRBAC: rep+\n- Input:\n slug: Customer ID\n type: \"Email\" | \"Call\" | \"Meeting\" | \"Note\" | \"Demo\" | \"Proposal\" | \"Contract\" | \"Other\"\n summary: 2-5 sentences describing what happened\n with: Who was involved (name or email)\n nextSteps?: Array of action items\n direction?: \"inbound\" | \"outbound\"\n source?: Source reference string (auto-generated if omitted)\n date?: Interaction date YYYY-MM-DD (defaults to today)\n- Returns: { success: boolean, path: string, entry: string }\n\n### update_deal({ slug, dealName, stage?, value?, probability?, closeDate?, notes? })\nUpdate or create a deal in pipeline.md. Upserts by deal name.\nRBAC: rep+\n- Input:\n slug: Customer ID\n dealName: Deal name (used as unique key)\n stage?: \"lead\" | \"qualified\" | \"proposal\" | \"negotiation\" | \"won\" | \"lost\"\n value?: Deal value in euros\n probability?: Win probability (0-100)\n closeDate?: Expected close date (YYYY-MM-DD)\n notes?: Free-text notes\n- Returns: { success: boolean, deal: object }\n\n### update_customer_facts({ slug, name?, domain?, email?, phone?, industry?, relationshipStage?, dealValue?, primaryContact?, timezone?, tags? })\nUpdate fields in a customer's main_facts.md profile. Merges patch into existing data. Sets updated = today.\nRBAC: admin\n- Input: slug (required) + any combination of the optional fields\n- Returns: { success: boolean, facts: object }\n\n### export_customer({ slug, format? })\nExport all customer data (main_facts + interactions count + pipeline + attachments list).\nRBAC: admin\n- Input: { slug: string, format?: \"json\" | \"markdown\" (default \"json\") }\n- Returns (JSON): { slug, exportedAt, mainFacts, interactionsCount, pipeline, attachments }\n- Returns (Markdown): Formatted document with all sections\n\n### get_deal_health({ slug })\nScore the health of all deals for a customer based on activity recency, stage velocity,\nclose date proximity, and probability.\n- Input: { slug: string }\n- Returns: { slug, deals: [{ deal, stage, score, grade, signals, warnings }] }\n\n### get_pipeline_forecast({ filter? })\nAggregate weighted pipeline revenue across all customers. Groups open deals by stage,\ncomputes probability-weighted expected revenue. Excludes won/lost deals.\n- Input: { filter?: string } — Optional filter by customer slug substring\n- Returns: { deals: [...], totalWeightedValue: number, byStage: { stage: { count, weightedValue } } }\n\n### get_pipeline_stages()\nReturns all configured pipeline stages. Falls back to default stages if no custom stages configured.\n- Input: none\n- Returns: { stages: [{ id, label, order, probability, color, final }] }\n\n### summarize_meeting({ slug, transcript, with?, date? })\nLLM-summarize a meeting transcript and log it as a Meeting interaction.\nFalls back to raw text slice if LLM unavailable.\nRBAC: rep+\n- Input:\n slug: Customer ID\n transcript: Full meeting transcript text\n with?: Participant names\n date?: Meeting date YYYY-MM-DD (defaults to today)\n- Returns: { success, summary, nextSteps, sourceRef }\n\n### get_market_intelligence({ query, excludeCurrentCustomer?, slug? })\nSearch across all customers to find patterns, common topics, or similar issues.\nUses semantic search (LanceDB) across all customer knowledge bases.\nResults use slug (not real names) for privacy.\n- Input: { query: string, excludeCurrentCustomer?: boolean, slug?: string }\n- Returns: { query, results: CrossCustomerResult[], totalCustomersSearched }\n\n### get_relationship_graph({ slug })\nReturns the knowledge graph for a customer: contacts, companies, and their relationships.\nAuto-populated from every log_interaction call. Shows stakeholder map with champions, blockers,\neconomic buyers, and warm intro paths.\n- Input: { slug: string }\n- Returns: { nodeCount, edgeCount, updatedAt, stakeholders: { champions[], blockers[], economicBuyers[], allContacts[], missingRoles[] }, warmIntroPaths[], nodes[], edges[] }\n\n### get_relationship_health({ slug })\nReturns health scores (0-100, A-F grade) for all contacts. Scores decay when cadence breaks.\nRisk flags: NO_CONTACT_14D, NO_CONTACT_30D, CHAMPION_SILENT.\nRecomputes automatically if stale (>1h) or missing.\n- Input: { slug: string }\n- Returns: { overallHealth, updatedAt, atRiskContacts[], coldContacts[], contacts: ContactHealth[] }\n\n### run_deal_agent({ slug, dealName, autonomyLevel?, instruction?, valueThreshold? })\nAnalyzes deal situation (health, relationships, stakeholder gaps) via LLM (rule-based fallback).\nReturns prioritized action plan with confidence scores and full reasoning trace.\nautonomyLevel: \"observe\" (read-only) | \"suggest\" (queue for review, default) | \"act\" (auto-execute if confidence ≥ 0.7 and value < valueThreshold)\nRBAC: rep+\n- Input: { slug, dealName, autonomyLevel?: \"observe\"|\"suggest\"|\"act\", instruction?, valueThreshold?: number (default 50000) }\n- Returns: { assessment, riskLevel, plan[], actionsQueued[], actionsExecuted[], trace }\n\n### approve_agent_action({ slug, actionId, approved })\nExecute (approved=true) or reject (approved=false) a pending deal agent action.\nFind actionId in run_deal_agent response.actionsQueued[].actionId\nRBAC: rep+\n- Input: { slug, actionId, approved: boolean }\n- Returns: { success, actionId, status }\n\n### simulate_revenue({ horizon?, iterations? })\nMonte Carlo simulation over all active deals. Adjusts probabilities via health score (D12) and\nchampion presence (D11). Returns P10/P50/P90 confidence interval + sensitivity map.\nhorizon: \"quarter\" (default) | \"year\"\n- Returns: { forecast: { p10, p50, p90, expected, stdDev, atRiskRevenue, byCloseMonth, topRisks, sensitivityMap }, confidence, dealCount, horizon }\n\n### get_playbook({ slug, stage?, value?, healthScore?, daysSinceContact?, championPresent? })\nReturns playbooks matching the current deal situation. Without deal context, returns all playbooks.\nPlaybooks are sorted by success rate (highest first). run_deal_agent uses playbooks automatically.\n- Input: slug (required) + optional deal context fields for trigger matching\n- Returns: { matches: [{ name, score, matchedConditions, trigger, successRate, usedCount, content }], totalPlaybooks, slug }\n\n### create_playbook({ slug, name, trigger, content, successRate? })\nCreate or update a playbook encoding proven tactics for a specific deal situation.\nTrigger DSL uses AND-only conditions: deal_stage_<s> | value > N | value < N | days_stalled > N | health < N | health > N | no_champion | has_champion\nRBAC: rep+\n- Input: slug, name, trigger (DSL string), content (markdown), successRate? (0–1, default 0.5)\n- Returns: { success: true, playbook: { name, trigger, successRate, usedCount, lastUpdated, path } }\n\n### list_playbooks({ slug })\nList all playbooks for a customer (metadata only — no body content for performance).\n- Input: { slug: string }\n- Returns: { playbooks: [{ name, trigger, successRate, usedCount, lastUpdated }], count, slug }\n\n### distill_playbook({ slug, dealName, outcome })\nLLM analyzes a deal's interaction history and extracts a reusable playbook.\nRun after every won or lost deal to build procedural memory.\nRBAC: rep+\noutcome: \"won\" | \"lost\"\n- Returns: { success: true, playbook: { name, trigger, successRate, path }, reasoning }\n\n### pursue_goal({ goal, deadline, context? })\nSet a revenue or pipeline goal and get an AI-decomposed action plan.\nAnalyzes current pipeline (P50 forecast) and decomposes the gap into prioritized sub-goals per deal.\nPersists goal to .agentic/goals.json for tracking.\nRBAC: manager+\n- Input: { goal: string, deadline: \"YYYY-MM-DD\", context?: string }\n- Returns: { goalId, description, target, deadline, decomposition: { analysis, currentPipeline, gap, subGoals, probabilisticOutcome } }\n\n### get_goal_status({ goalId? })\nGet the status of active goals. Without goalId, returns all active goals.\n- Input: { goalId?: string } — omit for all active goals\n- Returns: { goals: [{ id, description, target, progress, status, deadline, daysRemaining, subGoals }], activeCount, completedCount }\n\n### register_push_subscription({ provider, slug, webhookUrl, ... })\nRegister a real-time push subscription so providers send events in real-time (no polling).\nRBAC: admin only\n- Input:\n provider: \"gmail\" | \"microsoft-graph\" | \"slack\"\n slug: Customer slug to receive events for\n webhookUrl: Public HTTPS URL for provider callbacks\n gmailTopicName?: (Gmail) Cloud Pub/Sub topic name\n microsoftClientState?: (MS Graph) Secret for HMAC verification\n microsoftResource?: (MS Graph) Resource path\n slackTeamId?: (Slack) Workspace team ID\n slackChannelId?: (Slack) Optional specific channel\n- Returns: { subscriptionId, provider, slug, status, expiresAt, createdAt, warning? }\n\n### get_push_status({ slug?, provider? })\nShow all push subscriptions with expiry and event counts.\n- Input: { slug?: string, provider?: \"gmail\" | \"microsoft-graph\" | \"slack\" }\n- Returns: { subscriptions: [{ id, provider, slug, status, expiresAt, expiresInHours, needsRenewal, lastEventAt, eventsProcessed }], summary: { total, active, expiringSoon, expired } }\n\n### get_org_intelligence({ slug, dealName? })\nBuild a stakeholder map for a customer: champions, economic buyers, blockers, health scores, risk flags, and a prioritised recommendation.\n- Input: { slug: string, dealName?: string }\n- Returns: { slug, updatedAt, people: [{ name, email, role, healthScore, daysSinceContact, contactStrength, riskFlags }], missingRoles, riskAssessment, recommendation }\n\n### open_deal_room({ slug, dealName })\nMulti-agent deal brief: orchestrates relationship graph, health scores, deal health, Monte Carlo simulation, and playbook matching into a unified brief with executive summary, top priorities, and risk score (0–100).\n- Input: { slug: string, dealName: string }\n- Returns: { slug, dealName, generatedAt, stakeholders, relationshipHealth, dealHealth, revenueSimulation, recommendedPlaybook, executiveSummary, topPriorities, riskScore }\n\n### get_proactive_briefing({ date? })\nGenerate a proactive daily briefing: urgent alerts (relationship decay, deal risk, overdue close dates),\npipeline forecast (P50/P90), and a single top-action recommendation.\n- Input: { date?: \"YYYY-MM-DD\" } — defaults to today\n- Returns: { date, generatedAt, urgent: string[], opportunities: string[], forecast: string, topAction: string }\n\n### list_email_templates({ category? })\nList available email templates. Optionally filter by category.\n- Input: { category?: string } — e.g. \"outreach\", \"followup\", \"support\"\n- Returns: Array of { id, name, category, subject } (body excluded for performance)\n\n### get_email_template({ id })\nGet a specific email template with full body and detected template variables.\n- Input: { id: string } — Template ID (e.g. \"enterprise-intro\")\n- Returns: { id, name, category, subject, body, detectedVariables: string[] }\n\n### draft_email({ slug, templateId, overrides?, tone? })\nDraft a personalized email for a customer using a stored template.\nVariables are auto-filled from the customer's main_facts.md.\nOptional tone (e.g. \"formal\", \"friendly\", \"concise\") LLM-polishes the body;\nfalls back to plain template-fill without an ANTHROPIC_API_KEY.\nDoes NOT send automatically — returns the draft for review.\nRBAC: rep+\n- Input: { slug, templateId, overrides?: Record<string, string> }\n- Returns: { subject, body, to, slug, templateId, resolvedVariables }\n\n### enroll_in_sequence({ slug, contactEmail, sequenceId })\nEnroll a contact in an email sequence. Validates that the sequence and first template exist.\nRBAC: rep+\n- Input: { slug: string, contactEmail: string, sequenceId: string }\n- Returns: { enrollmentId, sequenceName, totalSteps }\n\n### list_sequence_enrollments({ slug?, status? })\nList email sequence enrollments. Filter by customer slug or status.\n- Input: { slug?: string, status?: \"active\" | \"paused\" | \"completed\" }\n- Returns: { enrollments: SequenceEnrollment[] }\n\n### unenroll_from_sequence({ enrollmentId })\nPause (soft-unenroll) a contact from an email sequence. Sets status to \"paused\".\nRBAC: rep+\n- Input: { enrollmentId: string }\n- Returns: { success: boolean }\n\n### list_sequences()\nList all defined email sequences with step count and current enrollment count.\n- Input: none\n- Returns: { sequences: [{ id, name, stepCount, enrollmentCount }] }\n\n### generate_quote({ slug, dealName, lineItems, vatPercent?, validUntilDays?, currency? })\nGenerate a professional HTML quote for a customer deal.\nCalculates subtotal, VAT, and total. Saves JSON + HTML to .agentic/quotes/.\nRBAC: rep+\n- Input:\n slug: Customer slug\n dealName: Deal name this quote is for\n lineItems: Array<{ description, quantity, unitPrice }>\n vatPercent?: VAT percentage (default 19)\n validUntilDays?: Quote validity in days (default 30)\n currency?: Currency code (default EUR)\n- Returns: { quoteNumber, htmlPath, total, subtotal, vat, vatPercent, currency, validUntil, status }\n\n### get_quote_status({ quoteNumber?, slug? })\nGet quote status and details. Filter by quoteNumber (single quote) or slug (all quotes for customer).\n- Input: { quoteNumber?: string, slug?: string }\n- Returns (single): Full quote object with status: draft | sent | viewed | accepted | declined\n- Returns (list): { quotes: [...] }\n\n### get_booking_link({ slug, eventType?, prefillName? })\nGet a Calendly booking link for a customer. Optionally pre-fills the customer's name/email.\nRequires CALENDLY_API_KEY env var or .agentic/integrations/calendly.yaml config.\nRBAC: rep+\n- Input: { slug, eventType?: string, prefillName?: boolean }\n- Returns: { bookingUrl, eventType, duration }\n\n### create_ticket({ slug, title, description?, priority?, assignee? })\nCreate a support ticket. Auto-calculates SLA due date based on priority.\nSLA defaults: urgent=4h, high=24h, normal=72h, low=168h.\nRBAC: rep+\n- Input:\n slug: Customer slug\n title: Ticket title\n description?: Detailed description\n priority?: \"urgent\" | \"high\" | \"normal\" | \"low\" (default: normal)\n assignee?: Assignee name or email\n- Returns: { ticket } with id T-NNN, status=open, slaDue\n\n### update_ticket({ slug, ticketId, status?, assignee? })\nUpdate a ticket's status or assignee. Setting status=resolved auto-sets resolved date.\nRBAC: rep+\n- Input: { slug, ticketId, status?: \"open\"|\"in-progress\"|\"waiting\"|\"resolved\"|\"closed\", assignee?: string }\n- Returns: { ticket }\n\n### list_tickets({ slug?, status?, priority?, assignee? })\nList support tickets sorted by priority then date. Filter by any combination of fields.\n- Input: { slug?, status?: \"open\"|\"in-progress\"|\"waiting\"|\"resolved\"|\"closed\", priority?: \"urgent\"|\"high\"|\"normal\"|\"low\", assignee? }\n- Returns: { tickets: Array<{ slug, ticket }> }\n\n### close_ticket({ slug, ticketId, resolution? })\nClose a ticket and optionally log the resolution as an interaction in interactions.md.\nRBAC: rep+\n- Input: { slug, ticketId, resolution?: string }\n- Returns: { ticket } with status=closed\n\n### send_nps_survey({ slug, contactEmail, surveyId, serverUrl? })\nGenerate an NPS/CSAT survey email draft. Returns subject, HTML body, and a token-based response URL.\nDoes NOT send automatically — returns draft for review.\nRequires survey definition in .agentic/surveys/.\nRBAC: rep+\n- Input: { slug, contactEmail, surveyId, serverUrl?: string }\n- Returns: { token, subject, body, surveyUrl, note }\n\n### get_survey_results({ surveyId, slug? })\nCalculate NPS score and breakdown for a survey. Optionally filter to a single customer.\n- Input: { surveyId: string, slug?: string }\n- Returns: { surveyId, totalResponses, npsScore (-100 to 100), promoters, passives, detractors, responses: [{ slug, email, score, comment?, respondedAt }] }\n\n### search_knowledge_base({ query, category?, publicOnly?, limit? })\nFull-text search across all KB articles (title, body, tags).\n- Input: { query, category?: string, publicOnly?: boolean, limit?: number (default 10) }\n- Returns: { query, count, articles: [{ id, title, category, excerpt, public, tags }] }\n\n### create_kb_article({ id, title, body, category?, tags?, public?, sourceTicketId? })\nCreate a new knowledge base article. Articles are stored as Markdown in .agentic/knowledge-base/.\nReturns an error if an article with the same ID already exists.\nRBAC: rep+\n- Input:\n id: Article slug (e.g. \"troubleshoot-api-timeout\")\n title: Article title\n body: Article body in Markdown\n category?: Category (default: \"general\")\n tags?: Array of tags for search\n public?: Make publicly accessible (default: false)\n sourceTicketId?: Ticket this article was created from\n- Returns: { id, title, category, path }\n\n### backup_now({ remote?, note? })\nTrigger an immediate backup of all CRM data (customers/ + .agentic/).\nCreates a timestamped ZIP with SHA-256 manifest. Optionally uploads to S3/rsync/local.\nRBAC: admin\n- Input: { remote?: string, note?: string }\n- Returns: { path, createdAt, customerCount, fileCount, sizeMb, directories, verified, uploadedTo? }\n\n### list_backups({ limit? })\nList available CRM backups with metadata. Shows log-tracked backups first, falls back to directory scan.\n- Input: { limit?: number (default 10, max 50) }\n- Returns: { count, totalAvailable, backups: [{ filename, createdAt, sizeMb, verified, encrypted, customerCount, fileCount }] }\n\n## Data Structure\n\nCustomer data lives in \\`customers/<slug>/\\`:\n- \\`main_facts.md\\` — YAML frontmatter + free-text sections\n- \\`interactions.md\\` — Chronological log (newest first)\n- \\`pipeline.md\\` — Deal table in Markdown\n- \\`sources.json\\` — Gmail/transcript sync config per customer\n\nAgentic data lives in \\`.agentic/\\`:\n- \\`goals.json\\` — Active goals and decompositions\n- \\`push-subscriptions.json\\` — Real-time push registrations\n- \\`backup-log.json\\` — Backup history\n- \\`rbac.json\\` — Role assignments\n- \\`audit.log\\` — Full audit trail\n- \\`knowledge-base/\\` — KB articles\n- \\`quotes/\\` — Generated quote files\n\n## Response Format\n\nAlways cite sources (gmail://thread/... or file://...) when available.\n\n## Framework Integration\n\n| Framework | Tier | Config |\n|---|---|---|\n| Claude Code | 1 | CLAUDE.md + ~/.claude.json + .claude/settings.json |\n| Codex CLI | 1 | AGENTS.md + ~/.codex/config.toml |\n| Grok Build (xAI) | 1 | AGENTS.md + ~/.grok/user-settings.json + .grok/settings.json |\n| OpenClaw | 1 | SOUL.md + AGENTS.md + TOOLS.md |\n| Hermes Agent | 1 | SOUL.md + Skill |\n| Antigravity CLI | 1 | GEMINI.md + AGENTS.md + SKILL.md |\n| Cursor | 2 | .cursor/rules/datasynx-crm.mdc |\n| Windsurf | 2 | MCP config only |\n| Cline | 2 | MCP config only |\n| Claude Desktop | 2 | MCP config only |\n\n### Manual Grok Build configuration\n\\`\\`\\`json\n// ~/.grok/user-settings.json (mcpServers is an ARRAY in Grok, not a map)\n{\n \"mcpServers\": [\n {\n \"name\": \"datasynx-opencrm\",\n \"transport\": {\n \"type\": \"stdio\",\n \"command\": \"node\",\n \"args\": [\"/path/to/node_modules/datasynx-opencrm/dist/mcp.js\"],\n \"env\": { \"DXCRM_DATA_DIR\": \"~/.dxcrm\" }\n }\n }\n ]\n}\n\\`\\`\\`\nRun \\`grok inspect\\` to verify the server is discovered.\n\n## CLI Reference (Phase 2)\n\n### dxcrm status\nShow daemon status, customer count, sync ages, and unmatched transcript queue.\n\\`\\`\\`\ndxcrm status\ndxcrm status --unmatched # list full unmatched transcript queue\n\\`\\`\\`\n\n### dxcrm agent spawn <slug>\nSpawn a wake-triggered agent for a customer. Sends Telegram notifications on new email.\n\\`\\`\\`\ndxcrm agent spawn acme-corp --channel telegram\ndxcrm agent spawn acme-corp --channel telegram --chat-id 12345\ndxcrm agent status\ndxcrm agent remove acme-corp\n\\`\\`\\`\nRequires: \\`TELEGRAM_BOT_TOKEN\\` + \\`TELEGRAM_CHAT_ID\\` env vars.\n\n### dxcrm import <path>\nImport customers and interactions from HubSpot or generic CSV export.\n\\`\\`\\`\ndxcrm import contacts.csv --from csv\ndxcrm import hubspot-export.csv --from hubspot\ndxcrm import hubspot-export.csv --from hubspot --dry-run\n\\`\\`\\`\n- Two-pass: creates customers first, then imports activities\n- Idempotent: re-running skips already-imported rows\n- sourceRef format: \\`hubspot://activity/<id>\\` or \\`csv://row/<hash>\\`\n\n## CLI Reference (Phase 3 — Team)\n\n### dxcrm server start\nStart a shared HTTP MCP server. Multiple team members connect via URL.\n\\`\\`\\`\ndxcrm server start --data /mnt/crm-data --port 3847\ndxcrm server status\n\\`\\`\\`\nSet actor identity: \\`export DXCRM_ACTOR=alice\\`\n\n### dxcrm audit\nShow who changed what and when. Audit trail at \\`.agentic/audit.log\\`.\n\\`\\`\\`\ndxcrm audit # Last 20 entries\ndxcrm audit --slug acme-corp # Filter by customer\ndxcrm audit --actor alice # Filter by actor\n\\`\\`\\`\nLog format: \\`2026-06-01T09:14:00Z | alice | log_interaction | acme-corp | summary\\`\n\n### session ownership\n\\`\\`\\`\ndxcrm session open acme-corp --owner alice\n# or: DXCRM_ACTOR=alice dxcrm session open acme-corp\n\\`\\`\\`\n\\`get_active_session()\\` returns \\`{ owner: \"alice\", ... }\\` when owner is set.\n\n## CLI Reference (Phase 5 — Migration)\n\n### dxcrm import — Pipedrive API\n\\`\\`\\`\ndxcrm import --from pipedrive --mode api --token <tok> --url https://myco.pipedrive.com\n\\`\\`\\`\nTwo-pass: persons → customers, activities → interactions.\nsourceRef: \\`pipedrive://activity/<id>\\`\n\n### CSV LLM Field Mapping\nGeneric CSV imports now use LLM-assisted column detection (fallback to heuristics when ANTHROPIC_API_KEY is unset).\n\n## CLI Reference (Enterprise — Sprints R1–R5)\n\n### dxcrm stages\nManage custom pipeline stages.\n\\`\\`\\`\ndxcrm stages list # List all stages\ndxcrm stages set <id> <label> [--order N] [--probability N] [--color #hex] [--final]\ndxcrm stages delete <id> # Remove a stage\ndxcrm stages reset # Reset to defaults\n\\`\\`\\`\nDefault stages: lead → qualified → proposal → negotiation → won → lost\n\n### dxcrm rbac\nRole-based access control. Roles: admin > manager > rep.\n\\`\\`\\`\ndxcrm rbac set alice admin # Assign role\ndxcrm rbac show # List all roles\ndxcrm rbac check alice update_deal # Permission check\n\\`\\`\\`\nConfig: \\`.agentic/rbac.json\\` | Actor: \\`DXCRM_ACTOR\\` env var\nEnforcement: per MCP tool call | Default role: rep\n\n### dxcrm gdpr\nGDPR erasure with dry-run safety and audit trail.\n\\`\\`\\`\ndxcrm gdpr erase acme-corp # Dry-run (shows plan)\ndxcrm gdpr erase acme-corp --confirm # Permanent deletion\ndxcrm gdpr list-erasures # Erasure history\n\\`\\`\\`\nOn confirm: deletes customers/<slug>/, writes audit.log, appends gdpr-erasures.json\n\n### dxcrm security-report\nGenerate Markdown security questionnaire for procurement/SOC2 review.\n\\`\\`\\`\ndxcrm security-report\ndxcrm security-report --output sec-report.md\n\\`\\`\\`\n\n### Microsoft Outlook Sync\n\\`\\`\\`\ndxcrm sync --provider microsoft\n\\`\\`\\`\nPrerequisites: write \\`.agentic/microsoft-token.json\\` with \\`{ \"accessToken\": \"...\" }\\`\nsourceRef: \\`microsoft://message/<id>\\` | API: Microsoft Graph v1.0\n\n### Salesforce Import\n\\`\\`\\`\ndxcrm import --from salesforce --mode api --token <tok> --url https://myco.salesforce.com\n\\`\\`\\`\nTwo-pass: contacts → customers, tasks → interactions (WhoId attribution)\nsourceRef: \\`salesforce://task/<id>\\` | API: Salesforce REST v58.0\n\n## CLI Reference (D16 — Goal-Based Orchestration)\n\n### dxcrm goal set\nSet a goal and get a decomposed action plan based on current pipeline state.\n\\`\\`\\`\ndxcrm goal set \"Close €500k ARR this quarter\" --deadline 2026-09-30\n\\`\\`\\`\n\n### dxcrm goal status\nShow all active goals with progress bars and days remaining.\n\\`\\`\\`\ndxcrm goal status\n\\`\\`\\`\n\n### dxcrm goal update\nManually update goal progress (0–100%).\n\\`\\`\\`\ndxcrm goal update goal_abc123 --progress 45\n\\`\\`\\`\n\n### dxcrm goal cancel\nCancel an active goal.\n\\`\\`\\`\ndxcrm goal cancel goal_abc123\n\\`\\`\\`\n\n## CLI Reference (D17 — Real-Time Push Ingestion)\n\n### dxcrm push register\nRegister a push subscription so providers send events in real-time (no polling).\n\\`\\`\\`\ndxcrm push register acme-corp --provider gmail --webhook-url https://myserver.com/webhooks/gmail --topic-name projects/x/topics/gmail-push\ndxcrm push register acme-corp --provider microsoft-graph --webhook-url https://myserver.com/webhooks/microsoft --client-state <secret>\ndxcrm push register acme-corp --provider slack --webhook-url https://myserver.com/webhooks/slack --team-id T12345\n\\`\\`\\`\n\n### dxcrm push status\nShow all push subscriptions, expiry and events processed.\n\\`\\`\\`\ndxcrm push status\ndxcrm push status --slug acme-corp\ndxcrm push status --provider gmail\n\\`\\`\\`\n\n### dxcrm push revoke\nRevoke a push subscription by ID.\n\\`\\`\\`\ndxcrm push revoke psub_1716892800_a1b2c3\n\\`\\`\\`\n\n### dxcrm push renew\nRenew expiring push subscriptions (also runs automatically daily at 06:00).\n\\`\\`\\`\ndxcrm push renew --all\n\\`\\`\\`\n\n### register_push_subscription (MCP)\nRegister a real-time push subscription. Admin only.\n\\`\\`\\`\nregister_push_subscription({ provider: \"gmail\", slug: \"acme-corp\", webhookUrl: \"https://myserver.com/webhooks/gmail\", gmailTopicName: \"projects/x/topics/y\" })\n\\`\\`\\`\nReturns: { subscriptionId, provider, slug, status, expiresAt, warning? }\n\n### get_push_status (MCP)\nShow all push subscriptions with expiry and event counts.\n\\`\\`\\`\nget_push_status() // all subscriptions\nget_push_status({ slug: \"acme-corp\" }) // filter by customer\nget_push_status({ provider: \"gmail\" }) // filter by provider\n\\`\\`\\`\nReturns: { subscriptions: [...], summary: { total, active, expiringSoon, expired } }\n\n### get_org_intelligence (MCP)\nBuild a stakeholder map for a customer: champions, economic buyers, blockers, health scores, risk flags, and a prioritised recommendation.\n\\`\\`\\`\nget_org_intelligence({ slug: \"acme-corp\" })\nget_org_intelligence({ slug: \"acme-corp\", dealName: \"Enterprise License\" })\n\\`\\`\\`\nReturns: { slug, updatedAt, people: [{ name, email, role, healthScore, daysSinceContact, contactStrength, riskFlags }], missingRoles, riskAssessment, recommendation }\n\n### open_deal_room (MCP)\nMulti-agent deal brief: orchestrates stakeholder map, relationship health, deal health, Monte Carlo simulation, and playbook matching into a single structured brief.\n\\`\\`\\`\nopen_deal_room({ slug: \"acme-corp\", dealName: \"Enterprise License 2026\" })\n\\`\\`\\`\nReturns: { slug, dealName, generatedAt, stakeholders, relationshipHealth, dealHealth, revenueSimulation, recommendedPlaybook, executiveSummary, topPriorities, riskScore }\n\n### get_proactive_briefing (MCP)\nGenerate a proactive daily briefing: urgent alerts (relationship decay, imminent close dates), opportunities (high-health customers with active pipeline), P50/P90 forecast, and a single top-action recommendation.\n\\`\\`\\`\nget_proactive_briefing() // today\nget_proactive_briefing({ date: \"2026-05-28\" }) // specific date\n\\`\\`\\`\nReturns: { date, generatedAt, urgent: string[], opportunities: string[], forecast: string, topAction: string }\n\n## H2 — Email Templates\n\n### list_email_templates (MCP)\nList all saved email templates. Returns id, name, category, subject, and body preview.\n\\`\\`\\`\nlist_email_templates()\nlist_email_templates({ category: \"follow-up\" })\n\\`\\`\\`\nReturns: { templates: [{ id, name, category, subject, bodyPreview }] }\n\n### get_email_template (MCP)\nRetrieve a single email template with full body and all variables.\n\\`\\`\\`\nget_email_template({ id: \"proposal-follow-up\" })\n\\`\\`\\`\nReturns: { id, name, category, subject, body, variables: string[] }\n\n### draft_email (MCP)\nDraft a personalized email from a template, substituting variables from customer context.\n\\`\\`\\`\ndraft_email({ slug: \"acme-corp\", templateId: \"proposal-follow-up\", overrides: { subject: \"Following up on your proposal\" } })\n\\`\\`\\`\nReturns: { subject, body, suggestedTo, suggestedCc?, variables }\n\n## H1 — Email Sequences\n\n### enroll_in_sequence (MCP)\nEnroll a customer contact in a multi-step email sequence. Steps are sent automatically.\n\\`\\`\\`\nenroll_in_sequence({ slug: \"acme-corp\", sequenceId: \"onboarding-7day\", contactEmail: \"alice@acme.com\" })\n\\`\\`\\`\nReturns: { enrollmentId, slug, sequenceId, contactEmail, enrolledAt, nextStepDue, totalSteps }\n\n### list_sequence_enrollments (MCP)\nList active (and optionally completed) sequence enrollments.\n\\`\\`\\`\nlist_sequence_enrollments()\nlist_sequence_enrollments({ slug: \"acme-corp\", status: \"active\" })\n\\`\\`\\`\nReturns: { enrollments: [{ enrollmentId, slug, sequenceId, contactEmail, currentStep, nextStepDue, status }] }\n\n### unenroll_from_sequence (MCP)\nRemove a contact from an active sequence (marks as cancelled).\n\\`\\`\\`\nunenroll_from_sequence({ enrollmentId: \"enr_abc123\" })\n\\`\\`\\`\nReturns: { success: boolean, enrollmentId }\n\n### list_sequences (MCP)\nList all defined email sequences with step count and description.\n\\`\\`\\`\nlist_sequences()\n\\`\\`\\`\nReturns: { sequences: [{ id, name, description, steps: number, triggerOn? }] }\n\n## H4 — Quotes\n\n### generate_quote (MCP)\nGenerate a structured quote document for a customer deal.\n\\`\\`\\`\ngenerate_quote({ slug: \"acme-corp\", dealName: \"Enterprise License\", lineItems: [{ description: \"Platform (12 mo)\", quantity: 1, unitPrice: 24000 }], validDays: 30 })\n\\`\\`\\`\nReturns: { quoteId, slug, dealName, total, currency, validUntil, markdownTable, fullText }\n\n### get_quote_status (MCP)\nRetrieve a generated quote with full line items and total.\n\\`\\`\\`\nget_quote_status({ quoteId: \"Q-2026-001\" })\n\\`\\`\\`\nReturns: { quoteId, slug, dealName, lineItems, subtotal, total, validUntil, status }\n\n## H3 — Meeting Scheduler\n\n### get_booking_link (MCP)\nGet a scheduling link for a meeting with a customer. Configure via DXCRM_CALENDLY_URL or per-customer sources.json.\n\\`\\`\\`\nget_booking_link({ slug: \"acme-corp\", meetingType: \"demo\" })\n\\`\\`\\`\nReturns: { url, meetingType, calendarProvider, prefillEmail?, note? }\n\n## H6 — Ticket Management\n\n### create_ticket (MCP)\nCreate a support ticket. Auto-sets SLA due date: critical=4h, high=24h, medium=72h, low=168h.\n\\`\\`\\`\ncreate_ticket({ slug: \"acme-corp\", title: \"Login broken\", priority: \"high\", description: \"Cannot login since yesterday\", assignee: \"alice\" })\n\\`\\`\\`\nReturns: { ticketId, slug, title, priority, status, slaDue, assignee?, createdAt }\n\n### update_ticket (MCP)\nUpdate ticket status or assignee.\n\\`\\`\\`\nupdate_ticket({ slug: \"acme-corp\", ticketId: \"T-001\", status: \"in-progress\", assignee: \"bob\" })\n\\`\\`\\`\nReturns: { ticketId, status, updatedAt }\n\n### list_tickets (MCP)\nList tickets sorted by priority. Filter by customer, status, priority, or assignee.\n\\`\\`\\`\nlist_tickets()\nlist_tickets({ slug: \"acme-corp\", status: \"open\" })\nlist_tickets({ priority: \"high\", assignee: \"alice\" })\n\\`\\`\\`\nReturns: { tickets: [{ ticketId, slug, title, priority, status, slaDue, assignee?, createdAt }] }\n\n### close_ticket (MCP)\nClose a ticket and optionally log a resolution note to interactions.md.\n\\`\\`\\`\nclose_ticket({ slug: \"acme-corp\", ticketId: \"T-001\", resolution: \"Fixed by updating oauth token\" })\n\\`\\`\\`\nReturns: { ticketId, status: \"closed\", closedAt, resolution? }\n\n## H7 — NPS/CSAT Survey Engine\n\n### send_nps_survey (MCP)\nGenerate a survey token and HTML email body. Customers click a score button (0–10) which\nposts to your server's /survey/respond endpoint. Set DXCRM_SERVER_URL or pass serverUrl.\n\\`\\`\\`\nsend_nps_survey({ slug: \"acme-corp\", contactEmail: \"alice@acme.com\", surveyId: \"q1-nps\" })\nsend_nps_survey({ slug: \"acme-corp\", contactEmail: \"alice@acme.com\", surveyId: \"q1-nps\", serverUrl: \"https://crm.myco.com\" })\n\\`\\`\\`\nReturns: { token, emailSubject, emailBody (HTML), surveyId, expiresAt }\n\n### get_survey_results (MCP)\nCalculate NPS score and breakdown by promoter/passive/detractor.\n\\`\\`\\`\nget_survey_results({ surveyId: \"q1-nps\" })\nget_survey_results({ surveyId: \"q1-nps\", slug: \"acme-corp\" })\n\\`\\`\\`\nReturns: { surveyId, npsScore (-100 to 100), responseCount, promoters, passives, detractors, responses: [{ slug, contactEmail, score, comment?, respondedAt }] }\n\n## H8 — Knowledge Base\n\n### search_knowledge_base (MCP)\nFull-text search across all KB articles (title, body, tags).\n\\`\\`\\`\nsearch_knowledge_base({ query: \"password reset\" })\nsearch_knowledge_base({ query: \"billing\", publicOnly: true })\n\\`\\`\\`\nReturns: { results: [{ id, title, category, excerpt, public, tags }] }\n\n### create_kb_article (MCP)\nCreate or update a knowledge base article (upserts by ID).\n\\`\\`\\`\ncreate_kb_article({ id: \"password-reset\", title: \"How to reset your password\", body: \"## Steps\\\\n1. Go to login...\", category: \"account\", tags: [\"password\", \"auth\"], public: true })\n\\`\\`\\`\nReturns: { id, title, createdAt, updatedAt, public }\n\n## Enterprise Backup\n\n### backup_now (MCP)\nTrigger an immediate backup of customers/ + .agentic/. Creates a timestamped ZIP with\nSHA-256 manifest. Optionally encrypts (AES-256-GCM) and uploads to S3/rsync/local.\n\\`\\`\\`\nbackup_now({})\nbackup_now({ remote: \"s3://my-bucket/crm-backups/\", note: \"Pre-migration backup\" })\n\\`\\`\\`\nReturns: { path, createdAt, customerCount, fileCount, sizeMb, directories, verified, uploadedTo? }\n\n### list_backups (MCP)\nList available CRM backups with metadata from .agentic/backup-log.json.\nFalls back to directory scan if log unavailable.\n\\`\\`\\`\nlist_backups({ limit: 10 })\n\\`\\`\\`\nReturns: { count, totalAvailable, backups: [{ filename, createdAt, sizeMb, verified, encrypted, customerCount, fileCount }] }\n\n### trigger_sync (MCP)\nForce an immediate Gmail sync without waiting for the 30-minute daemon cycle.\n\\`\\`\\`\ntrigger_sync({ slug: \"acme-corp\" }) // sync one customer\ntrigger_sync({}) // sync all customers\ntrigger_sync({ since: \"2026-06-01\" }) // sync from specific date\n\\`\\`\\`\nReturns: { success, synced, skipped, customers: [...], errors: [...] }\n\n### get_audit_log (MCP)\nRead the append-only CRM audit log of all write operations.\n\\`\\`\\`\nget_audit_log({}) // last 50 entries\nget_audit_log({ slug: \"acme-corp\" }) // filtered by customer\nget_audit_log({ actor: \"alice\", limit: 20 }) // filtered by actor\n\\`\\`\\`\nReturns: { total, returned, entries: [{ timestamp, actor, tool, slug, summary }] }\n`.trim();\n","import { z } from \"zod\";\n\nexport const KbArticleSchema = z.object({\n id: z.string().min(1),\n title: z.string().min(1),\n category: z.string().default(\"general\"),\n tags: z.array(z.string()).default([]),\n public: z.boolean().default(false),\n createdAt: z.string(),\n updatedAt: z.string(),\n sourceTicketId: z.string().optional(),\n});\n\nexport type KbArticleMeta = z.infer<typeof KbArticleSchema>;\nexport type KbArticle = KbArticleMeta & { body: string };\n","import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\nimport { KbArticleSchema, type KbArticle, type KbArticleMeta } from \"../schemas/kb-article.js\";\n\nexport function kbDir(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"knowledge-base\");\n}\n\nexport function listKbArticles(\n dataDir: string,\n opts?: { category?: string; publicOnly?: boolean }\n): KbArticle[] {\n const dir = kbDir(dataDir);\n if (!fs.existsSync(dir)) return [];\n\n const results: KbArticle[] = [];\n const categories = fs.readdirSync(dir).filter((f) => {\n try {\n return fs.statSync(path.join(dir, f)).isDirectory();\n } catch {\n return false;\n }\n });\n\n for (const cat of categories) {\n const catDir = path.join(dir, cat);\n const files = fs.readdirSync(catDir).filter((f) => f.endsWith(\".md\"));\n for (const file of files) {\n try {\n const content = fs.readFileSync(path.join(catDir, file), \"utf-8\") as string;\n const parsed = matter(content);\n const meta = KbArticleSchema.safeParse(parsed.data);\n if (!meta.success) continue;\n if (opts?.category && meta.data.category !== opts.category) continue;\n if (opts?.publicOnly && !meta.data.public) continue;\n results.push({ ...meta.data, body: parsed.content.trim() });\n } catch {\n continue;\n }\n }\n }\n\n return results;\n}\n\nexport function getKbArticle(dataDir: string, id: string): KbArticle | null {\n const all = listKbArticles(dataDir);\n return all.find((a) => a.id === id) ?? null;\n}\n\nexport function writeKbArticle(dataDir: string, article: KbArticle): void {\n const dir = path.join(kbDir(dataDir), article.category);\n fs.mkdirSync(dir, { recursive: true });\n const { body, ...meta } = article;\n const content = matter.stringify(body, meta as Record<string, unknown>);\n fs.writeFileSync(path.join(dir, `${article.id}.md`), content, \"utf-8\");\n}\n\nexport function deleteKbArticle(dataDir: string, id: string): boolean {\n const all = listKbArticles(dataDir);\n const article = all.find((a) => a.id === id);\n if (!article) return false;\n const p = path.join(kbDir(dataDir), article.category, `${id}.md`);\n if (!fs.existsSync(p)) return false;\n fs.unlinkSync(p);\n return true;\n}\n\nexport function searchKbSimple(\n dataDir: string,\n query: string,\n opts?: { publicOnly?: boolean }\n): KbArticle[] {\n const all = listKbArticles(dataDir, opts?.publicOnly ? { publicOnly: true } : {});\n const lower = query.toLowerCase();\n return all.filter(\n (a) =>\n a.title.toLowerCase().includes(lower) ||\n a.body.toLowerCase().includes(lower) ||\n a.tags.some((t) => t.toLowerCase().includes(lower))\n );\n}\n\nexport function getKbMetaForExport(article: KbArticle): KbArticleMeta {\n const { body: _body, ...meta } = article;\n return meta;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lancedb-rlvWoPwl.js","names":["ArrowFloat32"],"sources":["../src/core/embedder.ts","../src/core/lancedb.ts"],"sourcesContent":["import { pipeline, env, type FeatureExtractionPipeline } from \"@huggingface/transformers\";\nimport path from \"path\";\nimport os from \"os\";\n\nenv.cacheDir =\n process.env[\"HF_CACHE_DIR\"] ?? path.join(os.homedir(), \".cache\", \"datasynx-opencrm\", \"models\");\n\nclass EmbeddingPipeline {\n private static instance: Promise<FeatureExtractionPipeline> | null = null;\n\n static get(): Promise<FeatureExtractionPipeline> {\n if (!this.instance) {\n console.error(\"Loading embedding model (first time, ~25MB)...\");\n this.instance = pipeline(\n \"feature-extraction\",\n \"Xenova/all-MiniLM-L6-v2\"\n ) as Promise<FeatureExtractionPipeline>;\n }\n return this.instance;\n }\n\n static reset(): void {\n this.instance = null;\n }\n}\n\nexport async function embedText(text: string): Promise<Float32Array> {\n const extractor = await EmbeddingPipeline.get();\n const output = await extractor(text, { pooling: \"mean\", normalize: true });\n return (output as unknown as Array<{ data: Float32Array }>)[0]?.data ?? new Float32Array(384);\n}\n\nexport async function embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n const extractor = await EmbeddingPipeline.get();\n const output = await extractor(texts, { pooling: \"mean\", normalize: true });\n return (output as unknown as Array<{ data: Float32Array }>).map(\n (o) => o.data ?? new Float32Array(384)\n );\n}\n\nexport function resetEmbeddingPipeline(): void {\n EmbeddingPipeline.reset();\n}\n","import * as lancedb from \"@lancedb/lancedb\";\nimport { Index } from \"@lancedb/lancedb\";\nimport { makeArrowTable } from \"@lancedb/lancedb\";\nimport { Schema, Field, FixedSizeList, Float32 as ArrowFloat32, Utf8 } from \"apache-arrow\";\nimport path from \"path\";\nimport { embedText } from \"./embedder.js\";\n\nlet _db: lancedb.Connection | null = null;\n\nasync function getDb(dataDir: string): Promise<lancedb.Connection> {\n if (!_db) {\n const dbPath = path.join(dataDir, \".agentic\", \"lancedb\");\n _db = await lancedb.connect(dbPath);\n }\n return _db;\n}\n\n// Reset connection (useful for testing)\nexport function resetConnection(): void {\n _db = null;\n}\n\nconst CUSTOMER_TABLE_SCHEMA = new Schema([\n new Field(\"source_ref\", new Utf8(), false),\n new Field(\"text\", new Utf8(), false),\n new Field(\"date\", new Utf8(), false),\n new Field(\"type\", new Utf8(), false),\n new Field(\"vector\", new FixedSizeList(384, new Field(\"item\", new ArrowFloat32(), true)), false),\n]);\n\nasync function getOrCreateCustomerTable(\n db: lancedb.Connection,\n tableName: string\n): Promise<lancedb.Table> {\n const tableNames: string[] = await db.tableNames();\n if (!tableNames.includes(tableName)) {\n const table = await db.createEmptyTable(tableName, CUSTOMER_TABLE_SCHEMA);\n await table.createIndex(\"source_ref\", { config: Index.btree() });\n return table;\n }\n return db.openTable(tableName);\n}\n\nexport async function indexInLanceDB(\n dataDir: string,\n slug: string,\n text: string,\n sourceRef: string,\n meta?: { date?: string; type?: string }\n): Promise<void> {\n try {\n const vectorFloat32 = await embedText(text);\n const db = await getDb(dataDir);\n const tableName = `docs_${slug.replace(/[^a-z0-9]/gi, \"_\")}`;\n const table = await getOrCreateCustomerTable(db, tableName);\n\n const date = meta?.date ?? new Date().toISOString().slice(0, 10);\n const type = meta?.type ?? \"unknown\";\n\n const data = makeArrowTable([\n {\n source_ref: sourceRef,\n text: text.slice(0, 2000),\n date,\n type,\n vector: Array.from(vectorFloat32),\n },\n ]);\n\n await table\n .mergeInsert(\"source_ref\")\n .whenMatchedUpdateAll()\n .whenNotMatchedInsertAll()\n .execute(data);\n } catch (err) {\n process.stderr.write(`[lancedb] indexInLanceDB failed: ${(err as Error).message}\\n`);\n }\n}\n\nexport async function dropCustomerTable(dataDir: string, slug: string): Promise<void> {\n try {\n const db = await getDb(dataDir);\n const tableName = `docs_${slug.replace(/[^a-z0-9]/gi, \"_\")}`;\n const tableNames: string[] = await db.tableNames();\n if (tableNames.includes(tableName)) {\n await db.dropTable(tableName);\n }\n } catch (err) {\n process.stderr.write(`[lancedb] dropCustomerTable failed: ${(err as Error).message}\\n`);\n }\n}\n\nexport async function searchKnowledge(\n dataDir: string,\n slug: string,\n query: string,\n limit: number\n): Promise<Array<{ content: string; score: number; source: string }>> {\n try {\n const vectorFloat32 = await embedText(query);\n const db = await getDb(dataDir);\n const tableName = `docs_${slug.replace(/[^a-z0-9]/gi, \"_\")}`;\n\n // Check if table exists\n const tableNames: string[] = await db.tableNames();\n if (!tableNames.includes(tableName)) {\n return [];\n }\n\n const table = await db.openTable(tableName);\n\n const results = await table.search(Array.from(vectorFloat32)).limit(limit).toArray();\n\n return results.map((r: Record<string, unknown>) => ({\n content: String(r[\"text\"] ?? \"\"),\n score: typeof r[\"_distance\"] === \"number\" ? 1 - r[\"_distance\"] : 1,\n source: String(r[\"source_ref\"] ?? \"\"),\n }));\n } catch {\n // If LanceDB table doesn't exist or search fails, return empty array\n return [];\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lead-model-BCFzyktm.js","names":[],"sources":["../src/core/lead-model.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { readPipelineSync } from \"../fs/pipeline-writer.js\";\nimport { listCustomerSlugs } from \"../fs/customer-dir.js\";\nimport { scoreOpportunity } from \"./opportunity-score.js\";\nimport type { PipelineDeal } from \"../schemas/pipeline.js\";\n\n/**\n * Predictive lead/opportunity scoring (domino D14 / C8): a dependency-free\n * logistic-regression model learned from the workspace's own won/lost history.\n * Features are deliberately simple and always available at prediction time —\n * deal value (log-scaled) and stated probability — so the model stays explainable\n * and needs no external ML runtime (that lives in the agent framework, not here).\n * Falls back to the deterministic heuristic when there isn't enough history.\n */\nexport interface LeadModel {\n weights: number[];\n bias: number;\n mean: number[];\n std: number[];\n trainedOn: number;\n sufficient: boolean;\n}\n\nconst MODEL_FILE = \".agentic/lead-model.json\";\nconst FEATURES = 2; // [log10(value+1), probability/100]\n\nfunction modelPath(dataDir: string): string {\n return path.join(dataDir, MODEL_FILE);\n}\n\nfunction features(deal: PipelineDeal): number[] {\n return [Math.log10((deal.value ?? 0) + 1), (deal.probability ?? 0) / 100];\n}\n\nfunction sigmoid(z: number): number {\n return 1 / (1 + Math.exp(-z));\n}\n\n/** Read every customer's closed (won/lost) deals as labelled training rows. */\nexport function gatherTrainingDeals(dataDir: string): Array<{ x: number[]; y: number }> {\n const rows: Array<{ x: number[]; y: number }> = [];\n for (const slug of listCustomerSlugs(dataDir)) {\n let deals: PipelineDeal[] = [];\n try {\n deals = readPipelineSync(dataDir, slug);\n } catch {\n deals = [];\n }\n for (const d of deals) {\n if (d.stage === \"won\") rows.push({ x: features(d), y: 1 });\n else if (d.stage === \"lost\") rows.push({ x: features(d), y: 0 });\n }\n }\n return rows;\n}\n\n/** Train a logistic-regression model from the workspace's won/lost history. */\nexport function buildLeadModel(dataDir: string): LeadModel {\n const rows = gatherTrainingDeals(dataDir);\n const classes = new Set(rows.map((r) => r.y));\n const sufficient = rows.length >= 4 && classes.size === 2;\n\n const empty: LeadModel = {\n weights: new Array(FEATURES).fill(0),\n bias: 0,\n mean: new Array(FEATURES).fill(0),\n std: new Array(FEATURES).fill(1),\n trainedOn: rows.length,\n sufficient,\n };\n if (!sufficient) return empty;\n\n // Standardize features for stable gradient descent.\n const mean = new Array(FEATURES).fill(0);\n for (const r of rows) for (let j = 0; j < FEATURES; j++) mean[j] += r.x[j]! / rows.length;\n const std = new Array(FEATURES).fill(0);\n for (const r of rows)\n for (let j = 0; j < FEATURES; j++) std[j] += (r.x[j]! - mean[j]) ** 2 / rows.length;\n for (let j = 0; j < FEATURES; j++) std[j] = Math.sqrt(std[j]) || 1;\n\n const norm = rows.map((r) => ({\n x: r.x.map((v, j) => (v - mean[j]) / std[j]),\n y: r.y,\n }));\n\n const weights = new Array(FEATURES).fill(0);\n let bias = 0;\n const lr = 0.1;\n const iterations = 2000;\n for (let it = 0; it < iterations; it++) {\n const gradW = new Array(FEATURES).fill(0);\n let gradB = 0;\n for (const r of norm) {\n const z = bias + weights.reduce((acc, w, j) => acc + w * r.x[j]!, 0);\n const err = sigmoid(z) - r.y;\n for (let j = 0; j < FEATURES; j++) gradW[j] += (err * r.x[j]!) / norm.length;\n gradB += err / norm.length;\n }\n for (let j = 0; j < FEATURES; j++) weights[j] -= lr * gradW[j];\n bias -= lr * gradB;\n }\n\n return { weights, bias, mean, std, trainedOn: rows.length, sufficient: true };\n}\n\n/** Predict win probability (0–1). Falls back to the heuristic when untrained. */\nexport function predictWin(model: LeadModel, deal: PipelineDeal): number {\n if (!model.sufficient) return scoreOpportunity(deal).score / 100;\n const x = features(deal).map((v, j) => (v - model.mean[j]!) / model.std[j]!);\n const z = model.bias + model.weights.reduce((acc, w, j) => acc + w * x[j]!, 0);\n return sigmoid(z);\n}\n\nexport function saveLeadModel(dataDir: string, model: LeadModel): void {\n const p = modelPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify(model, null, 2), \"utf-8\");\n}\n\nexport function loadLeadModel(dataDir: string): LeadModel | null {\n const p = modelPath(dataDir);\n if (!fs.existsSync(p)) return null;\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as LeadModel;\n } catch {\n return null;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-Bb6ky3kb.js","names":[],"sources":["../src/core/memory.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { hybridSearch } from \"./hybrid-search.js\";\n\n/**\n * Agent memory (domino D6 / F4): persistent, typed memories per customer and\n * global, retrievable via hybrid search. Injected into context so the agent\n * gets durably smarter across every interaction (CoALA: semantic/procedural).\n */\nexport type MemoryType = \"fact\" | \"preference\" | \"learning\" | \"instruction\";\n\nexport interface MemoryEntry {\n id: string;\n scope: \"global\" | \"customer\";\n slug?: string;\n type: MemoryType;\n text: string;\n confidence?: number;\n createdAt: string;\n}\n\nfunction globalPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"memory\", \"global.json\");\n}\nfunction customerPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug, \"memory.json\");\n}\n\nfunction readFile(p: string): MemoryEntry[] {\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as { memories?: MemoryEntry[] };\n return Array.isArray(data.memories) ? data.memories : [];\n } catch {\n return [];\n }\n}\nfunction writeFile(p: string, memories: MemoryEntry[]): void {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ memories }, null, 2), \"utf-8\");\n}\n\nexport function addMemory(\n dataDir: string,\n m: {\n scope: \"global\" | \"customer\";\n slug?: string;\n type: MemoryType;\n text: string;\n confidence?: number;\n }\n): MemoryEntry {\n const entry: MemoryEntry = {\n id: `mem_${randomBytes(5).toString(\"hex\")}`,\n scope: m.scope,\n ...(m.slug ? { slug: m.slug } : {}),\n type: m.type,\n text: m.text,\n ...(typeof m.confidence === \"number\" ? { confidence: m.confidence } : {}),\n createdAt: new Date().toISOString(),\n };\n const p = m.scope === \"global\" ? globalPath(dataDir) : customerPath(dataDir, m.slug ?? \"\");\n writeFile(p, [...readFile(p), entry]);\n return entry;\n}\n\n/** Load memories: global always; plus the customer's own when a slug is given. */\nexport function loadMemories(dataDir: string, slug?: string): MemoryEntry[] {\n const global = readFile(globalPath(dataDir));\n if (!slug) return global;\n return [...global, ...readFile(customerPath(dataDir, slug))];\n}\n\n/** Search memories by relevance (hybrid keyword ranking over memory texts). */\nexport async function searchMemory(\n dataDir: string,\n query: string,\n slug?: string,\n limit = 5\n): Promise<MemoryEntry[]> {\n const memories = loadMemories(dataDir, slug);\n const docs = memories.map((m) => ({ id: m.id, text: m.text }));\n const ranked = hybridSearch(query, docs, { limit });\n const byId = new Map(memories.map((m) => [m.id, m]));\n return ranked.map((r) => byId.get(r.id)!).filter(Boolean);\n}\n"],"mappings":";;;;;AAsBA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,UAAU,aAAa;AAC/D;AACA,SAAS,aAAa,SAAiB,MAAsB;CAC3D,OAAO,KAAK,KAAK,SAAS,aAAa,MAAM,aAAa;AAC5D;AAEA,SAAS,SAAS,GAA0B;CAC1C,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,QAAQ,IAAI,KAAK,WAAW,CAAC;CACzD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AACA,SAAS,UAAU,GAAW,UAA+B;CAC3D,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,EAAE,SAAS,GAAG,MAAM,CAAC,GAAG,OAAO;AACpE;AAEA,SAAgB,UACd,SACA,GAOa;CACb,MAAM,QAAqB;EACzB,IAAI,OAAO,YAAY,CAAC,EAAE,SAAS,KAAK;EACxC,OAAO,EAAE;EACT,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,MAAM,EAAE;EACR,MAAM,EAAE;EACR,GAAI,OAAO,EAAE,eAAe,WAAW,EAAE,YAAY,EAAE,WAAW,IAAI,CAAC;EACvE,4BAAW,IAAI,KAAK,GAAE,YAAY;CACpC;CACA,MAAM,IAAI,EAAE,UAAU,WAAW,WAAW,OAAO,IAAI,aAAa,SAAS,EAAE,QAAQ,EAAE;CACzF,UAAU,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC;CACpC,OAAO;AACT;;AAGA,SAAgB,aAAa,SAAiB,MAA8B;CAC1E,MAAM,SAAS,SAAS,WAAW,OAAO,CAAC;CAC3C,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,CAAC,GAAG,QAAQ,GAAG,SAAS,aAAa,SAAS,IAAI,CAAC,CAAC;AAC7D;;AAGA,eAAsB,aACpB,SACA,OACA,MACA,QAAQ,GACgB;CACxB,MAAM,WAAW,aAAa,SAAS,IAAI;CAE3C,MAAM,SAAS,aAAa,OADf,SAAS,KAAK,OAAO;EAAE,IAAI,EAAE;EAAI,MAAM,EAAE;CAAK,EACrB,GAAG,EAAE,MAAM,CAAC;CAClD,MAAM,OAAO,IAAI,IAAI,SAAS,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACnD,OAAO,OAAO,KAAK,MAAM,KAAK,IAAI,EAAE,EAAE,CAAE,EAAE,OAAO,OAAO;AAC1D"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"notification-dispatcher-0vYNngWe.js","names":[],"sources":["../src/core/notification-dispatcher.ts"],"sourcesContent":["// src/core/notification-dispatcher.ts\n// Drains agent-queue.json and dispatches pending tasks to Telegram, Slack, or\n// marks them done for mcp_tool_response (consumed by get_proactive_briefing).\nimport https from \"https\";\nimport { readQueue, markTaskDone, type AgentTask } from \"./proactive-agent.js\";\n\n// ─── Transport helpers ────────────────────────────────────────────────────────\n\nexport async function sendTelegram(token: string, chatId: string, text: string): Promise<void> {\n const body = JSON.stringify({ chat_id: chatId, text, parse_mode: \"Markdown\" });\n await 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\nexport async function sendSlack(webhookUrl: string, text: string): Promise<void> {\n const body = JSON.stringify({ text });\n const url = new URL(webhookUrl);\n await new Promise<void>((resolve, reject) => {\n const req = https.request(\n {\n hostname: url.hostname,\n path: url.pathname + url.search,\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 formatting ───────────────────────────────────────────────────────\n\nexport function formatTaskMessage(task: AgentTask): string {\n const payload = task.payload as Record<string, unknown>;\n const slug = task.slug ?? \"\";\n\n switch (task.type) {\n case \"daily_briefing\": {\n const b = payload as { urgent?: string[]; forecast?: string; topAction?: string };\n const lines: string[] = [\"📋 *Daily CRM Briefing*\", \"\"];\n if (b.urgent?.length) {\n lines.push(\"🚨 *Urgent:*\");\n b.urgent.slice(0, 3).forEach((u) => lines.push(`• ${u}`));\n lines.push(\"\");\n }\n if (b.forecast) lines.push(`📊 ${b.forecast}`);\n if (b.topAction) lines.push(`\\n⚡ *Top Action:* ${b.topAction}`);\n return lines.join(\"\\n\");\n }\n case \"relationship_decay_alert\":\n return `⚠️ *Relationship Alert: ${slug}*\\n${String(payload[\"name\"] ?? \"\")} — ${String(payload[\"daysSinceContact\"] ?? \"?\")} days silent, grade ${String(payload[\"grade\"] ?? \"?\")}`;\n case \"deal_risk_alert\":\n return `🔴 *Deal Risk: ${slug}*\\n\"${String(payload[\"dealName\"] ?? \"\")}\" closes in ${String(payload[\"daysToClose\"] ?? \"?\")} days`;\n case \"external_signal_alert\":\n return `💡 *Signal: ${slug}*\\n${String(payload[\"summary\"] ?? \"\")}`;\n case \"follow_up_nudge\":\n return `📞 *Follow-up: ${slug}*\\n${String(payload[\"message\"] ?? \"\")}`;\n default:\n return `📌 CRM Task (${task.type})\\n${JSON.stringify(payload).slice(0, 200)}`;\n }\n}\n\n// ─── Queue drain ──────────────────────────────────────────────────────────────\n\nexport interface DrainResult {\n sent: number;\n failed: number;\n}\n\nexport async function drainProactiveQueue(dataDir: string): Promise<DrainResult> {\n const token = process.env[\"TELEGRAM_BOT_TOKEN\"];\n const chatId = process.env[\"TELEGRAM_CHAT_ID\"];\n const slackUrl = process.env[\"SLACK_WEBHOOK_URL\"];\n\n const tasks = readQueue(dataDir).filter((t) => t.status === \"pending\");\n let sent = 0;\n let failed = 0;\n\n for (const task of tasks) {\n const message = formatTaskMessage(task);\n try {\n if (task.channel === \"telegram\" && token && chatId) {\n await sendTelegram(token, chatId, message);\n } else if (task.channel === \"slack\" && slackUrl) {\n await sendSlack(slackUrl, message);\n }\n // mcp_tool_response tasks: consumed by get_proactive_briefing — just mark done\n await markTaskDone(dataDir, task.id, \"dispatched\");\n sent++;\n } catch (err) {\n failed++;\n process.stderr.write(`[dispatch] Task ${task.id} failed: ${(err as Error).message}\\n`);\n }\n }\n\n return { sent, failed };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-writer-BvVquKIe.js","names":[],"sources":["../src/schemas/pipeline.ts","../src/fs/pipeline-writer.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const PipelineDealSchema = z.object({\n name: z.string().min(1),\n stage: z.enum([\"lead\", \"qualified\", \"proposal\", \"negotiation\", \"won\", \"lost\"]),\n value: z.number().optional(),\n currency: z.string().default(\"EUR\"),\n probability: z.number().min(0).max(100).optional(),\n close_date: z\n .string()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\")\n .optional(),\n notes: z.string().optional(),\n updated: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\"),\n});\n\nexport type PipelineDeal = z.infer<typeof PipelineDealSchema>;\n","import fs from \"fs\";\nimport path from \"path\";\nimport { PipelineDealSchema, type PipelineDeal } from \"../schemas/pipeline.js\";\n\nconst PIPELINE_HEADER = \"# Pipeline\\n\\n\";\nconst TABLE_HEADER = `| Name | Stage | Value | Currency | Probability | Close Date | Notes | Updated |\n|------|-------|-------|----------|-------------|------------|-------|---------|`;\n\nfunction escapeMd(val: string | undefined | null): string {\n if (val === undefined || val === null) return \"\";\n return String(val).replace(/\\|/g, \"\\\\|\");\n}\n\nfunction serializeDeal(deal: PipelineDeal): string {\n return `| ${escapeMd(deal.name)} | ${escapeMd(deal.stage)} | ${deal.value !== undefined ? String(deal.value) : \"\"} | ${escapeMd(deal.currency)} | ${deal.probability !== undefined ? String(deal.probability) : \"\"} | ${escapeMd(deal.close_date)} | ${escapeMd(deal.notes)} | ${escapeMd(deal.updated)} |`;\n}\n\nfunction parseDealsFromMarkdown(content: string): PipelineDeal[] {\n const lines = content.split(\"\\n\");\n const deals: PipelineDeal[] = [];\n\n let inTable = false;\n let headerParsed = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Look for the table header row\n if (!inTable && trimmed.startsWith(\"| Name |\")) {\n inTable = true;\n headerParsed = false;\n continue;\n }\n\n if (inTable && !headerParsed && trimmed.startsWith(\"|---\")) {\n headerParsed = true;\n continue;\n }\n\n if (inTable && headerParsed && trimmed.startsWith(\"|\")) {\n // Parse a data row\n const cells = trimmed\n .split(\"|\")\n .slice(1, -1)\n .map((c) => c.trim().replace(/\\\\\\|/g, \"|\"));\n\n const [name, stage, valueStr, currency, probabilityStr, close_date, notes, updated] =\n cells as [string, string, string, string, string, string, string, string];\n\n if (!name || !stage || !updated) continue;\n\n const raw: Record<string, unknown> = {\n name,\n stage,\n currency: currency || \"EUR\",\n updated,\n };\n\n if (valueStr) raw[\"value\"] = parseFloat(valueStr);\n if (probabilityStr) raw[\"probability\"] = parseFloat(probabilityStr);\n if (close_date) raw[\"close_date\"] = close_date;\n if (notes) raw[\"notes\"] = notes;\n\n const result = PipelineDealSchema.safeParse(raw);\n if (result.success) {\n deals.push(result.data);\n }\n } else if (inTable && !trimmed.startsWith(\"|\")) {\n // End of table\n inTable = false;\n }\n }\n\n return deals;\n}\n\nfunction serializeDeals(deals: PipelineDeal[]): string {\n if (deals.length === 0) {\n return `${PIPELINE_HEADER}<!-- Deals listed here -->\\n`;\n }\n const rows = deals.map(serializeDeal).join(\"\\n\");\n return `${PIPELINE_HEADER}${TABLE_HEADER}\\n${rows}\\n`;\n}\n\nexport function readPipelineSync(dataDir: string, slug: string): PipelineDeal[] {\n const filePath = path.join(dataDir, \"customers\", slug, \"pipeline.md\");\n if (!fs.existsSync(filePath)) {\n return [];\n }\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n return parseDealsFromMarkdown(content);\n}\n\nexport async function readPipeline(dataDir: string, slug: string): Promise<PipelineDeal[]> {\n return readPipelineSync(dataDir, slug);\n}\n\nexport async function upsertDeal(dataDir: string, slug: string, deal: PipelineDeal): Promise<void> {\n const filePath = path.join(dataDir, \"customers\", slug, \"pipeline.md\");\n const existing = await readPipeline(dataDir, slug);\n\n const idx = existing.findIndex((d) => d.name === deal.name);\n let updated: PipelineDeal[];\n if (idx >= 0) {\n updated = [...existing];\n updated[idx] = deal;\n } else {\n updated = [...existing, deal];\n }\n\n const content = serializeDeals(updated);\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-writer-N2omexxp.cjs","names":["z"],"sources":["../src/schemas/pipeline.ts","../src/fs/pipeline-writer.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const PipelineDealSchema = z.object({\n name: z.string().min(1),\n stage: z.enum([\"lead\", \"qualified\", \"proposal\", \"negotiation\", \"won\", \"lost\"]),\n value: z.number().optional(),\n currency: z.string().default(\"EUR\"),\n probability: z.number().min(0).max(100).optional(),\n close_date: z\n .string()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\")\n .optional(),\n notes: z.string().optional(),\n updated: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\"),\n});\n\nexport type PipelineDeal = z.infer<typeof PipelineDealSchema>;\n","import fs from \"fs\";\nimport path from \"path\";\nimport { PipelineDealSchema, type PipelineDeal } from \"../schemas/pipeline.js\";\n\nconst PIPELINE_HEADER = \"# Pipeline\\n\\n\";\nconst TABLE_HEADER = `| Name | Stage | Value | Currency | Probability | Close Date | Notes | Updated |\n|------|-------|-------|----------|-------------|------------|-------|---------|`;\n\nfunction escapeMd(val: string | undefined | null): string {\n if (val === undefined || val === null) return \"\";\n return String(val).replace(/\\|/g, \"\\\\|\");\n}\n\nfunction serializeDeal(deal: PipelineDeal): string {\n return `| ${escapeMd(deal.name)} | ${escapeMd(deal.stage)} | ${deal.value !== undefined ? String(deal.value) : \"\"} | ${escapeMd(deal.currency)} | ${deal.probability !== undefined ? String(deal.probability) : \"\"} | ${escapeMd(deal.close_date)} | ${escapeMd(deal.notes)} | ${escapeMd(deal.updated)} |`;\n}\n\nfunction parseDealsFromMarkdown(content: string): PipelineDeal[] {\n const lines = content.split(\"\\n\");\n const deals: PipelineDeal[] = [];\n\n let inTable = false;\n let headerParsed = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Look for the table header row\n if (!inTable && trimmed.startsWith(\"| Name |\")) {\n inTable = true;\n headerParsed = false;\n continue;\n }\n\n if (inTable && !headerParsed && trimmed.startsWith(\"|---\")) {\n headerParsed = true;\n continue;\n }\n\n if (inTable && headerParsed && trimmed.startsWith(\"|\")) {\n // Parse a data row\n const cells = trimmed\n .split(\"|\")\n .slice(1, -1)\n .map((c) => c.trim().replace(/\\\\\\|/g, \"|\"));\n\n const [name, stage, valueStr, currency, probabilityStr, close_date, notes, updated] =\n cells as [string, string, string, string, string, string, string, string];\n\n if (!name || !stage || !updated) continue;\n\n const raw: Record<string, unknown> = {\n name,\n stage,\n currency: currency || \"EUR\",\n updated,\n };\n\n if (valueStr) raw[\"value\"] = parseFloat(valueStr);\n if (probabilityStr) raw[\"probability\"] = parseFloat(probabilityStr);\n if (close_date) raw[\"close_date\"] = close_date;\n if (notes) raw[\"notes\"] = notes;\n\n const result = PipelineDealSchema.safeParse(raw);\n if (result.success) {\n deals.push(result.data);\n }\n } else if (inTable && !trimmed.startsWith(\"|\")) {\n // End of table\n inTable = false;\n }\n }\n\n return deals;\n}\n\nfunction serializeDeals(deals: PipelineDeal[]): string {\n if (deals.length === 0) {\n return `${PIPELINE_HEADER}<!-- Deals listed here -->\\n`;\n }\n const rows = deals.map(serializeDeal).join(\"\\n\");\n return `${PIPELINE_HEADER}${TABLE_HEADER}\\n${rows}\\n`;\n}\n\nexport function readPipelineSync(dataDir: string, slug: string): PipelineDeal[] {\n const filePath = path.join(dataDir, \"customers\", slug, \"pipeline.md\");\n if (!fs.existsSync(filePath)) {\n return [];\n }\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n return parseDealsFromMarkdown(content);\n}\n\nexport async function readPipeline(dataDir: string, slug: string): Promise<PipelineDeal[]> {\n return readPipelineSync(dataDir, slug);\n}\n\nexport async function upsertDeal(dataDir: string, slug: string, deal: PipelineDeal): Promise<void> {\n const filePath = path.join(dataDir, \"customers\", slug, \"pipeline.md\");\n const existing = await readPipeline(dataDir, slug);\n\n const idx = existing.findIndex((d) => d.name === deal.name);\n let updated: PipelineDeal[];\n if (idx >= 0) {\n updated = [...existing];\n updated[idx] = deal;\n } else {\n updated = [...existing, deal];\n }\n\n const content = serializeDeals(updated);\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n"],"mappings":";;;;;;;AAEA,MAAa,qBAAqBA,IAAAA,EAAE,OAAO;CACzC,MAAMA,IAAAA,EAAE,OAAO,EAAE,IAAI,CAAC;CACtB,OAAOA,IAAAA,EAAE,KAAK;EAAC;EAAQ;EAAa;EAAY;EAAe;EAAO;CAAM,CAAC;CAC7E,OAAOA,IAAAA,EAAE,OAAO,EAAE,SAAS;CAC3B,UAAUA,IAAAA,EAAE,OAAO,EAAE,QAAQ,KAAK;CAClC,aAAaA,IAAAA,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;CACjD,YAAYA,IAAAA,EACT,OAAO,EACP,MAAM,uBAAuB,qBAAqB,EAClD,SAAS;CACZ,OAAOA,IAAAA,EAAE,OAAO,EAAE,SAAS;CAC3B,SAASA,IAAAA,EAAE,OAAO,EAAE,MAAM,uBAAuB,qBAAqB;AACxE,CAAC;;;;;;;;ACVD,MAAM,kBAAkB;AACxB,MAAM,eAAe;;AAGrB,SAAS,SAAS,KAAwC;CACxD,IAAI,QAAQ,KAAA,KAAa,QAAQ,MAAM,OAAO;CAC9C,OAAO,OAAO,GAAG,EAAE,QAAQ,OAAO,KAAK;AACzC;AAEA,SAAS,cAAc,MAA4B;CACjD,OAAO,KAAK,SAAS,KAAK,IAAI,EAAE,KAAK,SAAS,KAAK,KAAK,EAAE,KAAK,KAAK,UAAU,KAAA,IAAY,OAAO,KAAK,KAAK,IAAI,GAAG,KAAK,SAAS,KAAK,QAAQ,EAAE,KAAK,KAAK,gBAAgB,KAAA,IAAY,OAAO,KAAK,WAAW,IAAI,GAAG,KAAK,SAAS,KAAK,UAAU,EAAE,KAAK,SAAS,KAAK,KAAK,EAAE,KAAK,SAAS,KAAK,OAAO,EAAE;AAC1S;AAEA,SAAS,uBAAuB,SAAiC;CAC/D,MAAM,QAAQ,QAAQ,MAAM,IAAI;CAChC,MAAM,QAAwB,CAAC;CAE/B,IAAI,UAAU;CACd,IAAI,eAAe;CAEnB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,KAAK;EAG1B,IAAI,CAAC,WAAW,QAAQ,WAAW,UAAU,GAAG;GAC9C,UAAU;GACV,eAAe;GACf;EACF;EAEA,IAAI,WAAW,CAAC,gBAAgB,QAAQ,WAAW,MAAM,GAAG;GAC1D,eAAe;GACf;EACF;EAEA,IAAI,WAAW,gBAAgB,QAAQ,WAAW,GAAG,GAAG;GAOtD,MAAM,CAAC,MAAM,OAAO,UAAU,UAAU,gBAAgB,YAAY,OAAO,WAL7D,QACX,MAAM,GAAG,EACT,MAAM,GAAG,EAAE,EACX,KAAK,MAAM,EAAE,KAAK,EAAE,QAAQ,SAAS,GAAG,CAGrC;GAEN,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS;GAEjC,MAAM,MAA+B;IACnC;IACA;IACA,UAAU,YAAY;IACtB;GACF;GAEA,IAAI,UAAU,IAAI,WAAW,WAAW,QAAQ;GAChD,IAAI,gBAAgB,IAAI,iBAAiB,WAAW,cAAc;GAClE,IAAI,YAAY,IAAI,gBAAgB;GACpC,IAAI,OAAO,IAAI,WAAW;GAE1B,MAAM,SAAS,mBAAmB,UAAU,GAAG;GAC/C,IAAI,OAAO,SACT,MAAM,KAAK,OAAO,IAAI;EAE1B,OAAO,IAAI,WAAW,CAAC,QAAQ,WAAW,GAAG,GAE3C,UAAU;CAEd;CAEA,OAAO;AACT;AAEA,SAAS,eAAe,OAA+B;CACrD,IAAI,MAAM,WAAW,GACnB,OAAO,GAAG,gBAAgB;CAG5B,OAAO,GAAG,kBAAkB,aAAa,IAD5B,MAAM,IAAI,aAAa,EAAE,KAAK,IACK,EAAE;AACpD;AAEA,SAAgB,iBAAiB,SAAiB,MAA8B;CAC9E,MAAM,WAAW,KAAA,QAAK,KAAK,SAAS,aAAa,MAAM,aAAa;CACpE,IAAI,CAAC,GAAA,QAAG,WAAW,QAAQ,GACzB,OAAO,CAAC;CAGV,OAAO,uBADS,GAAA,QAAG,aAAa,UAAU,OACN,CAAC;AACvC;AAEA,eAAsB,aAAa,SAAiB,MAAuC;CACzF,OAAO,iBAAiB,SAAS,IAAI;AACvC;AAEA,eAAsB,WAAW,SAAiB,MAAc,MAAmC;CACjG,MAAM,WAAW,KAAA,QAAK,KAAK,SAAS,aAAa,MAAM,aAAa;CACpE,MAAM,WAAW,MAAM,aAAa,SAAS,IAAI;CAEjD,MAAM,MAAM,SAAS,WAAW,MAAM,EAAE,SAAS,KAAK,IAAI;CAC1D,IAAI;CACJ,IAAI,OAAO,GAAG;EACZ,UAAU,CAAC,GAAG,QAAQ;EACtB,QAAQ,OAAO;CACjB,OACE,UAAU,CAAC,GAAG,UAAU,IAAI;CAG9B,MAAM,UAAU,eAAe,OAAO;CACtC,GAAA,QAAG,cAAc,UAAU,SAAS,OAAO;AAC7C"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-writer-eufx_0o1.js","names":[],"sources":["../src/schemas/pipeline.ts","../src/fs/pipeline-writer.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const PipelineDealSchema = z.object({\n name: z.string().min(1),\n stage: z.enum([\"lead\", \"qualified\", \"proposal\", \"negotiation\", \"won\", \"lost\"]),\n value: z.number().optional(),\n currency: z.string().default(\"EUR\"),\n probability: z.number().min(0).max(100).optional(),\n close_date: z\n .string()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\")\n .optional(),\n notes: z.string().optional(),\n updated: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\"),\n});\n\nexport type PipelineDeal = z.infer<typeof PipelineDealSchema>;\n","import fs from \"fs\";\nimport path from \"path\";\nimport { PipelineDealSchema, type PipelineDeal } from \"../schemas/pipeline.js\";\n\nconst PIPELINE_HEADER = \"# Pipeline\\n\\n\";\nconst TABLE_HEADER = `| Name | Stage | Value | Currency | Probability | Close Date | Notes | Updated |\n|------|-------|-------|----------|-------------|------------|-------|---------|`;\n\nfunction escapeMd(val: string | undefined | null): string {\n if (val === undefined || val === null) return \"\";\n return String(val).replace(/\\|/g, \"\\\\|\");\n}\n\nfunction serializeDeal(deal: PipelineDeal): string {\n return `| ${escapeMd(deal.name)} | ${escapeMd(deal.stage)} | ${deal.value !== undefined ? String(deal.value) : \"\"} | ${escapeMd(deal.currency)} | ${deal.probability !== undefined ? String(deal.probability) : \"\"} | ${escapeMd(deal.close_date)} | ${escapeMd(deal.notes)} | ${escapeMd(deal.updated)} |`;\n}\n\nfunction parseDealsFromMarkdown(content: string): PipelineDeal[] {\n const lines = content.split(\"\\n\");\n const deals: PipelineDeal[] = [];\n\n let inTable = false;\n let headerParsed = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Look for the table header row\n if (!inTable && trimmed.startsWith(\"| Name |\")) {\n inTable = true;\n headerParsed = false;\n continue;\n }\n\n if (inTable && !headerParsed && trimmed.startsWith(\"|---\")) {\n headerParsed = true;\n continue;\n }\n\n if (inTable && headerParsed && trimmed.startsWith(\"|\")) {\n // Parse a data row\n const cells = trimmed\n .split(\"|\")\n .slice(1, -1)\n .map((c) => c.trim().replace(/\\\\\\|/g, \"|\"));\n\n const [name, stage, valueStr, currency, probabilityStr, close_date, notes, updated] =\n cells as [string, string, string, string, string, string, string, string];\n\n if (!name || !stage || !updated) continue;\n\n const raw: Record<string, unknown> = {\n name,\n stage,\n currency: currency || \"EUR\",\n updated,\n };\n\n if (valueStr) raw[\"value\"] = parseFloat(valueStr);\n if (probabilityStr) raw[\"probability\"] = parseFloat(probabilityStr);\n if (close_date) raw[\"close_date\"] = close_date;\n if (notes) raw[\"notes\"] = notes;\n\n const result = PipelineDealSchema.safeParse(raw);\n if (result.success) {\n deals.push(result.data);\n }\n } else if (inTable && !trimmed.startsWith(\"|\")) {\n // End of table\n inTable = false;\n }\n }\n\n return deals;\n}\n\nfunction serializeDeals(deals: PipelineDeal[]): string {\n if (deals.length === 0) {\n return `${PIPELINE_HEADER}<!-- Deals listed here -->\\n`;\n }\n const rows = deals.map(serializeDeal).join(\"\\n\");\n return `${PIPELINE_HEADER}${TABLE_HEADER}\\n${rows}\\n`;\n}\n\nexport function readPipelineSync(dataDir: string, slug: string): PipelineDeal[] {\n const filePath = path.join(dataDir, \"customers\", slug, \"pipeline.md\");\n if (!fs.existsSync(filePath)) {\n return [];\n }\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n return parseDealsFromMarkdown(content);\n}\n\nexport async function readPipeline(dataDir: string, slug: string): Promise<PipelineDeal[]> {\n return readPipelineSync(dataDir, slug);\n}\n\nexport async function upsertDeal(dataDir: string, slug: string, deal: PipelineDeal): Promise<void> {\n const filePath = path.join(dataDir, \"customers\", slug, \"pipeline.md\");\n const existing = await readPipeline(dataDir, slug);\n\n const idx = existing.findIndex((d) => d.name === deal.name);\n let updated: PipelineDeal[];\n if (idx >= 0) {\n updated = [...existing];\n updated[idx] = deal;\n } else {\n updated = [...existing, deal];\n }\n\n const content = serializeDeals(updated);\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"proactive-worker-BrLHNhjH.js","names":[],"sources":["../src/sync/external-signals.ts","../src/daemon/proactive-worker.ts"],"sourcesContent":["// src/sync/external-signals.ts\n// External signal detection: Hacker News (free), Crunchbase (key optional),\n// Clearbit (key optional). Non-fatal on network errors — CRM works offline.\nimport https from \"https\";\nimport fs from \"fs\";\nimport path from \"path\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type SignalType =\n | \"funding_round\"\n | \"leadership_change\"\n | \"layoffs\"\n | \"acquisition\"\n | \"expansion\"\n | \"product_launch\"\n | \"news_mention\";\n\nexport type SignalImpact = \"positive\" | \"negative\" | \"neutral\";\n\nexport interface ExternalSignal {\n id: string;\n slug: string;\n source: \"hacker_news\" | \"crunchbase\" | \"clearbit\" | \"rss\";\n type: SignalType;\n summary: string;\n url?: string;\n detectedAt: string;\n impact: SignalImpact;\n}\n\n// ─── File I/O ─────────────────────────────────────────────────────────────────\n\nexport function signalsDir(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug, \"signals\");\n}\n\nexport function signalsFilePath(dataDir: string, slug: string, date: string): string {\n return path.join(signalsDir(dataDir, slug), `${date}.json`);\n}\n\nexport function readSignals(dataDir: string, slug: string, date: string): ExternalSignal[] {\n const p = signalsFilePath(dataDir, slug, date);\n if (!fs.existsSync(p)) return [];\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as ExternalSignal[];\n } catch {\n return [];\n }\n}\n\nexport function writeSignals(\n dataDir: string,\n slug: string,\n date: string,\n signals: ExternalSignal[]\n): void {\n const dir = signalsDir(dataDir, slug);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(signalsFilePath(dataDir, slug, date), JSON.stringify(signals, null, 2), \"utf-8\");\n}\n\n// ─── HTTP helper ──────────────────────────────────────────────────────────────\n\nexport async function fetchJson<T>(url: string, headers: Record<string, string> = {}): Promise<T> {\n return new Promise((resolve, reject) => {\n const u = new URL(url);\n const options = {\n hostname: u.hostname,\n path: u.pathname + u.search,\n method: \"GET\",\n headers: { \"User-Agent\": \"datasynx-opencrm/2.0\", ...headers },\n };\n const req = https.request(options, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk: Buffer) => {\n data += chunk.toString();\n });\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(data) as T);\n } catch (e) {\n reject(e);\n }\n });\n });\n req.on(\"error\", reject);\n req.setTimeout(5000, () => {\n req.destroy();\n reject(new Error(\"timeout\"));\n });\n req.end();\n });\n}\n\n// ─── Hacker News / Algolia API (free, no key needed) ─────────────────────────\n\ninterface HNHit {\n objectID: string;\n title?: string;\n story_title?: string;\n url?: string;\n created_at: string;\n}\n\ninterface HNSearchResult {\n hits: HNHit[];\n}\n\nexport async function checkCompanyNews(\n domain: string,\n companyName: string\n): Promise<ExternalSignal[]> {\n const signals: ExternalSignal[] = [];\n try {\n const query = encodeURIComponent(companyName.split(\" \")[0] ?? domain.split(\".\")[0] ?? \"\");\n const since = Math.floor(Date.now() / 1000) - 30 * 86400;\n const url = `https://hn.algolia.com/api/v1/search?query=${query}&tags=story&numericFilters=created_at_i>${since}&hitsPerPage=5`;\n const result = await fetchJson<HNSearchResult>(url);\n\n for (const hit of result.hits ?? []) {\n const title = hit.title ?? hit.story_title ?? \"\";\n if (!title.toLowerCase().includes(query.toLowerCase())) continue;\n\n const titleLc = title.toLowerCase();\n const type: SignalType = titleLc.includes(\"fund\")\n ? \"funding_round\"\n : titleLc.includes(\"acqui\")\n ? \"acquisition\"\n : titleLc.includes(\"lay\") || titleLc.includes(\"reduc\")\n ? \"layoffs\"\n : \"news_mention\";\n\n const impact: SignalImpact =\n type === \"funding_round\" || type === \"acquisition\"\n ? \"positive\"\n : type === \"layoffs\"\n ? \"negative\"\n : \"neutral\";\n\n signals.push({\n id: `hn_${hit.objectID}`,\n slug: \"\",\n source: \"hacker_news\",\n type,\n summary: title,\n url: hit.url ?? `https://news.ycombinator.com/item?id=${hit.objectID}`,\n detectedAt: new Date().toISOString(),\n impact,\n });\n }\n } catch {\n // Network errors are non-fatal\n }\n return signals;\n}\n\n// ─── Crunchbase Basic API (CRUNCHBASE_API_KEY optional) ──────────────────────\n\ninterface CrunchbaseOrg {\n properties?: {\n short_description?: string;\n funding_total?: { value_usd?: number };\n last_funding_type?: string;\n };\n}\n\nexport async function checkFundingEvents(domain: string): Promise<ExternalSignal[]> {\n const apiKey = process.env[\"CRUNCHBASE_API_KEY\"];\n if (!apiKey) return [];\n\n const signals: ExternalSignal[] = [];\n try {\n const orgName = domain.split(\".\")[0] ?? domain;\n const url = `https://api.crunchbase.com/api/v4/entities/organizations/${orgName}?field_ids=short_description,funding_total,last_funding_type&user_key=${apiKey}`;\n const result = await fetchJson<{ data?: CrunchbaseOrg }>(url);\n const props = result.data?.properties;\n\n if (props?.last_funding_type && props?.funding_total?.value_usd) {\n const millions = (props.funding_total.value_usd / 1_000_000).toFixed(1);\n signals.push({\n id: `cb_${domain}_${Date.now()}`,\n slug: \"\",\n source: \"crunchbase\",\n type: \"funding_round\",\n summary: `${domain} raised funding (${props.last_funding_type}, $${millions}M total)`,\n detectedAt: new Date().toISOString(),\n impact: \"positive\",\n });\n }\n } catch {\n // Non-fatal\n }\n return signals;\n}\n\n// ─── Clearbit enrichment (CLEARBIT_API_KEY optional) ─────────────────────────\n\ninterface ClearbitPerson {\n name?: { fullName?: string };\n employment?: { title?: string; name?: string };\n}\n\nexport async function enrichContact(\n email: string\n): Promise<{ name?: string; title?: string; company?: string } | null> {\n const apiKey = process.env[\"CLEARBIT_API_KEY\"];\n if (!apiKey) return null;\n\n try {\n const url = `https://person.clearbit.com/v2/combined/find?email=${encodeURIComponent(email)}`;\n const result = await fetchJson<ClearbitPerson>(url, {\n Authorization: `Bearer ${apiKey}`,\n });\n return {\n ...(result.name?.fullName ? { name: result.name.fullName } : {}),\n ...(result.employment?.title ? { title: result.employment.title } : {}),\n ...(result.employment?.name ? { company: result.employment.name } : {}),\n };\n } catch {\n return null;\n }\n}\n\n// ─── Main entry point ─────────────────────────────────────────────────────────\n\nexport async function fetchSignalsForCustomer(\n dataDir: string,\n slug: string,\n domain: string,\n companyName: string,\n today: string\n): Promise<ExternalSignal[]> {\n const [newsSignals, fundingSignals] = await Promise.all([\n checkCompanyNews(domain, companyName),\n checkFundingEvents(domain),\n ]);\n\n const signals: ExternalSignal[] = [\n ...newsSignals.map((s) => ({ ...s, slug })),\n ...fundingSignals.map((s) => ({ ...s, slug })),\n ];\n\n if (signals.length > 0) {\n writeSignals(dataDir, slug, today, signals);\n }\n\n return signals;\n}\n","// src/daemon/proactive-worker.ts\n// Daily proactive checks: relationship decay + deal risk + daily briefing.\n// Called from worker.ts CronJob at 07:00. Enqueues tasks to agent-queue.json.\n// Queue draining (Telegram/Slack dispatch) is handled by G3 / notification-dispatcher.\nimport fs from \"fs\";\nimport path from \"path\";\nimport { computeCustomerHealth, readHealth } from \"../core/relationship-health.js\";\nimport { readPipeline } from \"../fs/pipeline-writer.js\";\nimport {\n buildDailyBriefing,\n enqueueTask,\n type NotificationChannel,\n} from \"../core/proactive-agent.js\";\nimport { fetchSignalsForCustomer } from \"../sync/external-signals.js\";\nimport { runScheduledBackupIfDue } from \"../commands/backup.js\";\n\nconst MAX_CUSTOMERS_PER_CYCLE = 50;\n\nfunction defaultChannel(): NotificationChannel {\n if (process.env[\"TELEGRAM_BOT_TOKEN\"] && process.env[\"TELEGRAM_CHAT_ID\"]) return \"telegram\";\n if (process.env[\"SLACK_WEBHOOK_URL\"]) return \"slack\";\n return \"mcp_tool_response\";\n}\n\nexport interface ProactiveCheckResult {\n today: string;\n customersChecked: number;\n tasksEnqueued: number;\n errors: string[];\n}\n\nexport async function runDailyProactiveChecks(\n dataDir: string,\n today: string = new Date().toISOString().slice(0, 10)\n): Promise<ProactiveCheckResult> {\n const result: ProactiveCheckResult = { today, customersChecked: 0, tasksEnqueued: 0, errors: [] };\n const channel = defaultChannel();\n\n const customersDir = path.join(dataDir, \"customers\");\n const slugs = fs.existsSync(customersDir)\n ? fs\n .readdirSync(customersDir)\n .filter((s) => {\n try {\n return fs.statSync(path.join(customersDir, s)).isDirectory();\n } catch {\n return false;\n }\n })\n .slice(0, MAX_CUSTOMERS_PER_CYCLE)\n : [];\n\n const todayMs = new Date(`${today}T00:00:00Z`).getTime();\n\n await Promise.all(\n slugs.map(async (slug) => {\n try {\n // Relationship health — use cached snapshot if fresh, compute otherwise\n const health = readHealth(dataDir, slug) ?? computeCustomerHealth(dataDir, slug, today);\n\n for (const contact of health.contacts) {\n const isDecayed = contact.riskFlags.includes(\"NO_CONTACT_30D\") || contact.grade === \"F\";\n\n if (isDecayed) {\n await enqueueTask(dataDir, {\n type: \"relationship_decay_alert\",\n slug,\n priority: contact.grade === \"F\" ? \"urgent\" : \"high\",\n payload: {\n contactId: contact.contactId,\n name: contact.name,\n email: contact.email,\n daysSinceContact: contact.daysSinceContact,\n grade: contact.grade,\n riskFlags: contact.riskFlags,\n },\n scheduledFor: new Date().toISOString(),\n channel,\n });\n result.tasksEnqueued++;\n }\n }\n\n // Deal risk — close date within 7 days or already overdue\n const deals = await readPipeline(dataDir, slug).catch(() => []);\n for (const deal of deals) {\n if (deal.stage === \"won\" || deal.stage === \"lost\") continue;\n if (!deal.close_date?.trim()) continue;\n\n const daysToClose = Math.floor(\n (new Date(deal.close_date).getTime() - todayMs) / 86_400_000\n );\n\n if (daysToClose <= 7) {\n await enqueueTask(dataDir, {\n type: \"deal_risk_alert\",\n slug,\n priority: daysToClose < 0 ? \"urgent\" : \"high\",\n payload: {\n dealName: deal.name,\n stage: deal.stage,\n value: deal.value,\n closeDate: deal.close_date,\n daysToClose,\n overdue: daysToClose < 0,\n },\n scheduledFor: new Date().toISOString(),\n channel,\n });\n result.tasksEnqueued++;\n }\n }\n\n // External signals — read domain/name from main_facts if available\n try {\n const factsPath = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n if (fs.existsSync(factsPath)) {\n const raw = fs.readFileSync(factsPath, \"utf-8\");\n const domainMatch = raw.match(/^domain:\\s*(.+)$/im);\n const nameMatch = raw.match(/^name:\\s*(.+)$/im);\n const domain = domainMatch?.[1]?.trim();\n const companyName = nameMatch?.[1]?.trim() ?? slug;\n if (domain) {\n const signals = await fetchSignalsForCustomer(\n dataDir,\n slug,\n domain,\n companyName,\n today\n );\n for (const signal of signals) {\n if (signal.impact === \"neutral\") continue;\n await enqueueTask(dataDir, {\n type: \"external_signal_alert\",\n slug,\n priority: signal.impact === \"negative\" ? \"urgent\" : \"high\",\n payload: signal,\n scheduledFor: new Date().toISOString(),\n channel,\n });\n result.tasksEnqueued++;\n }\n }\n }\n } catch {\n // External signals are best-effort — never block the rest of the cycle\n }\n\n result.customersChecked++;\n } catch (err) {\n result.errors.push(`${slug}: ${(err as Error).message}`);\n }\n })\n );\n\n // Daily briefing — always enqueue, agents consume via get_proactive_briefing\n try {\n const briefing = await buildDailyBriefing(dataDir, today);\n await enqueueTask(dataDir, {\n type: \"daily_briefing\",\n priority: \"normal\",\n payload: briefing,\n scheduledFor: new Date().toISOString(),\n channel,\n });\n result.tasksEnqueued++;\n } catch (err) {\n result.errors.push(`daily_briefing: ${(err as Error).message}`);\n }\n\n // Scheduled backup — fire-and-forget, non-blocking\n runScheduledBackupIfDue(dataDir).catch(() => {\n // non-critical — proactive cycle must not fail due to backup errors\n });\n\n return result;\n}\n"],"mappings":"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"}
|