@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":"push-manager-CdqIIkuh.js","names":[],"sources":["../src/sync/push-manager.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\nexport type PushProvider = \"gmail\" | \"microsoft-graph\" | \"slack\";\nexport type PushStatus = \"active\" | \"expired\" | \"revoked\" | \"error\" | \"permanently_failed\";\n\nexport interface PushSubscription {\n id: string;\n provider: PushProvider;\n slug: string;\n webhookUrl: string;\n expiresAt: string | null;\n renewedAt: string | null;\n createdAt: string;\n providerData: {\n gmailHistoryId?: string;\n gmailTopicName?: string;\n gmailLabelIds?: string[];\n gmailEmailAddress?: string;\n microsoftSubscriptionId?: string;\n microsoftResource?: string;\n microsoftClientState?: string;\n slackTeamId?: string;\n slackChannelId?: string;\n slackBotToken?: string;\n };\n status: PushStatus;\n lastEventAt: string | null;\n eventsProcessed: number;\n renewFailures?: number;\n}\n\ninterface PushSubscriptionsFile {\n subscriptions: PushSubscription[];\n updatedAt: string;\n}\n\nexport function makePushSubId(): string {\n return `psub_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;\n}\n\nexport function subscriptionsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"push-subscriptions.json\");\n}\n\nexport async function readSubscriptions(dataDir: string): Promise<PushSubscription[]> {\n const filePath = subscriptionsPath(dataDir);\n if (!fs.existsSync(filePath)) return [];\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\") as string;\n const parsed = JSON.parse(raw) as PushSubscriptionsFile;\n return parsed.subscriptions ?? [];\n } catch {\n return [];\n }\n}\n\nexport async function writeSubscriptions(dataDir: string, subs: PushSubscription[]): Promise<void> {\n const filePath = subscriptionsPath(dataDir);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n const file: PushSubscriptionsFile = { subscriptions: subs, updatedAt: new Date().toISOString() };\n fs.writeFileSync(filePath, JSON.stringify(file, null, 2), \"utf-8\");\n}\n\nfunction expiresAtForProvider(provider: PushProvider): string | null {\n if (provider === \"gmail\") {\n return new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();\n }\n if (provider === \"microsoft-graph\") {\n return new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString();\n }\n return null; // slack: no expiry\n}\n\nexport async function register(\n dataDir: string,\n provider: PushProvider,\n slug: string,\n opts: { webhookUrl: string; providerData?: Partial<PushSubscription[\"providerData\"]> }\n): Promise<PushSubscription> {\n const subs = await readSubscriptions(dataDir);\n const sub: PushSubscription = {\n id: makePushSubId(),\n provider,\n slug,\n webhookUrl: opts.webhookUrl,\n expiresAt: expiresAtForProvider(provider),\n renewedAt: null,\n createdAt: new Date().toISOString(),\n providerData: opts.providerData ?? {},\n status: \"active\",\n lastEventAt: null,\n eventsProcessed: 0,\n };\n await writeSubscriptions(dataDir, [...subs, sub]);\n return sub;\n}\n\nexport async function revoke(dataDir: string, id: string): Promise<void> {\n const subs = await readSubscriptions(dataDir);\n const idx = subs.findIndex((s) => s.id === id);\n if (idx === -1) throw new Error(`Subscription ${id} not found`);\n subs[idx] = { ...subs[idx]!, status: \"revoked\" };\n await writeSubscriptions(dataDir, subs);\n}\n\nexport type RenewFn = (\n sub: PushSubscription\n) => Promise<{ expiresAt: string; providerData?: Partial<PushSubscription[\"providerData\"]> }>;\n\nexport async function renewExpiringSubscriptions(\n dataDir: string,\n renewFn: RenewFn,\n thresholdHours = 24\n): Promise<{ renewed: string[]; errors: string[] }> {\n const subs = await readSubscriptions(dataDir);\n const thresholdMs = thresholdHours * 60 * 60 * 1000;\n const cutoff = Date.now() + thresholdMs;\n\n const renewed: string[] = [];\n const errors: string[] = [];\n\n const PERMANENT_FAILURE_THRESHOLD = 3;\n\n for (let i = 0; i < subs.length; i++) {\n const sub = subs[i]!;\n if (sub.status !== \"active\" && sub.status !== \"error\") continue;\n if (sub.expiresAt === null) continue; // slack: no expiry\n if (new Date(sub.expiresAt).getTime() > cutoff) continue;\n\n try {\n const result = await renewFn(sub);\n subs[i] = {\n ...sub,\n status: \"active\",\n expiresAt: result.expiresAt,\n renewedAt: new Date().toISOString(),\n renewFailures: 0,\n providerData: result.providerData\n ? { ...sub.providerData, ...result.providerData }\n : sub.providerData,\n };\n renewed.push(sub.id);\n } catch {\n const failures = (sub.renewFailures ?? 0) + 1;\n const newStatus: PushStatus =\n failures >= PERMANENT_FAILURE_THRESHOLD ? \"permanently_failed\" : \"error\";\n subs[i] = { ...sub, status: newStatus, renewFailures: failures };\n errors.push(sub.id);\n }\n }\n\n await writeSubscriptions(dataDir, subs);\n return { renewed, errors };\n}\n"],"mappings":";;;AAqCA,SAAgB,gBAAwB;CACtC,OAAO,QAAQ,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC;AACpE;AAEA,SAAgB,kBAAkB,SAAyB;CACzD,OAAO,KAAK,KAAK,SAAS,YAAY,yBAAyB;AACjE;AAEA,eAAsB,kBAAkB,SAA8C;CACpF,MAAM,WAAW,kBAAkB,OAAO;CAC1C,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,OAAO,CAAC;CACtC,IAAI;EACF,MAAM,MAAM,GAAG,aAAa,UAAU,OAAO;EAE7C,OADe,KAAK,MAAM,GACd,EAAE,iBAAiB,CAAC;CAClC,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,eAAsB,mBAAmB,SAAiB,MAAyC;CACjG,MAAM,WAAW,kBAAkB,OAAO;CAC1C,GAAG,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;CACxD,MAAM,OAA8B;EAAE,eAAe;EAAM,4BAAW,IAAI,KAAK,GAAE,YAAY;CAAE;CAC/F,GAAG,cAAc,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AACnE;AAEA,SAAS,qBAAqB,UAAuC;CACnE,IAAI,aAAa,SACf,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,QAAc,KAAK,GAAI,EAAE,YAAY;CAEpE,IAAI,aAAa,mBACf,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,OAAc,KAAK,GAAI,EAAE,YAAY;CAEpE,OAAO;AACT;AAEA,eAAsB,SACpB,SACA,UACA,MACA,MAC2B;CAC3B,MAAM,OAAO,MAAM,kBAAkB,OAAO;CAC5C,MAAM,MAAwB;EAC5B,IAAI,cAAc;EAClB;EACA;EACA,YAAY,KAAK;EACjB,WAAW,qBAAqB,QAAQ;EACxC,WAAW;EACX,4BAAW,IAAI,KAAK,GAAE,YAAY;EAClC,cAAc,KAAK,gBAAgB,CAAC;EACpC,QAAQ;EACR,aAAa;EACb,iBAAiB;CACnB;CACA,MAAM,mBAAmB,SAAS,CAAC,GAAG,MAAM,GAAG,CAAC;CAChD,OAAO;AACT;AAEA,eAAsB,OAAO,SAAiB,IAA2B;CACvE,MAAM,OAAO,MAAM,kBAAkB,OAAO;CAC5C,MAAM,MAAM,KAAK,WAAW,MAAM,EAAE,OAAO,EAAE;CAC7C,IAAI,QAAQ,IAAI,MAAM,IAAI,MAAM,gBAAgB,GAAG,WAAW;CAC9D,KAAK,OAAO;EAAE,GAAG,KAAK;EAAO,QAAQ;CAAU;CAC/C,MAAM,mBAAmB,SAAS,IAAI;AACxC;AAMA,eAAsB,2BACpB,SACA,SACA,iBAAiB,IACiC;CAClD,MAAM,OAAO,MAAM,kBAAkB,OAAO;CAC5C,MAAM,cAAc,iBAAiB,KAAK,KAAK;CAC/C,MAAM,SAAS,KAAK,IAAI,IAAI;CAE5B,MAAM,UAAoB,CAAC;CAC3B,MAAM,SAAmB,CAAC;CAE1B,MAAM,8BAA8B;CAEpC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EACjB,IAAI,IAAI,WAAW,YAAY,IAAI,WAAW,SAAS;EACvD,IAAI,IAAI,cAAc,MAAM;EAC5B,IAAI,IAAI,KAAK,IAAI,SAAS,EAAE,QAAQ,IAAI,QAAQ;EAEhD,IAAI;GACF,MAAM,SAAS,MAAM,QAAQ,GAAG;GAChC,KAAK,KAAK;IACR,GAAG;IACH,QAAQ;IACR,WAAW,OAAO;IAClB,4BAAW,IAAI,KAAK,GAAE,YAAY;IAClC,eAAe;IACf,cAAc,OAAO,eACjB;KAAE,GAAG,IAAI;KAAc,GAAG,OAAO;IAAa,IAC9C,IAAI;GACV;GACA,QAAQ,KAAK,IAAI,EAAE;EACrB,QAAQ;GACN,MAAM,YAAY,IAAI,iBAAiB,KAAK;GAC5C,MAAM,YACJ,YAAY,8BAA8B,uBAAuB;GACnE,KAAK,KAAK;IAAE,GAAG;IAAK,QAAQ;IAAW,eAAe;GAAS;GAC/D,OAAO,KAAK,IAAI,EAAE;EACpB;CACF;CAEA,MAAM,mBAAmB,SAAS,IAAI;CACtC,OAAO;EAAE;EAAS;CAAO;AAC3B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"quote-generator-BfwENXzg.js","names":[],"sources":["../src/core/quote-generator.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport yaml from \"js-yaml\";\nimport type { Quote, QuoteLineItem } from \"../schemas/quote.js\";\n\ninterface QuoteConfig {\n companyName?: string;\n companyAddress?: string;\n vatId?: string;\n currency?: string;\n paymentTerms?: string;\n footerText?: string;\n}\n\nexport interface GenerateQuoteInput {\n slug: string;\n dealName: string;\n lineItems: Array<{ description: string; quantity: number; unitPrice: number }>;\n vatPercent?: number;\n validUntilDays?: number;\n currency?: string;\n}\n\nfunction quotesDir(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"quotes\");\n}\n\nfunction loadQuoteConfig(dataDir: string): QuoteConfig {\n const p = path.join(dataDir, \".agentic\", \"quote-config.yaml\");\n if (!fs.existsSync(p)) return {};\n try {\n return (yaml.load(fs.readFileSync(p, \"utf-8\") as string) as QuoteConfig) ?? {};\n } catch {\n return {};\n }\n}\n\nfunction nextQuoteNumber(dataDir: string): string {\n const year = new Date().getFullYear();\n const dir = quotesDir(dataDir);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n return `Q-${year}-001`;\n }\n const existing = fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".json\") && f.startsWith(`Q-${year}-`))\n .map((f) => parseInt(f.replace(`Q-${year}-`, \"\").replace(\".json\", \"\"), 10))\n .filter((n) => !isNaN(n));\n const max = existing.length > 0 ? Math.max(...existing) : 0;\n return `Q-${year}-${String(max + 1).padStart(3, \"0\")}`;\n}\n\nfunction addDaysToDate(isoDate: string, days: number): string {\n const [year, month, day] = isoDate.slice(0, 10).split(\"-\").map(Number) as [\n number,\n number,\n number,\n ];\n const d = new Date(Date.UTC(year, month - 1, day));\n d.setUTCDate(d.getUTCDate() + days);\n return d.toISOString().slice(0, 10);\n}\n\nfunction buildHtml(quote: Quote, config: QuoteConfig, customerName: string): string {\n const lineRows = quote.lineItems\n .map(\n (item) =>\n `<tr><td>${item.description}</td><td style=\"text-align:right\">${item.quantity}</td><td style=\"text-align:right\">${item.unitPrice.toFixed(2)} ${quote.currency}</td><td style=\"text-align:right\">${item.total.toFixed(2)} ${quote.currency}</td></tr>`\n )\n .join(\"\\n\");\n\n return `<!DOCTYPE html>\n<html lang=\"de\">\n<head><meta charset=\"UTF-8\"><title>Angebot ${quote.quoteNumber}</title>\n<style>body{font-family:Arial,sans-serif;max-width:800px;margin:40px auto;color:#222}table{width:100%;border-collapse:collapse}th,td{padding:8px 12px;border:1px solid #ddd}th{background:#f5f5f5}h1{color:#1a1a2e}.total{font-weight:bold;font-size:1.1em}</style>\n</head>\n<body>\n<h1>Angebot ${quote.quoteNumber}</h1>\n<p><strong>${config.companyName ?? \"\"}</strong><br>${config.companyAddress ?? \"\"}<br>${config.vatId ? `USt-IdNr.: ${config.vatId}` : \"\"}</p>\n<hr>\n<p><strong>An:</strong> ${customerName}</p>\n<p><strong>Datum:</strong> ${quote.createdAt.slice(0, 10)} <strong>Gültig bis:</strong> ${quote.validUntil}</p>\n<h2>Leistungen</h2>\n<table>\n<thead><tr><th>Beschreibung</th><th style=\"text-align:right\">Menge</th><th style=\"text-align:right\">Einzelpreis</th><th style=\"text-align:right\">Gesamt</th></tr></thead>\n<tbody>${lineRows}</tbody>\n</table>\n<br>\n<table style=\"width:300px;margin-left:auto\">\n<tr><td>Nettobetrag</td><td style=\"text-align:right\">${quote.subtotal.toFixed(2)} ${quote.currency}</td></tr>\n<tr><td>MwSt. (${quote.vatPercent}%)</td><td style=\"text-align:right\">${quote.vat.toFixed(2)} ${quote.currency}</td></tr>\n<tr class=\"total\"><td><strong>Gesamtbetrag</strong></td><td style=\"text-align:right\"><strong>${quote.total.toFixed(2)} ${quote.currency}</strong></td></tr>\n</table>\n<br><p>${config.paymentTerms ?? \"\"}</p>\n<hr><small>${config.footerText ?? \"\"}</small>\n</body></html>`;\n}\n\nfunction readCustomerName(dataDir: string, slug: string): string {\n const p = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n if (!fs.existsSync(p)) return slug;\n const content = fs.readFileSync(p, \"utf-8\") as string;\n const match = /^name:\\s*(.+)$/m.exec(content);\n return match?.[1]?.trim() ?? slug;\n}\n\nexport function readQuote(dataDir: string, quoteNumber: string): Quote | null {\n const p = path.join(quotesDir(dataDir), `${quoteNumber}.json`);\n if (!fs.existsSync(p)) return null;\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Quote;\n } catch {\n return null;\n }\n}\n\nexport function listQuotes(dataDir: string, slug?: string): Quote[] {\n const dir = quotesDir(dataDir);\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".json\"))\n .flatMap((f) => {\n try {\n const q = JSON.parse(fs.readFileSync(path.join(dir, f), \"utf-8\") as string) as Quote;\n return slug === undefined || q.slug === slug ? [q] : [];\n } catch {\n return [];\n }\n });\n}\n\nexport function updateQuoteStatus(\n dataDir: string,\n quoteNumber: string,\n status: Quote[\"status\"]\n): void {\n const q = readQuote(dataDir, quoteNumber);\n if (!q) return;\n const updated: Quote = { ...q, status };\n if (status === \"viewed\" && !q.viewedAt) updated.viewedAt = new Date().toISOString();\n if (status === \"accepted\" && !q.acceptedAt) updated.acceptedAt = new Date().toISOString();\n fs.writeFileSync(\n path.join(quotesDir(dataDir), `${quoteNumber}.json`),\n JSON.stringify(updated, null, 2),\n \"utf-8\"\n );\n}\n\nexport async function generateQuote(dataDir: string, input: GenerateQuoteInput): Promise<Quote> {\n const config = loadQuoteConfig(dataDir);\n const vatPercent = input.vatPercent ?? 19;\n const validUntilDays = input.validUntilDays ?? 30;\n const currency = input.currency ?? config.currency ?? \"EUR\";\n\n const items: QuoteLineItem[] = input.lineItems.map((item) => ({\n description: item.description,\n quantity: item.quantity,\n unitPrice: item.unitPrice,\n total: item.quantity * item.unitPrice,\n }));\n\n const subtotal = items.reduce((sum, i) => sum + i.total, 0);\n const vat = Math.round(subtotal * (vatPercent / 100) * 100) / 100;\n const total = Math.round((subtotal + vat) * 100) / 100;\n\n const quoteNumber = nextQuoteNumber(dataDir);\n const now = new Date().toISOString();\n const validUntil = addDaysToDate(now.slice(0, 10), validUntilDays);\n\n const dir = quotesDir(dataDir);\n fs.mkdirSync(dir, { recursive: true });\n\n const htmlPath = path.join(dir, `${quoteNumber}.html`);\n\n const quote: Quote = {\n quoteNumber,\n slug: input.slug,\n dealName: input.dealName,\n lineItems: items,\n subtotal,\n vatPercent,\n vat,\n total,\n currency,\n createdAt: now,\n validUntilDays,\n validUntil,\n status: \"draft\",\n htmlPath,\n };\n\n fs.writeFileSync(path.join(dir, `${quoteNumber}.json`), JSON.stringify(quote, null, 2), \"utf-8\");\n\n const customerName = readCustomerName(dataDir, input.slug);\n const html = buildHtml(quote, config, customerName);\n fs.writeFileSync(htmlPath, html, \"utf-8\");\n\n return quote;\n}\n"],"mappings":"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"}
|
package/dist/rbac-C7c8tcES.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rbac-CTIktZaC.js","names":[],"sources":["../src/core/rbac.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\nexport type Role = \"admin\" | \"manager\" | \"rep\";\n\nexport interface RbacConfig {\n actors: Record<string, Role>;\n default?: Role;\n owned_customers?: Record<string, string[]>;\n /** Field-level ACL: field name → roles allowed to see it. Others get it redacted. */\n field_acl?: Record<string, Role[]>;\n}\n\nconst ALLOWED_TOOLS: Record<Role, string[]> = {\n admin: [\n \"log_interaction\",\n \"update_deal\",\n \"update_customer_facts\",\n \"export_customer\",\n \"pursue_goal\",\n \"register_push_subscription\",\n \"define_custom_object\",\n \"create_record\",\n ],\n manager: [\"log_interaction\", \"update_deal\", \"pursue_goal\", \"create_record\"],\n rep: [\"log_interaction\", \"update_deal\", \"create_record\"],\n};\n\nfunction rbacPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"rbac.json\");\n}\n\nexport function getRbacConfig(dataDir: string): RbacConfig {\n const p = rbacPath(dataDir);\n if (!fs.existsSync(p)) return { actors: {} };\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as RbacConfig;\n } catch {\n return { actors: {} };\n }\n}\n\nexport function setActorRole(dataDir: string, actor: string, role: Role): void {\n const p = rbacPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n const config = getRbacConfig(dataDir);\n config.actors[actor] = role;\n fs.writeFileSync(p, JSON.stringify(config, null, 2), \"utf-8\");\n}\n\nexport function getRole(dataDir: string, actor: string): Role {\n const config = getRbacConfig(dataDir);\n return config.actors[actor] ?? config.default ?? \"rep\";\n}\n\nexport function canWrite(role: Role, tool: string): boolean {\n return ALLOWED_TOOLS[role]?.includes(tool) ?? false;\n}\n\nexport function assertCanWrite(role: Role, tool: string, actor: string): void {\n if (!canWrite(role, tool)) {\n throw new Error(`Access denied: '${actor}' (role: ${role}) cannot use tool '${tool}'`);\n }\n}\n\nexport function enforceRbac(dataDir: string, tool: string): void {\n if (!fs.existsSync(rbacPath(dataDir))) return; // no rbac.json = open access\n const actor = process.env[\"DXCRM_ACTOR\"] ?? \"system\";\n if (actor === \"system\") return; // internal system actor bypasses RBAC\n const role = getRole(dataDir, actor);\n assertCanWrite(role, tool, actor);\n}\n\nexport function canSeeCustomer(dataDir: string, actor: string, slug: string): boolean {\n if (!fs.existsSync(rbacPath(dataDir))) return true; // open access\n if (actor === \"system\") return true; // internal system actor always has full access\n const config = getRbacConfig(dataDir);\n const role = config.actors[actor] ?? config.default ?? \"rep\";\n if (role === \"admin\" || role === \"manager\") return true;\n // rep: only sees customers listed in owned_customers[actor]\n const owned = config.owned_customers;\n if (!owned) return false;\n return (owned[actor] ?? []).includes(slug);\n}\n\n/** Load the field-level ACL (field → allowed roles) from rbac.json. */\nexport function loadFieldAcl(dataDir: string): Record<string, Role[]> {\n return getRbacConfig(dataDir).field_acl ?? {};\n}\n\n/** Whether a role may see a field given the ACL (fields not in the ACL are public). */\nexport function canSeeField(field: string, role: Role, acl: Record<string, Role[]>): boolean {\n const allowed = acl[field];\n if (!allowed) return true;\n return allowed.includes(role);\n}\n\n/** Return a copy of `values` with fields the role may not see removed. */\nexport function redactFields<T extends Record<string, unknown>>(\n values: T,\n role: Role,\n acl: Record<string, Role[]>\n): Partial<T> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(values)) {\n if (canSeeField(k, role, acl)) out[k] = v;\n }\n return out as Partial<T>;\n}\n"],"mappings":";;;AAaA,MAAM,gBAAwC;CAC5C,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,SAAS;EAAC;EAAmB;EAAe;EAAe;CAAe;CAC1E,KAAK;EAAC;EAAmB;EAAe;CAAe;AACzD;AAEA,SAAS,SAAS,SAAyB;CACzC,OAAO,KAAK,KAAK,SAAS,YAAY,WAAW;AACnD;AAEA,SAAgB,cAAc,SAA6B;CACzD,MAAM,IAAI,SAAS,OAAO;CAC1B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,EAAE,QAAQ,CAAC,EAAE;CAC3C,IAAI;EACF,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;CACzD,QAAQ;EACN,OAAO,EAAE,QAAQ,CAAC,EAAE;CACtB;AACF;AAEA,SAAgB,aAAa,SAAiB,OAAe,MAAkB;CAC7E,MAAM,IAAI,SAAS,OAAO;CAC1B,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,MAAM,SAAS,cAAc,OAAO;CACpC,OAAO,OAAO,SAAS;CACvB,GAAG,cAAc,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;AAC9D;AAEA,SAAgB,QAAQ,SAAiB,OAAqB;CAC5D,MAAM,SAAS,cAAc,OAAO;CACpC,OAAO,OAAO,OAAO,UAAU,OAAO,WAAW;AACnD;AAEA,SAAgB,SAAS,MAAY,MAAuB;CAC1D,OAAO,cAAc,OAAO,SAAS,IAAI,KAAK;AAChD;AAEA,SAAgB,eAAe,MAAY,MAAc,OAAqB;CAC5E,IAAI,CAAC,SAAS,MAAM,IAAI,GACtB,MAAM,IAAI,MAAM,mBAAmB,MAAM,WAAW,KAAK,qBAAqB,KAAK,EAAE;AAEzF;AAEA,SAAgB,YAAY,SAAiB,MAAoB;CAC/D,IAAI,CAAC,GAAG,WAAW,SAAS,OAAO,CAAC,GAAG;CACvC,MAAM,QAAQ,QAAQ,IAAI,kBAAkB;CAC5C,IAAI,UAAU,UAAU;CAExB,eADa,QAAQ,SAAS,KACZ,GAAG,MAAM,KAAK;AAClC;AAEA,SAAgB,eAAe,SAAiB,OAAe,MAAuB;CACpF,IAAI,CAAC,GAAG,WAAW,SAAS,OAAO,CAAC,GAAG,OAAO;CAC9C,IAAI,UAAU,UAAU,OAAO;CAC/B,MAAM,SAAS,cAAc,OAAO;CACpC,MAAM,OAAO,OAAO,OAAO,UAAU,OAAO,WAAW;CACvD,IAAI,SAAS,WAAW,SAAS,WAAW,OAAO;CAEnD,MAAM,QAAQ,OAAO;CACrB,IAAI,CAAC,OAAO,OAAO;CACnB,QAAQ,MAAM,UAAU,CAAC,GAAG,SAAS,IAAI;AAC3C;;AAGA,SAAgB,aAAa,SAAyC;CACpE,OAAO,cAAc,OAAO,EAAE,aAAa,CAAC;AAC9C;;AAGA,SAAgB,YAAY,OAAe,MAAY,KAAsC;CAC3F,MAAM,UAAU,IAAI;CACpB,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO,QAAQ,SAAS,IAAI;AAC9B;;AAGA,SAAgB,aACd,QACA,MACA,KACY;CACZ,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,GACxC,IAAI,YAAY,GAAG,MAAM,GAAG,GAAG,IAAI,KAAK;CAE1C,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"relationship-health-odxEoQdJ.js","names":[],"sources":["../src/core/graph.ts","../src/core/email-normalizer.ts","../src/core/graph-extractor.ts","../src/core/relationship-health.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { withJsonFile } from \"./file-lock.js\";\n\nexport type NodeType = \"person\" | \"company\" | \"deal\" | \"product\" | \"event\";\n\nexport type EdgeType =\n | \"KNOWS\"\n | \"WORKS_AT\"\n | \"IS_CHAMPION\"\n | \"IS_BLOCKER\"\n | \"IS_ECONOMIC_BUYER\"\n | \"INTRODUCED_BY\"\n | \"OWNS_DEAL\"\n | \"COMPETES_WITH\";\n\nexport interface GraphNode {\n id: string;\n type: NodeType;\n label: string;\n status?: \"active\" | \"inactive\";\n properties: {\n email?: string;\n title?: string;\n company?: string;\n domain?: string;\n [key: string]: unknown;\n };\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface GraphEdge {\n id: string;\n from: string;\n to: string;\n type: EdgeType;\n weight: number;\n sentiment: number;\n lastContact: string;\n contactCount: number;\n properties: Record<string, unknown>;\n // Bi-temporal fields (Graphiti-style), optional for backward compatibility:\n // valid time = when the fact held in the world; transaction time = when the\n // system recorded/retracted it. Edges are invalidated, never deleted.\n validFrom?: string;\n validTo?: string;\n recordedAt?: string;\n invalidatedAt?: string;\n}\n\nexport interface CustomerGraph {\n schemaVersion: \"1\";\n slug: string;\n nodes: GraphNode[];\n edges: GraphEdge[];\n updatedAt: string;\n}\n\nexport type StakeholderRole = \"champion\" | \"blocker\" | \"economic_buyer\" | \"user\";\n\nexport interface MissingRole {\n role: \"champion\" | \"economic_buyer\";\n urgency: \"critical\" | \"important\";\n suggestion: string;\n}\n\nexport interface StakeholderSummary {\n champions: GraphNode[];\n blockers: GraphNode[];\n economicBuyers: GraphNode[];\n allContacts: GraphNode[];\n missingRoles: MissingRole[];\n}\n\n// ─── File path ────────────────────────────────────────────────────────────────\n\nexport function graphPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug, \"graph.json\");\n}\n\nexport function emptyGraph(slug: string): CustomerGraph {\n return {\n schemaVersion: \"1\",\n slug,\n nodes: [],\n edges: [],\n updatedAt: new Date().toISOString(),\n };\n}\n\n// ─── Read / Write ─────────────────────────────────────────────────────────────\n\nexport function readGraph(dataDir: string, slug: string): CustomerGraph {\n const p = graphPath(dataDir, slug);\n if (!fs.existsSync(p)) return emptyGraph(slug);\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\")) as CustomerGraph;\n } catch {\n process.stderr.write(`[graph] failed to parse ${p} — returning empty graph\\n`);\n return emptyGraph(slug);\n }\n}\n\nexport async function writeGraph(\n dataDir: string,\n slug: string,\n updater: (current: CustomerGraph | null) => CustomerGraph\n): Promise<CustomerGraph> {\n return withJsonFile<CustomerGraph>(graphPath(dataDir, slug), (current) => {\n const g = updater(current);\n return { ...g, updatedAt: new Date().toISOString() };\n });\n}\n\n// ─── Node operations ──────────────────────────────────────────────────────────\n\nexport function upsertNode(\n graph: CustomerGraph,\n node: Omit<GraphNode, \"createdAt\" | \"updatedAt\">\n): CustomerGraph {\n const now = new Date().toISOString();\n const idx = graph.nodes.findIndex((n) => n.id === node.id);\n if (idx !== -1) {\n const existing = graph.nodes[idx]!;\n const updated: GraphNode = {\n ...existing,\n label: node.label || existing.label,\n properties: { ...existing.properties, ...node.properties },\n updatedAt: now,\n };\n const nodes = [...graph.nodes];\n nodes[idx] = updated;\n return { ...graph, nodes };\n }\n const newNode: GraphNode = { ...node, createdAt: now, updatedAt: now };\n return { ...graph, nodes: [...graph.nodes, newNode] };\n}\n\nexport function findNode(graph: CustomerGraph, id: string): GraphNode | undefined {\n return graph.nodes.find((n) => n.id === id);\n}\n\nexport function findNodesByType(graph: CustomerGraph, type: NodeType): GraphNode[] {\n return graph.nodes.filter((n) => n.type === type);\n}\n\n// ─── Edge operations ──────────────────────────────────────────────────────────\n\nexport function makeEdgeId(type: EdgeType, fromId: string, toId: string): string {\n return `${type}:${fromId}__${toId}`;\n}\n\nexport function upsertEdge(\n graph: CustomerGraph,\n edge: Omit<GraphEdge, \"id\"> & { id?: string }\n): CustomerGraph {\n const id = edge.id ?? makeEdgeId(edge.type, edge.from, edge.to);\n const idx = graph.edges.findIndex((e) => e.id === id);\n if (idx !== -1) {\n const existing = graph.edges[idx]!;\n const updated: GraphEdge = {\n ...existing,\n weight: Math.min(1.0, existing.weight + 0.05),\n contactCount: existing.contactCount + 1,\n lastContact: edge.lastContact,\n properties: { ...existing.properties, ...edge.properties },\n };\n const edges = [...graph.edges];\n edges[idx] = updated;\n return { ...graph, edges };\n }\n const now = new Date().toISOString();\n const newEdge: GraphEdge = {\n ...edge,\n id,\n recordedAt: edge.recordedAt ?? now,\n validFrom: edge.validFrom ?? edge.lastContact ?? now,\n };\n return { ...graph, edges: [...graph.edges, newEdge] };\n}\n\n/**\n * Invalidate an edge instead of deleting it: records when the fact stopped\n * being true (validTo) and when the system retracted it (invalidatedAt),\n * preserving full temporal auditability.\n */\nexport function invalidateEdge(graph: CustomerGraph, edgeId: string, at?: string): CustomerGraph {\n const stamp = at ?? new Date().toISOString();\n const edges = graph.edges.map((e) =>\n e.id === edgeId ? { ...e, validTo: stamp, invalidatedAt: new Date().toISOString() } : e\n );\n return { ...graph, edges };\n}\n\n/**\n * Edges considered active at a point in time (default: now): not invalidated,\n * already valid (validFrom <= at), and not yet expired (at < validTo).\n * Edges without temporal fields (legacy) are treated as active.\n */\nexport function activeEdges(graph: CustomerGraph, at?: string): GraphEdge[] {\n const t = at ?? new Date().toISOString();\n return graph.edges.filter((e) => {\n if (e.invalidatedAt) return false;\n if (e.validFrom && e.validFrom > t) return false;\n if (e.validTo && t >= e.validTo) return false;\n return true;\n });\n}\n\nexport function findEdges(graph: CustomerGraph, fromId: string, type?: EdgeType): GraphEdge[] {\n return graph.edges.filter((e) => e.from === fromId && (type === undefined || e.type === type));\n}\n\nexport function findEdgesTo(graph: CustomerGraph, toId: string, type?: EdgeType): GraphEdge[] {\n return graph.edges.filter((e) => e.to === toId && (type === undefined || e.type === type));\n}\n\n// ─── Role assignment ──────────────────────────────────────────────────────────\n\nconst ROLE_EDGE_MAP: Record<Exclude<StakeholderRole, \"user\">, EdgeType> = {\n champion: \"IS_CHAMPION\",\n blocker: \"IS_BLOCKER\",\n economic_buyer: \"IS_ECONOMIC_BUYER\",\n};\n\nexport function setNodeRole(\n graph: CustomerGraph,\n nodeId: string,\n targetId: string,\n role: StakeholderRole\n): CustomerGraph {\n if (role === \"user\") return graph;\n const edgeType = ROLE_EDGE_MAP[role];\n const today = new Date().toISOString().slice(0, 10);\n return upsertEdge(graph, {\n from: nodeId,\n to: targetId,\n type: edgeType,\n weight: 0.8,\n sentiment: 0,\n lastContact: today,\n contactCount: 1,\n properties: {},\n });\n}\n\n// ─── Stakeholder query ────────────────────────────────────────────────────────\n\nexport function getStakeholders(graph: CustomerGraph): StakeholderSummary {\n const dedup = (nodes: GraphNode[]): GraphNode[] => {\n const seen = new Set<string>();\n return nodes.filter((n) => (seen.has(n.id) ? false : (seen.add(n.id), true)));\n };\n\n const resolve = (edges: GraphEdge[]): GraphNode[] =>\n dedup(edges.map((e) => findNode(graph, e.from)).filter((n): n is GraphNode => n !== undefined));\n\n const champions = resolve(graph.edges.filter((e) => e.type === \"IS_CHAMPION\"));\n const blockers = resolve(graph.edges.filter((e) => e.type === \"IS_BLOCKER\"));\n const economicBuyers = resolve(graph.edges.filter((e) => e.type === \"IS_ECONOMIC_BUYER\"));\n const allContacts = findNodesByType(graph, \"person\");\n\n const missingRoles: MissingRole[] = [];\n if (allContacts.length > 0) {\n if (champions.length === 0) {\n missingRoles.push({\n role: \"champion\",\n urgency: \"important\",\n suggestion: \"Identify who is driving this deal internally.\",\n });\n }\n if (economicBuyers.length === 0) {\n missingRoles.push({\n role: \"economic_buyer\",\n urgency: \"critical\",\n suggestion: \"Find out who signs the contract. Ask your champion directly.\",\n });\n }\n }\n\n return { champions, blockers, economicBuyers, allContacts, missingRoles };\n}\n\n// ─── Pruning ──────────────────────────────────────────────────────────────────\n\nexport function pruneStaleNodes(\n graph: CustomerGraph,\n maxAgeDays = 365,\n today?: string\n): CustomerGraph {\n const todayMs = today ? new Date(`${today}T00:00:00Z`).getTime() : Date.now();\n const threshold = maxAgeDays * 86_400_000;\n return {\n ...graph,\n nodes: graph.nodes.map((node) => {\n const age = todayMs - new Date(node.updatedAt).getTime();\n if (age > threshold && node.status !== \"inactive\") {\n return { ...node, status: \"inactive\" as const };\n }\n return node;\n }),\n };\n}\n\n// ─── Path finding ─────────────────────────────────────────────────────────────\n\n/** BFS shortest path between two nodes. Returns [] when no path exists. */\nexport function findPath(graph: CustomerGraph, fromId: string, toId: string): string[] {\n if (fromId === toId) return [fromId];\n\n const visited = new Set<string>([fromId]);\n const queue: Array<{ nodeId: string; path: string[] }> = [{ nodeId: fromId, path: [fromId] }];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n\n const neighbors = graph.edges\n .filter((e) => e.from === current.nodeId || e.to === current.nodeId)\n .map((e) => (e.from === current.nodeId ? e.to : e.from))\n .filter((id) => !visited.has(id));\n\n for (const neighborId of neighbors) {\n const newPath = [...current.path, neighborId];\n if (neighborId === toId) return newPath;\n visited.add(neighborId);\n queue.push({ nodeId: neighborId, path: newPath });\n }\n }\n\n return [];\n}\n","export function normalizeEmail(raw: string): string {\n if (!raw) return \"\";\n const trimmed = raw.trim();\n // Extract email from \"Display Name <email@example.com>\" format\n const angleMatch = trimmed.match(/<([^>]+)>/);\n const address = angleMatch ? angleMatch[1]! : trimmed;\n return address.toLowerCase().trim();\n}\n\nexport function isSameContact(a: string, b: string): boolean {\n return normalizeEmail(a) === normalizeEmail(b);\n}\n\nexport function normalizeContactId(raw: string): string {\n const email = normalizeEmail(raw);\n // Replace @ with _at_ so it can be used as an object key safely\n return email.replace(\"@\", \"_at_\");\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\nimport type { GraphNode, GraphEdge, EdgeType, CustomerGraph } from \"./graph.js\";\nimport { writeGraph, upsertNode, upsertEdge } from \"./graph.js\";\nimport { normalizeEmail } from \"./email-normalizer.js\";\n\nexport interface ExtractionInput {\n slug: string;\n withStr: string;\n interactionDate: string;\n domain?: string;\n companyName?: string;\n}\n\nexport function extractEmail(withStr: string): string | undefined {\n const angleMatch = withStr.match(/<([^>]+@[^>]+)>/);\n if (angleMatch?.[1]) return angleMatch[1].toLowerCase();\n const bareMatch = withStr.match(/^([^\\s]+@[^\\s]+)$/);\n if (bareMatch?.[1]) return bareMatch[1].toLowerCase();\n return undefined;\n}\n\nexport function extractDisplayName(withStr: string): string {\n const match = withStr.match(/^(.+?)\\s*<[^>]+>$/);\n if (match?.[1]) return match[1].trim();\n return withStr.trim();\n}\n\nexport function makePersonId(withStr: string, slug: string): string {\n const email = normalizeEmail(withStr);\n if (email.includes(\"@\")) return `person:${email}`;\n const name = extractDisplayName(withStr);\n const nameSlug = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n return `person:${slug}:${nameSlug}`;\n}\n\nexport function makeCompanyId(domain?: string, slug?: string, companyName?: string): string {\n if (domain) return `company:${domain.toLowerCase()}`;\n if (slug) return `company:${slug}`;\n if (companyName) {\n const s = companyName\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n return `company:${s}`;\n }\n return `company:unknown`;\n}\n\nexport function extractNodes(input: ExtractionInput): Omit<GraphNode, \"createdAt\" | \"updatedAt\">[] {\n const email = extractEmail(input.withStr);\n const label = extractDisplayName(input.withStr);\n const personId = makePersonId(input.withStr, input.slug);\n\n const personProps: GraphNode[\"properties\"] = {};\n if (email !== undefined) personProps[\"email\"] = email;\n if (input.companyName !== undefined) personProps[\"company\"] = input.companyName;\n if (input.domain !== undefined) personProps[\"domain\"] = input.domain;\n\n const personNode: Omit<GraphNode, \"createdAt\" | \"updatedAt\"> = {\n id: personId,\n type: \"person\",\n label,\n properties: personProps,\n };\n\n const nodes: Omit<GraphNode, \"createdAt\" | \"updatedAt\">[] = [personNode];\n\n if (input.domain !== undefined || input.companyName !== undefined) {\n const companyId = makeCompanyId(input.domain, input.slug, input.companyName);\n const companyProps: GraphNode[\"properties\"] = {};\n if (input.domain !== undefined) companyProps[\"domain\"] = input.domain;\n\n const companyNode: Omit<GraphNode, \"createdAt\" | \"updatedAt\"> = {\n id: companyId,\n type: \"company\",\n label: input.companyName ?? input.domain ?? input.slug,\n properties: companyProps,\n };\n nodes.push(companyNode);\n }\n\n return nodes;\n}\n\nexport function extractEdges(\n personId: string,\n companyId: string | undefined,\n interactionDate: string\n): GraphEdge[] {\n if (!companyId) return [];\n return [\n {\n id: `WORKS_AT:${personId}__${companyId}`,\n from: personId,\n to: companyId,\n type: \"WORKS_AT\" as EdgeType,\n weight: 0.5,\n sentiment: 0,\n lastContact: interactionDate,\n contactCount: 1,\n properties: {},\n },\n ];\n}\n\nexport async function updateGraphFromInteraction(\n dataDir: string,\n slug: string,\n input: { withStr: string; interactionDate: string }\n): Promise<void> {\n if (!input.withStr.trim()) return;\n\n let domain: string | undefined;\n let companyName: string | undefined;\n const factsPath = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n if (fs.existsSync(factsPath)) {\n try {\n const parsed = matter(fs.readFileSync(factsPath, \"utf-8\"));\n domain = parsed.data[\"domain\"] as string | undefined;\n companyName = parsed.data[\"name\"] as string | undefined;\n } catch {\n // non-critical\n }\n }\n\n const extractionInput: ExtractionInput = {\n slug,\n withStr: input.withStr,\n interactionDate: input.interactionDate,\n };\n if (domain !== undefined) extractionInput.domain = domain;\n if (companyName !== undefined) extractionInput.companyName = companyName;\n const nodes = extractNodes(extractionInput);\n\n const personId = makePersonId(input.withStr, slug);\n const companyId =\n domain !== undefined || companyName !== undefined\n ? makeCompanyId(domain, slug, companyName)\n : undefined;\n const edges = extractEdges(personId, companyId, input.interactionDate);\n\n await writeGraph(dataDir, slug, (current) => {\n const empty: CustomerGraph = {\n schemaVersion: \"1\",\n slug,\n nodes: [],\n edges: [],\n updatedAt: new Date().toISOString(),\n };\n let graph = current ?? empty;\n for (const node of nodes) graph = upsertNode(graph, node);\n for (const edge of edges) graph = upsertEdge(graph, edge);\n return graph;\n });\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport { readGraph } from \"./graph.js\";\nimport { extractEmail, extractDisplayName, makePersonId } from \"./graph-extractor.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type HealthGrade = \"A\" | \"B\" | \"C\" | \"D\" | \"F\";\n\nexport type HealthTrend = \"rising\" | \"stable\" | \"declining\" | \"cold\";\n\nexport type RiskFlag =\n | \"NO_CONTACT_14D\"\n | \"NO_CONTACT_30D\"\n | \"SENTIMENT_DECLINING\"\n | \"CHAMPION_SILENT\"\n | \"DEAL_STALLED\"\n | \"CLOSE_DATE_PASSED\"\n | \"CONTACT_LEFT_COMPANY\"\n | \"RESPONSE_LATENCY_INCREASING\";\n\nexport interface ContactHealth {\n contactId: string;\n name: string;\n email?: string;\n score: number;\n grade: HealthGrade;\n trend: HealthTrend;\n daysSinceContact: number;\n avgCadenceDays: number;\n sentimentTrend: number;\n riskFlags: RiskFlag[];\n lastContact: string;\n interactionCount30d: number;\n recommendation: string;\n updatedAt: string;\n}\n\nexport interface HealthSnapshot {\n schemaVersion: \"1\";\n slug: string;\n contacts: ContactHealth[];\n overallHealth: number;\n updatedAt: string;\n}\n\n// ─── Parsed interaction (from interactions.md) ────────────────────────────────\n\nexport interface ParsedInteraction {\n date: string;\n type: string;\n withStr: string;\n}\n\nexport interface ContactInteractionGroup {\n contactId: string;\n name: string;\n email?: string;\n interactions: ParsedInteraction[];\n}\n\n// ─── File path ────────────────────────────────────────────────────────────────\n\nexport function healthPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug, \"health.json\");\n}\n\n// ─── Read / Write ─────────────────────────────────────────────────────────────\n\nexport function readHealth(dataDir: string, slug: string): HealthSnapshot | null {\n const p = healthPath(dataDir, slug);\n if (!fs.existsSync(p)) return null;\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\")) as HealthSnapshot;\n } catch {\n return null;\n }\n}\n\nexport function writeHealth(dataDir: string, slug: string, health: HealthSnapshot): void {\n const p = healthPath(dataDir, slug);\n const dir = path.dirname(p);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const updated: HealthSnapshot = { ...health, updatedAt: new Date().toISOString() };\n fs.writeFileSync(p, JSON.stringify(updated, null, 2), \"utf-8\");\n}\n\n// ─── Parsing ──────────────────────────────────────────────────────────────────\n\nexport function parseContactInteractions(content: string): ParsedInteraction[] {\n const blocks = content.split(/(?=^## \\d{4}-\\d{2}-\\d{2})/m).filter((b) => b.trim().length > 0);\n\n const result: ParsedInteraction[] = [];\n for (const block of blocks) {\n const headingMatch = block.match(/^## (\\d{4}-\\d{2}-\\d{2}) · (\\w+)/m);\n if (!headingMatch) continue;\n const date = headingMatch[1]!;\n const type = headingMatch[2]!;\n\n const withMatch = block.match(/^\\*\\*(?:With|Subject):\\*\\*\\s*(.+)$/m);\n if (!withMatch) continue;\n const withStr = withMatch[1]!.trim();\n\n result.push({ date, type, withStr });\n }\n return result;\n}\n\n// ─── Score functions (pure) ───────────────────────────────────────────────────\n\nexport function calcRecencyScore(daysSince: number): number {\n if (daysSince <= 0) return 100;\n if (daysSince >= 30) return 0;\n return Math.round(100 * (1 - daysSince / 30));\n}\n\nexport function calcCadenceScore(daysSince: number, avgCadenceDays: number): number {\n if (avgCadenceDays <= 0) return 50;\n const ratio = daysSince / avgCadenceDays;\n if (ratio <= 1.0) return 100;\n if (ratio >= 3.0) return 0;\n return Math.round(100 * (1 - (ratio - 1.0) / 2.0));\n}\n\nexport function calcMomentumScore(last30d: number, prev30d: number): number {\n if (last30d === 0 && prev30d === 0) return 50;\n if (prev30d === 0) return 80;\n const ratio = last30d / prev30d;\n if (ratio >= 1.5) return 100;\n if (ratio >= 1.0) return 75;\n if (ratio >= 0.5) return 50;\n if (ratio >= 0.25) return 25;\n return 0;\n}\n\nexport function gradeFromScore(score: number): HealthGrade {\n if (score >= 80) return \"A\";\n if (score >= 60) return \"B\";\n if (score >= 40) return \"C\";\n if (score >= 20) return \"D\";\n return \"F\";\n}\n\nexport function trendFromState(\n score: number,\n daysSince: number,\n avgCadenceDays: number,\n momentumScore: number\n): HealthTrend {\n if (score < 20 || daysSince >= 30) return \"cold\";\n if (momentumScore > 70 && score > 60) return \"rising\";\n if (momentumScore < 30 || (daysSince > avgCadenceDays * 1.5 && score < 60)) return \"declining\";\n return \"stable\";\n}\n\nexport function calcRiskFlags(\n _contactId: string,\n daysSince: number,\n score: number,\n isChampion: boolean\n): RiskFlag[] {\n const flags: RiskFlag[] = [];\n if (daysSince >= 30) flags.push(\"NO_CONTACT_30D\");\n if (daysSince >= 14) flags.push(\"NO_CONTACT_14D\");\n if (isChampion && score < 50) flags.push(\"CHAMPION_SILENT\");\n return flags;\n}\n\nexport function generateRecommendation(\n name: string,\n grade: HealthGrade,\n trend: HealthTrend,\n riskFlags: RiskFlag[],\n daysSince: number,\n avgCadenceDays: number\n): string {\n if (riskFlags.includes(\"NO_CONTACT_30D\")) {\n return `Re-engage ${name} urgently — no contact in ${daysSince} days.`;\n }\n if (riskFlags.includes(\"CHAMPION_SILENT\")) {\n return `Champion ${name} has gone quiet — critical to re-engage before deal stalls.`;\n }\n if (riskFlags.includes(\"NO_CONTACT_14D\")) {\n return `Schedule contact with ${name} — ${daysSince} days since last touchpoint.`;\n }\n if (trend === \"declining\") {\n return `${name} relationship declining — increase touchpoint frequency.`;\n }\n if (grade === \"A\") {\n return `${name} — strong relationship. Keep current cadence.`;\n }\n const daysUntilDue = Math.max(0, avgCadenceDays - daysSince);\n return `${name} — grade ${grade}. Next contact due in ~${daysUntilDue} day${daysUntilDue === 1 ? \"\" : \"s\"}.`;\n}\n\n// ─── Average cadence ──────────────────────────────────────────────────────────\n\nfunction dateUtcMs(d: string): number {\n return new Date(`${d}T00:00:00Z`).getTime();\n}\n\nexport function calcAvgCadence(interactions: ParsedInteraction[]): number {\n if (interactions.length < 2) return 0;\n const sorted = [...interactions].sort((a, b) => b.date.localeCompare(a.date));\n let totalDays = 0;\n for (let i = 0; i < sorted.length - 1; i++) {\n const gap = Math.round(\n (dateUtcMs(sorted[i]!.date) - dateUtcMs(sorted[i + 1]!.date)) / 86_400_000\n );\n totalDays += gap;\n }\n return Math.round(totalDays / (sorted.length - 1));\n}\n\n// ─── Group interactions by contact ───────────────────────────────────────────\n\nexport function groupInteractionsByContact(\n interactions: ParsedInteraction[],\n slug: string\n): ContactInteractionGroup[] {\n const map = new Map<\n string,\n { contactId: string; name: string; email?: string; interactions: ParsedInteraction[] }\n >();\n\n for (const ix of interactions) {\n const email = extractEmail(ix.withStr);\n const name = extractDisplayName(ix.withStr);\n const contactId = makePersonId(ix.withStr, slug);\n\n if (!map.has(contactId)) {\n const entry: {\n contactId: string;\n name: string;\n email?: string;\n interactions: ParsedInteraction[];\n } = {\n contactId,\n name,\n interactions: [],\n };\n if (email !== undefined) entry.email = email;\n map.set(contactId, entry);\n }\n map.get(contactId)!.interactions.push(ix);\n }\n\n return Array.from(map.values());\n}\n\n// ─── Per-contact health ───────────────────────────────────────────────────────\n\nexport function computeContactHealth(\n group: ContactInteractionGroup,\n today: string,\n isChampion: boolean\n): ContactHealth {\n const sorted = [...group.interactions].sort((a, b) => b.date.localeCompare(a.date));\n const lastContact = sorted[0]?.date ?? \"\";\n\n const daysSince = lastContact\n ? Math.round((dateUtcMs(today) - dateUtcMs(lastContact)) / 86_400_000)\n : 999;\n\n const avgCadenceDays = calcAvgCadence(group.interactions);\n\n const todayMs = dateUtcMs(today);\n const d30 = todayMs - 30 * 86_400_000;\n const d60 = todayMs - 60 * 86_400_000;\n const last30d = group.interactions.filter((i) => dateUtcMs(i.date) >= d30).length;\n const prev30d = group.interactions.filter(\n (i) => dateUtcMs(i.date) >= d60 && dateUtcMs(i.date) < d30\n ).length;\n\n const recency = calcRecencyScore(daysSince);\n const cadence = calcCadenceScore(daysSince, avgCadenceDays);\n const sentiment = 50;\n const response = 50;\n const momentum = calcMomentumScore(last30d, prev30d);\n\n const score = Math.round(\n recency * 0.35 + cadence * 0.25 + sentiment * 0.2 + response * 0.1 + momentum * 0.1\n );\n\n const grade = gradeFromScore(score);\n const trend = trendFromState(score, daysSince, avgCadenceDays, momentum);\n const riskFlags = calcRiskFlags(group.contactId, daysSince, score, isChampion);\n const recommendation = generateRecommendation(\n group.name,\n grade,\n trend,\n riskFlags,\n daysSince,\n avgCadenceDays\n );\n\n const health: ContactHealth = {\n contactId: group.contactId,\n name: group.name,\n score,\n grade,\n trend,\n daysSinceContact: daysSince,\n avgCadenceDays,\n sentimentTrend: 0,\n riskFlags,\n lastContact,\n interactionCount30d: last30d,\n recommendation,\n updatedAt: new Date().toISOString(),\n };\n if (group.email !== undefined) health.email = group.email;\n return health;\n}\n\n// ─── Full customer health ─────────────────────────────────────────────────────\n\nexport function computeCustomerHealth(\n dataDir: string,\n slug: string,\n today: string = new Date().toISOString().slice(0, 10)\n): HealthSnapshot {\n const interactionsPath = path.join(dataDir, \"customers\", slug, \"interactions.md\");\n const content = fs.existsSync(interactionsPath)\n ? (fs.readFileSync(interactionsPath, \"utf-8\") as string)\n : \"\";\n\n const parsed = parseContactInteractions(content);\n const groups = groupInteractionsByContact(parsed, slug);\n\n const graph = readGraph(dataDir, slug);\n const championIds = new Set(\n graph.edges.filter((e) => e.type === \"IS_CHAMPION\").map((e) => e.from)\n );\n\n const contacts = groups.map((group) =>\n computeContactHealth(group, today, championIds.has(group.contactId))\n );\n\n const overallHealth =\n contacts.length === 0\n ? 100\n : Math.round(contacts.reduce((sum, c) => sum + c.score, 0) / contacts.length);\n\n return {\n schemaVersion: \"1\",\n slug,\n contacts,\n overallHealth,\n updatedAt: new Date().toISOString(),\n };\n}\n\n// ─── Fire-and-forget update ───────────────────────────────────────────────────\n\nexport async function updateHealthFromInteraction(dataDir: string, slug: string): Promise<void> {\n const health = computeCustomerHealth(dataDir, slug);\n writeHealth(dataDir, slug, health);\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"revenue-simulation-Bqf2DLVB.js","names":[],"sources":["../src/core/pipeline-stages.ts","../src/core/revenue-simulation.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\nexport interface PipelineStage {\n id: string;\n label: string;\n color?: string;\n order: number;\n isFinal?: boolean;\n probability?: number;\n}\n\nexport const DEFAULT_STAGES: PipelineStage[] = [\n { id: \"lead\", label: \"Lead\", order: 1, probability: 10 },\n { id: \"qualified\", label: \"Qualified\", order: 2, probability: 30 },\n { id: \"proposal\", label: \"Proposal\", order: 3, probability: 50 },\n { id: \"negotiation\", label: \"Negotiation\", order: 4, probability: 75 },\n { id: \"won\", label: \"Won\", order: 5, isFinal: true, probability: 100 },\n { id: \"lost\", label: \"Lost\", order: 6, isFinal: true, probability: 0 },\n];\n\nfunction stagesPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"pipeline-stages.json\");\n}\n\nexport function getPipelineStages(dataDir: string): PipelineStage[] {\n const p = stagesPath(dataDir);\n if (!fs.existsSync(p)) return DEFAULT_STAGES;\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as PipelineStage[];\n } catch {\n return DEFAULT_STAGES;\n }\n}\n\nexport function setPipelineStage(dataDir: string, stage: PipelineStage): void {\n const stages = getPipelineStages(dataDir);\n const idx = stages.findIndex((s) => s.id === stage.id);\n if (idx >= 0) stages[idx] = stage;\n else stages.push(stage);\n stages.sort((a, b) => a.order - b.order);\n fs.mkdirSync(path.dirname(stagesPath(dataDir)), { recursive: true });\n fs.writeFileSync(stagesPath(dataDir), JSON.stringify(stages, null, 2));\n}\n\nexport function deletePipelineStage(dataDir: string, id: string): void {\n const stages = getPipelineStages(dataDir).filter((s) => s.id !== id);\n fs.mkdirSync(path.dirname(stagesPath(dataDir)), { recursive: true });\n fs.writeFileSync(stagesPath(dataDir), JSON.stringify(stages, null, 2));\n}\n\nexport function resetToDefaults(dataDir: string): void {\n fs.mkdirSync(path.dirname(stagesPath(dataDir)), { recursive: true });\n fs.writeFileSync(stagesPath(dataDir), JSON.stringify(DEFAULT_STAGES, null, 2));\n}\n","import { readPipeline } from \"../fs/pipeline-writer.js\";\nimport { listCustomerSlugs } from \"../fs/customer-dir.js\";\nimport { readHealth, computeCustomerHealth } from \"./relationship-health.js\";\nimport { readGraph, getStakeholders } from \"./graph.js\";\nimport { getPipelineStages } from \"./pipeline-stages.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface ExternalSignal {\n slug: string;\n type: \"funding_round\" | \"leadership_change\" | \"news_positive\" | \"news_negative\";\n impact: \"positive\" | \"negative\" | \"neutral\";\n magnitude: number; // 0.0–1.0\n summary: string;\n}\n\nexport interface DealSnapshot {\n slug: string;\n name: string;\n stage: string;\n value: number;\n probability: number;\n closeDate?: string;\n healthScore: number;\n daysSinceContact: number;\n championPresent: boolean;\n}\n\nexport interface SimulationInput {\n deals: DealSnapshot[];\n externalSignals: ExternalSignal[];\n iterations: number;\n horizon: \"quarter\" | \"year\";\n today: string;\n}\n\nexport interface MonthForecast {\n p50: number;\n range: [number, number];\n}\n\nexport interface SimulationResult {\n p10: number;\n p50: number;\n p90: number;\n expected: number;\n stdDev: number;\n atRiskRevenue: number;\n byCloseMonth: Record<string, MonthForecast>;\n topRisks: string[];\n sensitivityMap: Record<string, number>;\n}\n\n// ─── Pure helpers ─────────────────────────────────────────────────────────────\n\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const idx = Math.max(0, Math.ceil((p / 100) * sorted.length) - 1);\n return sorted[idx]!;\n}\n\nexport function mean(values: number[]): number {\n if (values.length === 0) return 0;\n return values.reduce((s, v) => s + v, 0) / values.length;\n}\n\nexport function stdDevFn(values: number[], m: number): number {\n if (values.length < 2) return 0;\n const variance = values.reduce((s, v) => s + (v - m) ** 2, 0) / values.length;\n return Math.sqrt(variance);\n}\n\nexport function adjustProbability(deal: DealSnapshot, signals: ExternalSignal[] = []): number {\n let prob = deal.probability / 100;\n\n // Health adjustment: health 60 = neutral, range -0.12 to +0.08\n const healthAdj = ((deal.healthScore - 60) / 100) * 0.2;\n prob += healthAdj;\n\n // Champion bonus\n if (deal.championPresent) prob += 0.05;\n\n // External signals (D18-ready)\n for (const signal of signals) {\n if (signal.slug === deal.slug) {\n if (signal.impact === \"positive\") prob += 0.05 * signal.magnitude;\n if (signal.impact === \"negative\") prob -= 0.1 * signal.magnitude;\n }\n }\n\n return Math.max(0.02, Math.min(0.98, prob));\n}\n\nexport function closeVarianceFn(\n deal: DealSnapshot,\n randFn: () => number,\n todayMs: number = Date.now()\n): number {\n const daysToClose = deal.closeDate\n ? Math.max(0, Math.floor((new Date(deal.closeDate).getTime() - todayMs) / 86_400_000))\n : 90;\n const variance = daysToClose < 30 ? 0.05 : 0.15;\n return 1 + (randFn() - 0.5) * 2 * variance;\n}\n\nexport function buildSensitivityMap(\n deals: DealSnapshot[],\n signals: ExternalSignal[]\n): Record<string, number> {\n const map: Record<string, number> = {};\n for (const deal of deals) {\n map[deal.name] = Math.round(deal.value * adjustProbability(deal, signals));\n }\n return map;\n}\n\nexport function buildTopRisks(\n deals: DealSnapshot[],\n signals: ExternalSignal[],\n sensitivityMap: Record<string, number>\n): string[] {\n const atRisk = deals.filter((d) => d.healthScore < 60 || d.daysSinceContact > 14);\n return atRisk\n .sort((a, b) => (sensitivityMap[b.name] ?? 0) - (sensitivityMap[a.name] ?? 0))\n .slice(0, 5)\n .map((d) => {\n const reasons: string[] = [];\n if (d.healthScore < 60) reasons.push(`health ${d.healthScore}`);\n if (d.daysSinceContact > 14) reasons.push(`${d.daysSinceContact}d no contact`);\n if (!d.championPresent) reasons.push(\"no champion\");\n return `${d.slug}/${d.name}: ${reasons.join(\", \")} — €${d.value} at risk`;\n });\n}\n\n// ─── Monte Carlo Core ─────────────────────────────────────────────────────────\n\nconst MAX_ITERATIONS = 50_000;\n\nexport function runSimulation(\n input: SimulationInput,\n randFn: () => number = Math.random\n): SimulationResult {\n const { deals, externalSignals } = input;\n const iterations = Math.min(input.iterations, MAX_ITERATIONS);\n\n if (deals.length === 0) {\n return {\n p10: 0,\n p50: 0,\n p90: 0,\n expected: 0,\n stdDev: 0,\n atRiskRevenue: 0,\n byCloseMonth: {},\n topRisks: [],\n sensitivityMap: {},\n };\n }\n\n const todayMs = new Date(input.today).getTime();\n const adjustedProbs = deals.map((d) => adjustProbability(d, externalSignals));\n const outcomes: number[] = [];\n const byMonthOutcomes: Record<string, number[]> = {};\n\n for (let i = 0; i < iterations; i++) {\n let total = 0;\n const monthTotals: Record<string, number> = {};\n\n for (let j = 0; j < deals.length; j++) {\n const deal = deals[j]!;\n const prob = adjustedProbs[j]!;\n if (randFn() < prob) {\n const closedValue = Math.round(deal.value * closeVarianceFn(deal, randFn, todayMs));\n total += closedValue;\n if (deal.closeDate) {\n const month = deal.closeDate.slice(0, 7);\n monthTotals[month] = (monthTotals[month] ?? 0) + closedValue;\n }\n }\n }\n\n outcomes.push(total);\n // Winning-only: only record months where at least one deal closed in this iteration\n for (const [month, val] of Object.entries(monthTotals)) {\n if (val > 0) {\n byMonthOutcomes[month] ??= [];\n byMonthOutcomes[month]!.push(val);\n }\n }\n }\n\n outcomes.sort((a, b) => a - b);\n const exp = Math.round(mean(outcomes));\n const sd = Math.round(stdDevFn(outcomes, exp));\n\n const byCloseMonth: Record<string, MonthForecast> = {};\n for (const [month, vals] of Object.entries(byMonthOutcomes)) {\n const sorted = [...vals].sort((a, b) => a - b);\n byCloseMonth[month] = {\n p50: Math.round(percentile(sorted, 50)),\n range: [Math.round(percentile(sorted, 10)), Math.round(percentile(sorted, 90))],\n };\n }\n\n const sensitivityMap = buildSensitivityMap(deals, externalSignals);\n const topRisks = buildTopRisks(deals, externalSignals, sensitivityMap);\n const atRiskRevenue = deals.filter((d) => d.healthScore < 60).reduce((s, d) => s + d.value, 0);\n\n return {\n p10: Math.round(percentile(outcomes, 10)),\n p50: Math.round(percentile(outcomes, 50)),\n p90: Math.round(percentile(outcomes, 90)),\n expected: exp,\n stdDev: sd,\n atRiskRevenue,\n byCloseMonth,\n topRisks,\n sensitivityMap,\n };\n}\n\n// ─── Confidence message ───────────────────────────────────────────────────────\n\nexport function buildConfidenceMessage(result: SimulationResult, dealCount: number): string {\n const range = result.p90 - result.p10;\n const atRiskPct =\n result.expected > 0 ? Math.round((result.atRiskRevenue / result.expected) * 100) : 0;\n return `P50 forecast: €${(result.p50 / 1000).toFixed(1)}k with ±€${(range / 2 / 1000).toFixed(1)}k uncertainty (P10–P90 range). ${atRiskPct}% of pipeline is at risk. ${dealCount} deals simulated.`;\n}\n\n// ─── Quarter helper ───────────────────────────────────────────────────────────\n\nfunction getQuarterEnd(date: Date): Date {\n const month = date.getMonth();\n const quarterEndMonth = Math.floor(month / 3) * 3 + 2;\n return new Date(date.getFullYear(), quarterEndMonth + 1, 0);\n}\n\n// ─── Data aggregation ─────────────────────────────────────────────────────────\n\nexport async function buildSimulationInput(\n dataDir: string,\n horizon: \"quarter\" | \"year\",\n today: string,\n externalSignals: ExternalSignal[] = []\n): Promise<SimulationInput> {\n const slugs = listCustomerSlugs(dataDir);\n if (slugs.length === 0) {\n return { deals: [], externalSignals, iterations: 10_000, horizon, today };\n }\n\n const stages = getPipelineStages(dataDir);\n const stageProb: Record<string, number> = {};\n for (const s of stages) {\n stageProb[s.id] = s.probability ?? 50;\n }\n\n const deals: DealSnapshot[] = [];\n const todayDate = new Date(today);\n const horizonEnd =\n horizon === \"quarter\" ? getQuarterEnd(todayDate) : new Date(todayDate.getFullYear(), 11, 31);\n\n for (const slug of slugs) {\n const pipelineDeals = await readPipeline(dataDir, slug).catch(() => []);\n if (pipelineDeals.length === 0) continue;\n\n const health = readHealth(dataDir, slug) ?? computeCustomerHealth(dataDir, slug, today);\n const healthScore = health.overallHealth;\n\n const graph = readGraph(dataDir, slug);\n const stakeholders = getStakeholders(graph);\n const championPresent = stakeholders.champions.length > 0;\n\n const lastContact = health.contacts\n .map((c) => c.lastContact)\n .filter((lc): lc is string => !!lc)\n .sort()\n .pop();\n const daysSinceContact = lastContact\n ? Math.floor((todayDate.getTime() - new Date(lastContact).getTime()) / 86_400_000)\n : 999;\n\n for (const deal of pipelineDeals) {\n if (deal.stage === \"won\" || deal.stage === \"lost\") continue;\n\n if (deal.close_date && deal.close_date.trim() !== \"\") {\n const closeDate = new Date(deal.close_date);\n if (closeDate > horizonEnd) continue;\n }\n\n const probability = deal.probability ?? stageProb[deal.stage] ?? 50;\n const snapshot: DealSnapshot = {\n slug,\n name: deal.name,\n stage: deal.stage,\n value: deal.value ?? 0,\n probability,\n healthScore,\n daysSinceContact,\n championPresent,\n };\n if (deal.close_date && deal.close_date.trim() !== \"\") {\n snapshot.closeDate = deal.close_date;\n }\n deals.push(snapshot);\n }\n }\n\n return { deals, externalSignals, iterations: 10_000, horizon, today };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"segments-BqcD5HIl.js","names":[],"sources":["../src/core/segments.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { readMainFacts, listCustomerSlugs } from \"../fs/customer-dir.js\";\n\n/**\n * Customer segments (marketing lists, N4-1): named filter criteria over\n * customers, evaluated on demand. Definitions live in .agentic/segments.json.\n */\nexport interface SegmentCriteria {\n stage?: string;\n tags?: string[];\n minDealValue?: number;\n staleDays?: number;\n}\n\nexport interface SegmentDefinition {\n name: string;\n criteria: SegmentCriteria;\n}\n\nfunction segmentsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"segments.json\");\n}\n\nexport function loadSegments(dataDir: string): SegmentDefinition[] {\n const p = segmentsPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as {\n segments?: SegmentDefinition[];\n };\n return Array.isArray(data.segments) ? data.segments : [];\n } catch {\n return [];\n }\n}\n\nexport function defineSegment(\n dataDir: string,\n name: string,\n criteria: SegmentCriteria\n): SegmentDefinition[] {\n const segs = loadSegments(dataDir);\n const idx = segs.findIndex((s) => s.name === name);\n const def: SegmentDefinition = { name, criteria };\n if (idx >= 0) segs[idx] = def;\n else segs.push(def);\n const p = segmentsPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ segments: segs }, null, 2), \"utf-8\");\n return segs;\n}\n\nexport function removeSegment(dataDir: string, name: string): boolean {\n const segs = loadSegments(dataDir);\n const next = segs.filter((s) => s.name !== name);\n if (next.length === segs.length) return false;\n fs.writeFileSync(segmentsPath(dataDir), JSON.stringify({ segments: next }, null, 2), \"utf-8\");\n return true;\n}\n\nfunction daysBetween(fromIso: string, toIso: string): number {\n const a = new Date(fromIso).getTime();\n const b = new Date(toIso).getTime();\n if (Number.isNaN(a) || Number.isNaN(b)) return 0;\n return Math.floor((b - a) / 86_400_000);\n}\n\n/** Return the customer slugs matching the criteria (now defaults to today). */\nexport async function evaluateSegment(\n dataDir: string,\n criteria: SegmentCriteria,\n now: string = new Date().toISOString().slice(0, 10)\n): Promise<string[]> {\n const matches: string[] = [];\n for (const slug of listCustomerSlugs(dataDir)) {\n const facts = await readMainFacts(dataDir, slug).catch(() => null);\n if (!facts) continue;\n\n if (criteria.stage && facts.relationship_stage !== criteria.stage) continue;\n if (\n criteria.minDealValue !== undefined &&\n !(typeof facts.deal_value === \"number\" && facts.deal_value >= criteria.minDealValue)\n ) {\n continue;\n }\n if (criteria.tags && criteria.tags.length > 0) {\n const tags = facts.tags ?? [];\n if (!criteria.tags.every((t) => tags.includes(t))) continue;\n }\n if (criteria.staleDays !== undefined) {\n // `updated` is the recency proxy (last record change).\n const lt = facts.updated;\n if (!lt || daysBetween(lt, now) < criteria.staleDays) continue;\n }\n matches.push(slug);\n }\n return matches;\n}\n"],"mappings":";;;;AAoBA,SAAS,aAAa,SAAyB;CAC7C,OAAO,KAAK,KAAK,SAAS,YAAY,eAAe;AACvD;AAEA,SAAgB,aAAa,SAAsC;CACjE,MAAM,IAAI,aAAa,OAAO;CAC9B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAG7D,OAAO,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC;CACzD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAgB,cACd,SACA,MACA,UACqB;CACrB,MAAM,OAAO,aAAa,OAAO;CACjC,MAAM,MAAM,KAAK,WAAW,MAAM,EAAE,SAAS,IAAI;CACjD,MAAM,MAAyB;EAAE;EAAM;CAAS;CAChD,IAAI,OAAO,GAAG,KAAK,OAAO;MACrB,KAAK,KAAK,GAAG;CAClB,MAAM,IAAI,aAAa,OAAO;CAC9B,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,EAAE,UAAU,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO;CACxE,OAAO;AACT;AAUA,SAAS,YAAY,SAAiB,OAAuB;CAC3D,MAAM,IAAI,IAAI,KAAK,OAAO,EAAE,QAAQ;CACpC,MAAM,IAAI,IAAI,KAAK,KAAK,EAAE,QAAQ;CAClC,IAAI,OAAO,MAAM,CAAC,KAAK,OAAO,MAAM,CAAC,GAAG,OAAO;CAC/C,OAAO,KAAK,OAAO,IAAI,KAAK,KAAU;AACxC;;AAGA,eAAsB,gBACpB,SACA,UACA,uBAAc,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,GAC/B;CACnB,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,QAAQ,kBAAkB,OAAO,GAAG;EAC7C,MAAM,QAAQ,MAAM,cAAc,SAAS,IAAI,EAAE,YAAY,IAAI;EACjE,IAAI,CAAC,OAAO;EAEZ,IAAI,SAAS,SAAS,MAAM,uBAAuB,SAAS,OAAO;EACnE,IACE,SAAS,iBAAiB,KAAA,KAC1B,EAAE,OAAO,MAAM,eAAe,YAAY,MAAM,cAAc,SAAS,eAEvE;EAEF,IAAI,SAAS,QAAQ,SAAS,KAAK,SAAS,GAAG;GAC7C,MAAM,OAAO,MAAM,QAAQ,CAAC;GAC5B,IAAI,CAAC,SAAS,KAAK,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,GAAG;EACrD;EACA,IAAI,SAAS,cAAc,KAAA,GAAW;GAEpC,MAAM,KAAK,MAAM;GACjB,IAAI,CAAC,MAAM,YAAY,IAAI,GAAG,IAAI,SAAS,WAAW;EACxD;EACA,QAAQ,KAAK,IAAI;CACnB;CACA,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sequence-engine-J1lTW_in.js","names":[],"sources":["../src/core/sequence-engine.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { getSequence, readEnrollments, updateEnrollment } from \"../fs/sequence-store.js\";\nimport { getTemplate } from \"../fs/template-store.js\";\nimport { interpolate, buildVariablesFromCustomer } from \"./template-engine.js\";\nimport type { SequenceEnrollment } from \"../schemas/sequence.js\";\n\n/**\n * Add n days to an ISO date string (YYYY-MM-DD) and return YYYY-MM-DD.\n */\nexport function addDays(isoDateStr: string, n: number): string {\n // Parse the date parts to avoid timezone issues\n const [year, month, day] = isoDateStr.split(\"-\").map(Number) as [number, number, number];\n const date = new Date(Date.UTC(year, month - 1, day));\n date.setUTCDate(date.getUTCDate() + n);\n return date.toISOString().slice(0, 10);\n}\n\nexport async function processSequenceStep(\n dataDir: string,\n enrollment: SequenceEnrollment,\n today: string // YYYY-MM-DD\n): Promise<\"sent\" | \"skipped_replied\" | \"completed\" | \"no_step_due\"> {\n const sequence = getSequence(dataDir, enrollment.sequenceId);\n if (!sequence) {\n process.stderr.write(`[sequences] Sequence not found: ${enrollment.sequenceId}\\n`);\n return \"no_step_due\";\n }\n\n // All steps completed\n if (enrollment.currentStep >= sequence.steps.length) {\n await updateEnrollment(dataDir, enrollment.id, { status: \"completed\" });\n return \"completed\";\n }\n\n const step = sequence.steps[enrollment.currentStep]!;\n\n // Calculate due date: enrolledAt date + step.day\n const enrolledDate = enrollment.enrolledAt.slice(0, 10); // YYYY-MM-DD\n const dueDate = addDays(enrolledDate, step.day);\n\n // Not yet due\n if (today < dueDate) {\n return \"no_step_due\";\n }\n\n // Skip if replied and skipIfReplied is true\n if (step.skipIfReplied && enrollment.lastRepliedAt) {\n await updateEnrollment(dataDir, enrollment.id, {\n currentStep: enrollment.currentStep + 1,\n });\n return \"skipped_replied\";\n }\n\n // Load template\n const template = getTemplate(dataDir, step.templateId);\n if (!template) {\n process.stderr.write(`[sequences] Template not found: ${step.templateId}, skipping step\\n`);\n await updateEnrollment(dataDir, enrollment.id, {\n currentStep: enrollment.currentStep + 1,\n lastSentAt: new Date().toISOString(),\n });\n return \"no_step_due\";\n }\n\n // Build variables\n const vars = await buildVariablesFromCustomer(dataDir, enrollment.slug);\n vars[\"contactEmail\"] = enrollment.contactEmail;\n\n // Interpolate\n const subject = interpolate(template.subject, vars);\n const body = interpolate(template.body, vars);\n\n // Try Gmail send if credentials available\n const tokenPath = path.join(dataDir, \".agentic\", \"gmail-token.json\");\n const credPath = path.join(dataDir, \".agentic\", \"gmail-credentials.json\");\n\n if (fs.existsSync(tokenPath) && fs.existsSync(credPath)) {\n try {\n const { getGmailAuth } = await import(\"../sync/gmail-auth.js\");\n const { sendEmail } = await import(\"../sync/gmail-sender.js\");\n const auth = await getGmailAuth(credPath, tokenPath);\n await sendEmail({\n auth,\n to: enrollment.contactEmail,\n subject,\n body,\n isHtml: false,\n });\n process.stderr.write(\n `[sequences] Sent step ${enrollment.currentStep} to ${enrollment.contactEmail}\\n`\n );\n } catch (err) {\n process.stderr.write(`[sequences] Send failed: ${(err as Error).message}\\n`);\n }\n } else {\n process.stderr.write(\n `[sequences] Gmail not configured, advancing step for ${enrollment.contactEmail}\\n`\n );\n }\n\n // Update enrollment\n await updateEnrollment(dataDir, enrollment.id, {\n currentStep: enrollment.currentStep + 1,\n lastSentAt: new Date().toISOString(),\n stepsCompleted: [...enrollment.stepsCompleted, enrollment.currentStep],\n });\n\n return \"sent\";\n}\n\nexport async function runSequenceCycle(\n dataDir: string,\n today: string\n): Promise<{ processed: number; sent: number; completed: number; errors: string[] }> {\n const enrollments = readEnrollments(dataDir);\n const active = enrollments.filter((e) => e.status === \"active\");\n\n let sent = 0;\n let completed = 0;\n const errors: string[] = [];\n\n for (const enrollment of active) {\n try {\n const result = await processSequenceStep(dataDir, enrollment, today);\n if (result === \"sent\") sent++;\n if (result === \"completed\") completed++;\n } catch (err) {\n const msg = `${enrollment.id}: ${(err as Error).message}`;\n errors.push(msg);\n process.stderr.write(\n `[sequences] Error processing ${enrollment.id}: ${(err as Error).message}\\n`\n );\n }\n }\n\n return { processed: active.length, sent, completed, errors };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sequence-store-DaaWr0Os.js","names":[],"sources":["../src/schemas/email-template.ts","../src/fs/template-store.ts","../src/core/template-engine.ts","../src/schemas/sequence.ts","../src/fs/sequence-store.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const EmailTemplateSchema = z.object({\n id: z.string().min(1),\n subject: z.string().min(1),\n category: z.string().default(\"general\"),\n variables: z.array(z.string()).default([]),\n language: z.string().default(\"de\"),\n createdAt: z.string(),\n updatedAt: z.string().optional(),\n});\n\nexport type EmailTemplateMeta = z.infer<typeof EmailTemplateSchema>;\n\nexport interface EmailTemplate extends EmailTemplateMeta {\n body: string;\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\nimport { EmailTemplateSchema, type EmailTemplate } from \"../schemas/email-template.js\";\n\nexport function templatesDir(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"templates\");\n}\n\nfunction parseTemplateFile(filePath: string, id: string): EmailTemplate | null {\n try {\n const raw = matter(fs.readFileSync(filePath, \"utf-8\"));\n const result = EmailTemplateSchema.safeParse({\n id,\n createdAt: new Date().toISOString(),\n ...raw.data,\n });\n if (!result.success) return null;\n return { ...result.data, body: raw.content.trim() };\n } catch {\n return null;\n }\n}\n\nexport function listTemplates(dataDir: string, opts?: { category?: string }): EmailTemplate[] {\n const base = templatesDir(dataDir);\n if (!fs.existsSync(base)) return [];\n const results: EmailTemplate[] = [];\n\n const categories = fs.readdirSync(base).filter((name) => {\n try {\n return fs.statSync(path.join(base, name)).isDirectory();\n } catch {\n return false;\n }\n });\n\n for (const cat of categories) {\n if (opts?.category && cat !== opts.category) continue;\n const catDir = path.join(base, cat);\n const files = fs.readdirSync(catDir).filter((f) => f.endsWith(\".md\"));\n for (const file of files) {\n const id = file.replace(/\\.md$/, \"\");\n const tmpl = parseTemplateFile(path.join(catDir, file), id);\n if (tmpl) results.push(tmpl);\n }\n }\n return results;\n}\n\nexport function getTemplate(dataDir: string, id: string): EmailTemplate | null {\n const base = templatesDir(dataDir);\n if (!fs.existsSync(base)) return null;\n\n // Search all categories\n const categories = fs.existsSync(base)\n ? fs.readdirSync(base).filter((n) => {\n try {\n return fs.statSync(path.join(base, n)).isDirectory();\n } catch {\n return false;\n }\n })\n : [];\n\n for (const cat of categories) {\n const p = path.join(base, cat, `${id}.md`);\n if (fs.existsSync(p)) return parseTemplateFile(p, id);\n }\n return null;\n}\n\nexport function writeTemplate(dataDir: string, tmpl: EmailTemplate): void {\n const { body, ...meta } = tmpl;\n const category = meta.category ?? \"general\";\n const dir = path.join(templatesDir(dataDir), category);\n fs.mkdirSync(dir, { recursive: true });\n const content = matter.stringify(body, { ...meta, updatedAt: new Date().toISOString() });\n fs.writeFileSync(path.join(dir, `${tmpl.id}.md`), content, \"utf-8\");\n}\n\nexport function deleteTemplate(dataDir: string, id: string): boolean {\n const tmpl = getTemplate(dataDir, id);\n if (!tmpl) return false;\n const p = path.join(templatesDir(dataDir), tmpl.category, `${id}.md`);\n if (fs.existsSync(p)) {\n fs.unlinkSync(p);\n return true;\n }\n return false;\n}\n","import { readMainFacts } from \"../fs/customer-dir.js\";\n\nexport type TemplateVariables = Record<string, string | number | undefined>;\n\nconst VARIABLE_REGEX = /\\{\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}\\}/g;\n\nexport function interpolate(template: string, vars: TemplateVariables): string {\n return template.replace(VARIABLE_REGEX, (match, key: string) => {\n const val = vars[key];\n return val !== undefined ? String(val) : match; // keep {{key}} if unresolved\n });\n}\n\nexport function extractVariables(template: string): string[] {\n return [...template.matchAll(new RegExp(VARIABLE_REGEX.source, \"g\"))].map((m) => m[1]!);\n}\n\nexport async function buildVariablesFromCustomer(\n dataDir: string,\n slug: string\n): Promise<TemplateVariables> {\n const facts = await readMainFacts(dataDir, slug).catch(() => null);\n const now = new Date();\n return {\n company: facts?.name ?? slug,\n domain: facts?.domain ?? \"\",\n email: facts?.email ?? \"\",\n stage: facts?.relationship_stage ?? \"\",\n slug,\n date: now.toLocaleDateString(\"de-DE\"),\n year: now.getFullYear(),\n month: now.toLocaleDateString(\"de-DE\", { month: \"long\" }),\n };\n}\n","import { z } from \"zod\";\n\nexport const SequenceStepSchema = z.object({\n day: z.number().int().min(0),\n templateId: z.string().min(1),\n skipIfReplied: z.boolean().default(true),\n});\n\nexport const SequenceSchema = z.object({\n id: z.string().min(1),\n name: z.string().min(1),\n steps: z.array(SequenceStepSchema).min(1),\n createdAt: z.string(),\n});\n\nexport const SequenceEnrollmentSchema = z.object({\n id: z.string(),\n sequenceId: z.string(),\n slug: z.string(),\n contactEmail: z.string().email(),\n enrolledAt: z.string(),\n status: z.enum([\"active\", \"paused\", \"completed\", \"bounced\"]),\n currentStep: z.number().int().min(0),\n stepsCompleted: z.array(z.number()),\n lastSentAt: z.string().optional(),\n lastRepliedAt: z.string().optional(),\n});\n\nexport type SequenceStep = z.infer<typeof SequenceStepSchema>;\nexport type Sequence = z.infer<typeof SequenceSchema>;\nexport type SequenceEnrollment = z.infer<typeof SequenceEnrollmentSchema>;\n","import fs from \"fs\";\nimport path from \"path\";\nimport yaml from \"js-yaml\";\nimport { withJsonFile } from \"../core/file-lock.js\";\nimport {\n SequenceSchema,\n SequenceEnrollmentSchema,\n type Sequence,\n type SequenceEnrollment,\n} from \"../schemas/sequence.js\";\n\nexport function sequencesDir(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"sequences\");\n}\n\nexport function enrollmentsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"sequence-enrollments.json\");\n}\n\nexport function listSequences(dataDir: string): Sequence[] {\n const dir = sequencesDir(dataDir);\n if (!fs.existsSync(dir)) return [];\n\n const files = fs.readdirSync(dir).filter((f) => f.endsWith(\".yaml\"));\n const results: Sequence[] = [];\n\n for (const file of files) {\n try {\n const content = fs.readFileSync(path.join(dir, file), \"utf-8\") as string;\n const raw = yaml.load(content);\n const parsed = SequenceSchema.safeParse(raw);\n if (parsed.success) {\n results.push(parsed.data);\n }\n } catch {\n // skip invalid files\n }\n }\n\n return results;\n}\n\nexport function getSequence(dataDir: string, id: string): Sequence | null {\n const sequences = listSequences(dataDir);\n return sequences.find((s) => s.id === id) ?? null;\n}\n\nexport function writeSequence(dataDir: string, seq: Sequence): void {\n const dir = sequencesDir(dataDir);\n fs.mkdirSync(dir, { recursive: true });\n const content = yaml.dump(seq);\n fs.writeFileSync(path.join(dir, `${seq.id}.yaml`), content, \"utf-8\");\n}\n\nexport function readEnrollments(dataDir: string): SequenceEnrollment[] {\n const p = enrollmentsPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n return Array.isArray(raw) ? (raw as SequenceEnrollment[]) : [];\n } catch {\n return [];\n }\n}\n\nexport async function writeEnrollment(\n dataDir: string,\n enrollment: SequenceEnrollment\n): Promise<void> {\n await withJsonFile<SequenceEnrollment[]>(enrollmentsPath(dataDir), (current) => {\n const existing = Array.isArray(current) ? current : [];\n return [...existing, enrollment];\n });\n}\n\nexport async function updateEnrollment(\n dataDir: string,\n id: string,\n updates: Partial<SequenceEnrollment>\n): Promise<SequenceEnrollment | null> {\n let updated: SequenceEnrollment | null = null;\n\n await withJsonFile<SequenceEnrollment[]>(enrollmentsPath(dataDir), (current) => {\n const existing = Array.isArray(current) ? current : [];\n const idx = existing.findIndex((e) => e.id === id);\n if (idx < 0) return existing;\n\n const merged = SequenceEnrollmentSchema.parse({ ...existing[idx], ...updates });\n updated = merged;\n const next = [...existing];\n next[idx] = merged;\n return next;\n });\n\n return updated;\n}\n"],"mappings":"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"}
|