@datasynx/agentic-crm 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +264 -670
- package/dist/{approvals-DpjxGHFp.js → approvals-CmDT2eUg.js} +7 -24
- package/dist/approvals-CmDT2eUg.js.map +1 -0
- package/dist/{ask-CID3jnuL.js → ask-D8iYqDAr.js} +6 -6
- package/dist/{ask-CID3jnuL.js.map → ask-D8iYqDAr.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/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/{churn-C28IgnAj.js → churn-DN9WDGNM.js} +3 -3
- package/dist/{churn-C28IgnAj.js.map → churn-DN9WDGNM.js.map} +1 -1
- package/dist/cli.js +282 -184
- 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/{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-rQaVqKWd.js → gmail-sync-C-NmibzS.js} +13 -9
- package/dist/gmail-sync-C-NmibzS.js.map +1 -0
- package/dist/{gmail-sync-DIaxInDT.js → gmail-sync-DueE6tl5.js} +14 -10
- package/dist/gmail-sync-DueE6tl5.js.map +1 -0
- package/dist/{gmail-sync-hHm9gaWd.cjs → gmail-sync-GEy3oVvw.cjs} +13 -9
- package/dist/gmail-sync-GEy3oVvw.cjs.map +1 -0
- package/dist/{gmail-webhook-handler-DS7OlRPX.js → gmail-webhook-handler-B26COilD.js} +2 -2
- package/dist/{gmail-webhook-handler-e5Od25FX.js → gmail-webhook-handler-kGKpbY9h.js} +4 -4
- package/dist/{gmail-webhook-handler-e5Od25FX.js.map → gmail-webhook-handler-kGKpbY9h.js.map} +1 -1
- 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-D1n7WKZn.js} +3 -3
- package/dist/{google-drive-sync-DEPcqFca.js.map → google-drive-sync-D1n7WKZn.js.map} +1 -1
- 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-DB4n89jy.js} +51 -45
- package/dist/import-hubspot-DB4n89jy.js.map +1 -0
- package/dist/{index-V8BFaH-b.d.ts → index-B0IMMrp_.d.ts} +8 -4
- package/dist/{index-V8BFaH-b.d.ts.map → index-B0IMMrp_.d.ts.map} +1 -1
- package/dist/{index-YqwMd6aQ.d.cts → index-pY7tYXwH.d.cts} +8 -4
- package/dist/{index-YqwMd6aQ.d.cts.map → index-pY7tYXwH.d.cts.map} +1 -1
- package/dist/index.cjs +19 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +8 -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-DO3KcSR3.js → interactions-writer-BZzUIgJd.js} +5 -2
- package/dist/interactions-writer-BZzUIgJd.js.map +1 -0
- package/dist/{interactions-writer-SLHnoEeE.js → interactions-writer-DbSyI2rt.js} +32 -3
- package/dist/interactions-writer-DbSyI2rt.js.map +1 -0
- package/dist/interactions-writer-RJB8SWf2.js +2 -0
- package/dist/{interactions-writer-CrPStUll.cjs → interactions-writer-a2yzBd7T.cjs} +5 -2
- package/dist/interactions-writer-a2yzBd7T.cjs.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-DHNc4hVj.js} +43 -16
- package/dist/knowledge-base-DHNc4hVj.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 +327 -303
- 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 +327 -303
- 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-jIu9K5zX.js} +4 -4
- package/dist/{microsoft-calendar-B6MMtUQK.js.map → microsoft-calendar-jIu9K5zX.js.map} +1 -1
- package/dist/{microsoft-sync-CpZVoSuq.js → microsoft-sync-R_r8HL-B.js} +5 -5
- package/dist/{microsoft-sync-CpZVoSuq.js.map → microsoft-sync-R_r8HL-B.js.map} +1 -1
- package/dist/{nba-3wanmJ0U.js → nba-mTJ4yEqD.js} +3 -3
- package/dist/{nba-3wanmJ0U.js.map → nba-mTJ4yEqD.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-DqSMYhSA.js} +278 -220
- package/dist/server-DqSMYhSA.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-0mh2ZhmH.js} +18 -11
- package/dist/transcript-watcher-0mh2ZhmH.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 +11 -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/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,5 +1,5 @@
|
|
|
1
|
+
import { i as writeJsonFile, n as readJsonFile, r as writeJsonArray, t as readJsonArray } from "./json-store-WWsFzXub.js";
|
|
1
2
|
import path from "path";
|
|
2
|
-
import fs from "fs";
|
|
3
3
|
import "crypto";
|
|
4
4
|
//#region src/core/approvals.ts
|
|
5
5
|
function policyPath(dataDir) {
|
|
@@ -9,13 +9,7 @@ function approvalsPath(dataDir) {
|
|
|
9
9
|
return path.join(dataDir, ".agentic", "approvals.json");
|
|
10
10
|
}
|
|
11
11
|
function loadPolicyConfig(dataDir) {
|
|
12
|
-
|
|
13
|
-
if (!fs.existsSync(p)) return {};
|
|
14
|
-
try {
|
|
15
|
-
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
16
|
-
} catch {
|
|
17
|
-
return {};
|
|
18
|
-
}
|
|
12
|
+
return readJsonFile(policyPath(dataDir), {});
|
|
19
13
|
}
|
|
20
14
|
function setPolicy(dataDir, tool, policy, slug) {
|
|
21
15
|
const cfg = loadPolicyConfig(dataDir);
|
|
@@ -29,25 +23,14 @@ function setPolicy(dataDir, tool, policy, slug) {
|
|
|
29
23
|
...cfg.tools ?? {},
|
|
30
24
|
[tool]: policy
|
|
31
25
|
};
|
|
32
|
-
|
|
33
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
34
|
-
fs.writeFileSync(p, JSON.stringify(cfg, null, 2), "utf-8");
|
|
26
|
+
writeJsonFile(policyPath(dataDir), cfg);
|
|
35
27
|
}
|
|
36
28
|
function listApprovals(dataDir, status) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const all = JSON.parse(fs.readFileSync(p, "utf-8")).approvals;
|
|
41
|
-
const list = Array.isArray(all) ? all : [];
|
|
42
|
-
return status ? list.filter((a) => a.status === status) : list;
|
|
43
|
-
} catch {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
29
|
+
const list = readJsonArray(approvalsPath(dataDir), "approvals");
|
|
30
|
+
return status ? list.filter((a) => a.status === status) : list;
|
|
46
31
|
}
|
|
47
32
|
function writeApprovals(dataDir, approvals) {
|
|
48
|
-
|
|
49
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
50
|
-
fs.writeFileSync(p, JSON.stringify({ approvals }, null, 2), "utf-8");
|
|
33
|
+
writeJsonArray(approvalsPath(dataDir), "approvals", approvals);
|
|
51
34
|
}
|
|
52
35
|
function decideApproval(dataDir, id, decision) {
|
|
53
36
|
const all = listApprovals(dataDir);
|
|
@@ -64,4 +47,4 @@ function decideApproval(dataDir, id, decision) {
|
|
|
64
47
|
//#endregion
|
|
65
48
|
export { decideApproval, listApprovals, setPolicy };
|
|
66
49
|
|
|
67
|
-
//# sourceMappingURL=approvals-
|
|
50
|
+
//# sourceMappingURL=approvals-CmDT2eUg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approvals-CmDT2eUg.js","names":[],"sources":["../src/core/approvals.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport path from \"path\";\nimport { readJsonFile, writeJsonFile, readJsonArray, writeJsonArray } from \"../fs/json-store.js\";\n\n/**\n * Human-in-the-loop / approval gate (domino D4 / F3). A per-tool, per-customer\n * autonomy policy (auto | approve | block) decides whether an agent action runs\n * immediately, is queued for human approval, or is blocked. The generic gate\n * wraps any write/automation action so later features get HITL for free.\n */\nexport type Policy = \"auto\" | \"approve\" | \"block\";\n\ninterface PolicyConfig {\n default?: Policy;\n tools?: Record<string, Policy>;\n customers?: Record<string, Record<string, Policy>>;\n}\n\nexport interface Approval {\n id: string;\n tool: string;\n slug?: string;\n payload: unknown;\n status: \"pending\" | \"approved\" | \"rejected\";\n requestedAt: string;\n decidedAt?: string;\n}\n\nfunction policyPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"policy.json\");\n}\nfunction approvalsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"approvals.json\");\n}\n\nfunction loadPolicyConfig(dataDir: string): PolicyConfig {\n return readJsonFile<PolicyConfig>(policyPath(dataDir), {});\n}\n\n/** Resolve the effective policy: customer-specific → global tool → default (auto). */\nexport function getPolicy(dataDir: string, tool: string, slug?: string): Policy {\n const cfg = loadPolicyConfig(dataDir);\n if (slug && cfg.customers?.[slug]?.[tool]) return cfg.customers[slug]![tool]!;\n if (cfg.tools?.[tool]) return cfg.tools[tool]!;\n return cfg.default ?? \"auto\";\n}\n\nexport function setPolicy(dataDir: string, tool: string, policy: Policy, slug?: string): void {\n const cfg = loadPolicyConfig(dataDir);\n if (slug) {\n cfg.customers = cfg.customers ?? {};\n cfg.customers[slug] = { ...(cfg.customers[slug] ?? {}), [tool]: policy };\n } else {\n cfg.tools = { ...(cfg.tools ?? {}), [tool]: policy };\n }\n writeJsonFile(policyPath(dataDir), cfg);\n}\n\nexport function listApprovals(dataDir: string, status?: Approval[\"status\"]): Approval[] {\n const list = readJsonArray<Approval>(approvalsPath(dataDir), \"approvals\");\n return status ? list.filter((a) => a.status === status) : list;\n}\n\nfunction writeApprovals(dataDir: string, approvals: Approval[]): void {\n writeJsonArray(approvalsPath(dataDir), \"approvals\", approvals);\n}\n\nexport function requestApproval(\n dataDir: string,\n req: { tool: string; slug?: string; payload: unknown }\n): Approval {\n const approval: Approval = {\n id: `apr_${randomBytes(5).toString(\"hex\")}`,\n tool: req.tool,\n ...(req.slug ? { slug: req.slug } : {}),\n payload: req.payload,\n status: \"pending\",\n requestedAt: new Date().toISOString(),\n };\n writeApprovals(dataDir, [...listApprovals(dataDir), approval]);\n return approval;\n}\n\nexport function decideApproval(\n dataDir: string,\n id: string,\n decision: \"approved\" | \"rejected\"\n): boolean {\n const all = listApprovals(dataDir);\n const idx = all.findIndex((a) => a.id === id);\n if (idx < 0) return false;\n all[idx] = { ...all[idx]!, status: decision, decidedAt: new Date().toISOString() };\n writeApprovals(dataDir, all);\n return true;\n}\n\nexport interface GateResult<T> {\n status: \"executed\" | \"pending\" | \"blocked\";\n result?: T;\n approvalId?: string;\n}\n\n/** Gate an action by the effective policy: run, queue for approval, or block. */\nexport async function gateAction<T>(\n dataDir: string,\n action: { tool: string; slug?: string; payload: unknown },\n execute: () => T | Promise<T>\n): Promise<GateResult<T>> {\n const policy = getPolicy(dataDir, action.tool, action.slug);\n if (policy === \"block\") return { status: \"blocked\" };\n if (policy === \"approve\") {\n const approval = requestApproval(dataDir, action);\n return { status: \"pending\", approvalId: approval.id };\n }\n return { status: \"executed\", result: await execute() };\n}\n"],"mappings":";;;;AA4BA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,aAAa;AACrD;AACA,SAAS,cAAc,SAAyB;CAC9C,OAAO,KAAK,KAAK,SAAS,YAAY,gBAAgB;AACxD;AAEA,SAAS,iBAAiB,SAA+B;CACvD,OAAO,aAA2B,WAAW,OAAO,GAAG,CAAC,CAAC;AAC3D;AAUA,SAAgB,UAAU,SAAiB,MAAc,QAAgB,MAAqB;CAC5F,MAAM,MAAM,iBAAiB,OAAO;CACpC,IAAI,MAAM;EACR,IAAI,YAAY,IAAI,aAAa,CAAC;EAClC,IAAI,UAAU,QAAQ;GAAE,GAAI,IAAI,UAAU,SAAS,CAAC;IAAK,OAAO;EAAO;CACzE,OACE,IAAI,QAAQ;EAAE,GAAI,IAAI,SAAS,CAAC;GAAK,OAAO;CAAO;CAErD,cAAc,WAAW,OAAO,GAAG,GAAG;AACxC;AAEA,SAAgB,cAAc,SAAiB,QAAyC;CACtF,MAAM,OAAO,cAAwB,cAAc,OAAO,GAAG,WAAW;CACxE,OAAO,SAAS,KAAK,QAAQ,MAAM,EAAE,WAAW,MAAM,IAAI;AAC5D;AAEA,SAAS,eAAe,SAAiB,WAA6B;CACpE,eAAe,cAAc,OAAO,GAAG,aAAa,SAAS;AAC/D;AAkBA,SAAgB,eACd,SACA,IACA,UACS;CACT,MAAM,MAAM,cAAc,OAAO;CACjC,MAAM,MAAM,IAAI,WAAW,MAAM,EAAE,OAAO,EAAE;CAC5C,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,OAAO;EAAE,GAAG,IAAI;EAAO,QAAQ;EAAU,4BAAW,IAAI,KAAK,GAAE,YAAY;CAAE;CACjF,eAAe,SAAS,GAAG;CAC3B,OAAO;AACT"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as readPipeline } from "./pipeline-writer-
|
|
1
|
+
import { i as readInteractions } from "./interactions-writer-DbSyI2rt.js";
|
|
2
|
+
import { t as readPipeline } from "./pipeline-writer-CIllfnZl.js";
|
|
3
3
|
import { t as hybridSearch } from "./hybrid-search-BmHttLrR.js";
|
|
4
|
-
import { n as loadMemories } from "./memory-
|
|
5
|
-
import { r as loadSops } from "./sop-
|
|
4
|
+
import { n as loadMemories } from "./memory-Dzr9dXSM.js";
|
|
5
|
+
import { r as loadSops } from "./sop-BV7ICAFR.js";
|
|
6
6
|
//#region src/core/ask.ts
|
|
7
7
|
async function gatherCorpus(dataDir, slug) {
|
|
8
8
|
const docs = [];
|
|
@@ -34,7 +34,7 @@ async function askCrm(dataDir, question, slug) {
|
|
|
34
34
|
const sources = ranked.map((r) => byId.get(r.id)).filter(Boolean);
|
|
35
35
|
if (sources.length === 0) return { sources: [] };
|
|
36
36
|
try {
|
|
37
|
-
const { callLlm } = await import("./llm-
|
|
37
|
+
const { callLlm } = await import("./llm-DSX1-wFu.js");
|
|
38
38
|
return {
|
|
39
39
|
answer: await callLlm(`Answer the question using ONLY the context. Cite snippet numbers. If unknown, say so.\n\nQuestion: ${question}\n\nContext:\n${sources.map((s, i) => `[${i + 1}] ${s.text}`).join("\n")}`, {
|
|
40
40
|
tool: "ask_crm",
|
|
@@ -49,4 +49,4 @@ async function askCrm(dataDir, question, slug) {
|
|
|
49
49
|
//#endregion
|
|
50
50
|
export { askCrm };
|
|
51
51
|
|
|
52
|
-
//# sourceMappingURL=ask-
|
|
52
|
+
//# sourceMappingURL=ask-D8iYqDAr.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-
|
|
1
|
+
{"version":3,"file":"ask-D8iYqDAr.js","names":[],"sources":["../src/core/ask.ts"],"sourcesContent":["import { hybridSearch, type HybridDoc } from \"./hybrid-search.js\";\nimport { loadMemories } from \"./memory.js\";\nimport { loadSops } from \"./sop.js\";\nimport { readInteractions } from \"../fs/interactions-writer.js\";\nimport { readPipeline } from \"../fs/pipeline-writer.js\";\n\n/**\n * Ask-your-CRM (domino D10 / C2): natural-language Q&A over CRM data. Gathers a\n * corpus (interactions, pipeline, memories, SOPs), retrieves relevant snippets\n * via hybrid search, and — when an LLM is available — synthesizes a grounded\n * answer. Without an LLM it returns the ranked sources (still useful).\n */\nexport interface AskResult {\n answer?: string;\n sources: Array<{ id: string; text: string }>;\n}\n\nexport async function gatherCorpus(dataDir: string, slug?: string): Promise<HybridDoc[]> {\n const docs: HybridDoc[] = [];\n\n for (const m of loadMemories(dataDir, slug)) docs.push({ id: `mem:${m.id}`, text: m.text });\n for (const s of loadSops(dataDir, slug))\n docs.push({ id: `sop:${s.id}`, text: `${s.title} ${s.triggers.join(\" \")} ${s.body}` });\n\n if (slug) {\n const interactions = await readInteractions(dataDir, slug).catch(() => \"\");\n interactions\n .split(/(?=^## )/m)\n .map((e) => e.trim())\n .filter((e) => e && !e.startsWith(\"# \"))\n .forEach((e, i) => docs.push({ id: `int:${slug}:${i}`, text: e }));\n\n const deals = await readPipeline(dataDir, slug).catch(() => []);\n for (const d of deals)\n docs.push({\n id: `deal:${d.name}`,\n text: `${d.name} stage ${d.stage} value ${d.value ?? \"\"} ${d.notes ?? \"\"}`,\n });\n }\n\n return docs;\n}\n\nexport async function askCrm(dataDir: string, question: string, slug?: string): Promise<AskResult> {\n const corpus = await gatherCorpus(dataDir, slug);\n const ranked = hybridSearch(question, corpus, { limit: 6 });\n const byId = new Map(corpus.map((d) => [d.id, d]));\n const sources = ranked.map((r) => byId.get(r.id)!).filter(Boolean);\n\n if (sources.length === 0) return { sources: [] };\n\n try {\n const { callLlm } = await import(\"./llm.js\");\n const context = sources.map((s, i) => `[${i + 1}] ${s.text}`).join(\"\\n\");\n const answer = await callLlm(\n `Answer the question using ONLY the context. Cite snippet numbers. If unknown, say so.\\n\\n` +\n `Question: ${question}\\n\\nContext:\\n${context}`,\n { tool: \"ask_crm\", ...(slug ? { slug } : {}) }\n );\n return { answer, sources };\n } catch {\n return { sources };\n }\n}\n"],"mappings":";;;;;;AAiBA,eAAsB,aAAa,SAAiB,MAAqC;CACvF,MAAM,OAAoB,CAAC;CAE3B,KAAK,MAAM,KAAK,aAAa,SAAS,IAAI,GAAG,KAAK,KAAK;EAAE,IAAI,OAAO,EAAE;EAAM,MAAM,EAAE;CAAK,CAAC;CAC1F,KAAK,MAAM,KAAK,SAAS,SAAS,IAAI,GACpC,KAAK,KAAK;EAAE,IAAI,OAAO,EAAE;EAAM,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE,SAAS,KAAK,GAAG,EAAE,GAAG,EAAE;CAAO,CAAC;CAEvF,IAAI,MAAM;EAER,CAAA,MAD2B,iBAAiB,SAAS,IAAI,EAAE,YAAY,EAAE,GAEtE,MAAM,WAAW,EACjB,KAAK,MAAM,EAAE,KAAK,CAAC,EACnB,QAAQ,MAAM,KAAK,CAAC,EAAE,WAAW,IAAI,CAAC,EACtC,SAAS,GAAG,MAAM,KAAK,KAAK;GAAE,IAAI,OAAO,KAAK,GAAG;GAAK,MAAM;EAAE,CAAC,CAAC;EAEnE,MAAM,QAAQ,MAAM,aAAa,SAAS,IAAI,EAAE,YAAY,CAAC,CAAC;EAC9D,KAAK,MAAM,KAAK,OACd,KAAK,KAAK;GACR,IAAI,QAAQ,EAAE;GACd,MAAM,GAAG,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE,SAAS;EACxE,CAAC;CACL;CAEA,OAAO;AACT;AAEA,eAAsB,OAAO,SAAiB,UAAkB,MAAmC;CACjG,MAAM,SAAS,MAAM,aAAa,SAAS,IAAI;CAC/C,MAAM,SAAS,aAAa,UAAU,QAAQ,EAAE,OAAO,EAAE,CAAC;CAC1D,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACjD,MAAM,UAAU,OAAO,KAAK,MAAM,KAAK,IAAI,EAAE,EAAE,CAAE,EAAE,OAAO,OAAO;CAEjE,IAAI,QAAQ,WAAW,GAAG,OAAO,EAAE,SAAS,CAAC,EAAE;CAE/C,IAAI;EACF,MAAM,EAAE,YAAY,MAAM,OAAO;EAOjC,OAAO;GAAE,QAAA,MALY,QACnB,sGACe,SAAS,gBAHV,QAAQ,KAAK,GAAG,MAAM,IAAI,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,IAGnB,KAC9C;IAAE,MAAM;IAAW,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GAAG,CAC/C;GACiB;EAAQ;CAC3B,QAAQ;EACN,OAAO,EAAE,QAAQ;CACnB;AACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { randomBytes } from "crypto";
|
|
4
|
+
//#region src/fs/atomic-write.ts
|
|
5
|
+
/**
|
|
6
|
+
* Atomically write `content` to `filePath`, creating parent directories as
|
|
7
|
+
* needed. The payload is written to a sibling temp file and then renamed over
|
|
8
|
+
* the target. rename(2) is atomic within a filesystem, so a crash or a
|
|
9
|
+
* concurrent reader can never observe a half-written (truncated/corrupt) file —
|
|
10
|
+
* only the complete old or complete new content. Used for every durable file
|
|
11
|
+
* the product owns (customer Markdown, JSON config/state, audit logs).
|
|
12
|
+
*/
|
|
13
|
+
function writeFileAtomic(filePath, content) {
|
|
14
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
15
|
+
const tmp = `${filePath}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
16
|
+
try {
|
|
17
|
+
fs.writeFileSync(tmp, content, "utf-8");
|
|
18
|
+
fs.renameSync(tmp, filePath);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
try {
|
|
21
|
+
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
|
|
22
|
+
} catch {}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { writeFileAtomic as t };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=atomic-write-8yjqqLtS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write-8yjqqLtS.js","names":[],"sources":["../src/fs/atomic-write.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { randomBytes } from \"crypto\";\n\n/**\n * Atomically write `content` to `filePath`, creating parent directories as\n * needed. The payload is written to a sibling temp file and then renamed over\n * the target. rename(2) is atomic within a filesystem, so a crash or a\n * concurrent reader can never observe a half-written (truncated/corrupt) file —\n * only the complete old or complete new content. Used for every durable file\n * the product owns (customer Markdown, JSON config/state, audit logs).\n */\nexport function writeFileAtomic(filePath: string, content: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n const tmp = `${filePath}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n try {\n fs.writeFileSync(tmp, content, \"utf-8\");\n fs.renameSync(tmp, filePath);\n } catch (err) {\n // Best-effort cleanup of the temp file if the write/rename failed.\n try {\n if (fs.existsSync(tmp)) fs.unlinkSync(tmp);\n } catch {\n /* ignore cleanup failure */\n }\n throw err;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,gBAAgB,UAAkB,SAAuB;CACvE,GAAG,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;CACxD,MAAM,MAAM,GAAG,SAAS,GAAG,QAAQ,IAAI,GAAG,YAAY,CAAC,EAAE,SAAS,KAAK,EAAE;CACzE,IAAI;EACF,GAAG,cAAc,KAAK,SAAS,OAAO;EACtC,GAAG,WAAW,KAAK,QAAQ;CAC7B,SAAS,KAAK;EAEZ,IAAI;GACF,IAAI,GAAG,WAAW,GAAG,GAAG,GAAG,WAAW,GAAG;EAC3C,QAAQ,CAER;EACA,MAAM;CACR;AACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-DakpK96I.cjs");
|
|
2
|
+
let path = require("path");
|
|
3
|
+
path = require_chunk.__toESM(path, 1);
|
|
4
|
+
let fs = require("fs");
|
|
5
|
+
fs = require_chunk.__toESM(fs, 1);
|
|
6
|
+
let crypto = require("crypto");
|
|
7
|
+
//#region src/fs/atomic-write.ts
|
|
8
|
+
/**
|
|
9
|
+
* Atomically write `content` to `filePath`, creating parent directories as
|
|
10
|
+
* needed. The payload is written to a sibling temp file and then renamed over
|
|
11
|
+
* the target. rename(2) is atomic within a filesystem, so a crash or a
|
|
12
|
+
* concurrent reader can never observe a half-written (truncated/corrupt) file —
|
|
13
|
+
* only the complete old or complete new content. Used for every durable file
|
|
14
|
+
* the product owns (customer Markdown, JSON config/state, audit logs).
|
|
15
|
+
*/
|
|
16
|
+
function writeFileAtomic(filePath, content) {
|
|
17
|
+
fs.default.mkdirSync(path.default.dirname(filePath), { recursive: true });
|
|
18
|
+
const tmp = `${filePath}.${process.pid}.${(0, crypto.randomBytes)(4).toString("hex")}.tmp`;
|
|
19
|
+
try {
|
|
20
|
+
fs.default.writeFileSync(tmp, content, "utf-8");
|
|
21
|
+
fs.default.renameSync(tmp, filePath);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
try {
|
|
24
|
+
if (fs.default.existsSync(tmp)) fs.default.unlinkSync(tmp);
|
|
25
|
+
} catch {}
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
Object.defineProperty(exports, "writeFileAtomic", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function() {
|
|
33
|
+
return writeFileAtomic;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=atomic-write-BYmF-ThH.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write-BYmF-ThH.cjs","names":[],"sources":["../src/fs/atomic-write.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { randomBytes } from \"crypto\";\n\n/**\n * Atomically write `content` to `filePath`, creating parent directories as\n * needed. The payload is written to a sibling temp file and then renamed over\n * the target. rename(2) is atomic within a filesystem, so a crash or a\n * concurrent reader can never observe a half-written (truncated/corrupt) file —\n * only the complete old or complete new content. Used for every durable file\n * the product owns (customer Markdown, JSON config/state, audit logs).\n */\nexport function writeFileAtomic(filePath: string, content: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n const tmp = `${filePath}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n try {\n fs.writeFileSync(tmp, content, \"utf-8\");\n fs.renameSync(tmp, filePath);\n } catch (err) {\n // Best-effort cleanup of the temp file if the write/rename failed.\n try {\n if (fs.existsSync(tmp)) fs.unlinkSync(tmp);\n } catch {\n /* ignore cleanup failure */\n }\n throw err;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAYA,SAAgB,gBAAgB,UAAkB,SAAuB;CACvE,GAAA,QAAG,UAAU,KAAA,QAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;CACxD,MAAM,MAAM,GAAG,SAAS,GAAG,QAAQ,IAAI,IAAA,GAAA,OAAA,aAAe,CAAC,EAAE,SAAS,KAAK,EAAE;CACzE,IAAI;EACF,GAAA,QAAG,cAAc,KAAK,SAAS,OAAO;EACtC,GAAA,QAAG,WAAW,KAAK,QAAQ;CAC7B,SAAS,KAAK;EAEZ,IAAI;GACF,IAAI,GAAA,QAAG,WAAW,GAAG,GAAG,GAAA,QAAG,WAAW,GAAG;EAC3C,QAAQ,CAER;EACA,MAAM;CACR;AACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { r as writeJsonArray, t as readJsonArray } from "./json-store-WWsFzXub.js";
|
|
1
2
|
import path from "path";
|
|
2
|
-
import fs from "fs";
|
|
3
3
|
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
4
4
|
//#region src/mcp/auth.ts
|
|
5
5
|
function tokensPath(dataDir) {
|
|
@@ -10,14 +10,7 @@ function hashToken(token) {
|
|
|
10
10
|
return createHash("sha256").update(token).digest("hex");
|
|
11
11
|
}
|
|
12
12
|
function loadMcpTokens(dataDir) {
|
|
13
|
-
|
|
14
|
-
if (!fs.existsSync(p)) return [];
|
|
15
|
-
try {
|
|
16
|
-
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
17
|
-
return Array.isArray(data.tokens) ? data.tokens : [];
|
|
18
|
-
} catch {
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
13
|
+
return readJsonArray(tokensPath(dataDir), "tokens");
|
|
21
14
|
}
|
|
22
15
|
/**
|
|
23
16
|
* Whether the HTTP MCP endpoint must require a bearer token.
|
|
@@ -69,9 +62,7 @@ function createMcpToken(dataDir, actor, role, label) {
|
|
|
69
62
|
...label ? { label } : {},
|
|
70
63
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
71
64
|
});
|
|
72
|
-
|
|
73
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
74
|
-
fs.writeFileSync(p, JSON.stringify({ tokens: records }, null, 2), "utf-8");
|
|
65
|
+
writeJsonArray(tokensPath(dataDir), "tokens", records);
|
|
75
66
|
return token;
|
|
76
67
|
}
|
|
77
68
|
/** RFC 9728 OAuth 2.0 Protected Resource Metadata document. */
|
|
@@ -90,4 +81,4 @@ function wwwAuthenticateHeader(metadataUrl) {
|
|
|
90
81
|
//#endregion
|
|
91
82
|
export { protectedResourceMetadata as a, loadMcpTokens as i, hashToken as n, verifyBearer as o, isAuthRequired as r, wwwAuthenticateHeader as s, createMcpToken as t };
|
|
92
83
|
|
|
93
|
-
//# sourceMappingURL=auth-
|
|
84
|
+
//# sourceMappingURL=auth-DDXZTwS0.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-DDXZTwS0.js","names":[],"sources":["../src/mcp/auth.ts"],"sourcesContent":["import { createHash, randomBytes, timingSafeEqual } from \"crypto\";\nimport path from \"path\";\nimport { readJsonArray, writeJsonArray } from \"../fs/json-store.js\";\n\nexport type McpRole = \"admin\" | \"manager\" | \"rep\";\n\nexport interface McpTokenRecord {\n hash: string;\n actor: string;\n role: McpRole;\n label?: string;\n createdAt?: string;\n}\n\nexport interface AuthResult {\n ok: boolean;\n actor?: string;\n role?: McpRole;\n}\n\nfunction tokensPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"mcp-tokens.json\");\n}\n\n/** SHA-256 hex of a token. Only hashes are ever persisted. */\nexport function hashToken(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"hex\");\n}\n\nexport function loadMcpTokens(dataDir: string): McpTokenRecord[] {\n return readJsonArray<McpTokenRecord>(tokensPath(dataDir), \"tokens\");\n}\n\n/**\n * Whether the HTTP MCP endpoint must require a bearer token.\n * - `DXCRM_MCP_AUTH=required` forces auth on (even with no tokens yet).\n * - `DXCRM_MCP_AUTH=off` forces it off.\n * - Otherwise: on as soon as at least one token is configured (opt-in by\n * provisioning a token; stays open for local/firewalled dev by default).\n */\nexport function isAuthRequired(dataDir: string): boolean {\n const mode = process.env[\"DXCRM_MCP_AUTH\"];\n if (mode === \"required\") return true;\n if (mode === \"off\") return false;\n return loadMcpTokens(dataDir).length > 0;\n}\n\n/** Validate an `Authorization: Bearer <token>` header against stored hashes. */\nexport function verifyBearer(authHeader: string | undefined, dataDir: string): AuthResult {\n if (!authHeader || !authHeader.startsWith(\"Bearer \")) return { ok: false };\n const token = authHeader.slice(\"Bearer \".length).trim();\n if (!token) return { ok: false };\n\n const candidate = hashToken(token);\n const candidateBuf = Buffer.from(candidate, \"hex\");\n for (const rec of loadMcpTokens(dataDir)) {\n if (rec.hash.length !== candidate.length) continue;\n let recBuf: Buffer;\n try {\n recBuf = Buffer.from(rec.hash, \"hex\");\n } catch {\n continue;\n }\n if (recBuf.length === candidateBuf.length && timingSafeEqual(recBuf, candidateBuf)) {\n return { ok: true, actor: rec.actor, role: rec.role };\n }\n }\n return { ok: false };\n}\n\n/**\n * Mint a new token: generates a random secret, persists only its hash mapped\n * to an actor/role, and returns the plaintext ONCE (never stored).\n */\nexport function createMcpToken(\n dataDir: string,\n actor: string,\n role: McpRole,\n label?: string\n): string {\n const token = randomBytes(24).toString(\"base64url\");\n const records = loadMcpTokens(dataDir);\n records.push({\n hash: hashToken(token),\n actor,\n role,\n ...(label ? { label } : {}),\n createdAt: new Date().toISOString(),\n });\n writeJsonArray(tokensPath(dataDir), \"tokens\", records);\n return token;\n}\n\n/** RFC 9728 OAuth 2.0 Protected Resource Metadata document. */\nexport function protectedResourceMetadata(resourceUrl: string): Record<string, unknown> {\n return {\n resource: resourceUrl,\n // Self-hosted default: tokens are provisioned out-of-band (createMcpToken).\n // Populate with an external Authorization Server to enable full OAuth flows.\n authorization_servers: [] as string[],\n bearer_methods_supported: [\"header\"],\n scopes_supported: [\"crm:read\", \"crm:write\"],\n };\n}\n\n/** Value for the `WWW-Authenticate` header on a 401 (RFC 9728 §5.1). */\nexport function wwwAuthenticateHeader(metadataUrl: string): string {\n return `Bearer resource_metadata=\"${metadataUrl}\"`;\n}\n"],"mappings":";;;;AAoBA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,iBAAiB;AACzD;;AAGA,SAAgB,UAAU,OAAuB;CAC/C,OAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AACxD;AAEA,SAAgB,cAAc,SAAmC;CAC/D,OAAO,cAA8B,WAAW,OAAO,GAAG,QAAQ;AACpE;;;;;;;;AASA,SAAgB,eAAe,SAA0B;CACvD,MAAM,OAAO,QAAQ,IAAI;CACzB,IAAI,SAAS,YAAY,OAAO;CAChC,IAAI,SAAS,OAAO,OAAO;CAC3B,OAAO,cAAc,OAAO,EAAE,SAAS;AACzC;;AAGA,SAAgB,aAAa,YAAgC,SAA6B;CACxF,IAAI,CAAC,cAAc,CAAC,WAAW,WAAW,SAAS,GAAG,OAAO,EAAE,IAAI,MAAM;CACzE,MAAM,QAAQ,WAAW,MAAM,CAAgB,EAAE,KAAK;CACtD,IAAI,CAAC,OAAO,OAAO,EAAE,IAAI,MAAM;CAE/B,MAAM,YAAY,UAAU,KAAK;CACjC,MAAM,eAAe,OAAO,KAAK,WAAW,KAAK;CACjD,KAAK,MAAM,OAAO,cAAc,OAAO,GAAG;EACxC,IAAI,IAAI,KAAK,WAAW,UAAU,QAAQ;EAC1C,IAAI;EACJ,IAAI;GACF,SAAS,OAAO,KAAK,IAAI,MAAM,KAAK;EACtC,QAAQ;GACN;EACF;EACA,IAAI,OAAO,WAAW,aAAa,UAAU,gBAAgB,QAAQ,YAAY,GAC/E,OAAO;GAAE,IAAI;GAAM,OAAO,IAAI;GAAO,MAAM,IAAI;EAAK;CAExD;CACA,OAAO,EAAE,IAAI,MAAM;AACrB;;;;;AAMA,SAAgB,eACd,SACA,OACA,MACA,OACQ;CACR,MAAM,QAAQ,YAAY,EAAE,EAAE,SAAS,WAAW;CAClD,MAAM,UAAU,cAAc,OAAO;CACrC,QAAQ,KAAK;EACX,MAAM,UAAU,KAAK;EACrB;EACA;EACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,4BAAW,IAAI,KAAK,GAAE,YAAY;CACpC,CAAC;CACD,eAAe,WAAW,OAAO,GAAG,UAAU,OAAO;CACrD,OAAO;AACT;;AAGA,SAAgB,0BAA0B,aAA8C;CACtF,OAAO;EACL,UAAU;EAGV,uBAAuB,CAAC;EACxB,0BAA0B,CAAC,QAAQ;EACnC,kBAAkB,CAAC,YAAY,WAAW;CAC5C;AACF;;AAGA,SAAgB,sBAAsB,aAA6B;CACjE,OAAO,6BAA6B,YAAY;AAClD"}
|
|
@@ -26,7 +26,7 @@ function heuristicExtract(transcript) {
|
|
|
26
26
|
}
|
|
27
27
|
async function extractAutofill(transcript, ctx) {
|
|
28
28
|
try {
|
|
29
|
-
const { callLlm } = await import("./llm-
|
|
29
|
+
const { callLlm } = await import("./llm-DSX1-wFu.js");
|
|
30
30
|
const raw = await callLlm(`Extract CRM fields from this meeting transcript. Return ONLY JSON: { "summary": string, "nextSteps": string[], "objections": string[], "stage": "lead"|"qualified"|"proposal"|"negotiation"|"won"|"lost"|null }.\n\n${transcript}`, {
|
|
31
31
|
tool: "autofill",
|
|
32
32
|
...ctx?.slug ? { slug: ctx.slug } : {}
|
|
@@ -48,4 +48,4 @@ async function extractAutofill(transcript, ctx) {
|
|
|
48
48
|
//#endregion
|
|
49
49
|
export { extractAutofill };
|
|
50
50
|
|
|
51
|
-
//# sourceMappingURL=autofill-
|
|
51
|
+
//# sourceMappingURL=autofill-B9VtlR2j.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autofill-
|
|
1
|
+
{"version":3,"file":"autofill-B9VtlR2j.js","names":[],"sources":["../src/core/autofill.ts"],"sourcesContent":["/**\n * Call/Meeting → CRM autofill (domino D9 / C1): extract structured fields from a\n * transcript so they can be written to the CRM (through the D4 approval gate).\n * Uses the LLM when available, with a deterministic heuristic fallback so it\n * always returns something useful and is testable offline.\n */\nexport type StageName = \"lead\" | \"qualified\" | \"proposal\" | \"negotiation\" | \"won\" | \"lost\";\n\nexport interface AutofillResult {\n summary: string;\n nextSteps: string[];\n objections: string[];\n stage?: StageName;\n}\n\nconst NEXT_STEP_RE = /^(?:-?\\s*\\[ \\]|next step|todo|follow.?up|action item)\\b[:\\-\\s]*/i;\nconst OBJECTION_RE = /\\b(concern|worried|too expensive|expensive|hesitant|however|push back)\\b/i;\n\nfunction detectStage(text: string): StageName | undefined {\n const t = text.toLowerCase();\n if (t.includes(\"closed won\") || t.includes(\"signed\")) return \"won\";\n if (t.includes(\"closed lost\") || t.includes(\"lost the deal\")) return \"lost\";\n if (t.includes(\"negotiat\")) return \"negotiation\";\n if (t.includes(\"proposal\") || t.includes(\"quote\")) return \"proposal\";\n if (t.includes(\"qualif\")) return \"qualified\";\n return undefined;\n}\n\nexport function heuristicExtract(transcript: string): AutofillResult {\n const lines = transcript\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n\n const nextSteps = lines\n .filter((l) => NEXT_STEP_RE.test(l))\n .map((l) => l.replace(NEXT_STEP_RE, \"\").trim())\n .filter(Boolean)\n .slice(0, 10);\n\n const objections = lines.filter((l) => OBJECTION_RE.test(l)).slice(0, 10);\n\n const base = { summary: transcript.slice(0, 400), nextSteps, objections };\n const stage = detectStage(transcript);\n return stage === undefined ? base : { ...base, stage };\n}\n\nexport async function extractAutofill(\n transcript: string,\n ctx?: { slug?: string }\n): Promise<AutofillResult> {\n try {\n const { callLlm } = await import(\"./llm.js\");\n const prompt =\n `Extract CRM fields from this meeting transcript. Return ONLY JSON: ` +\n `{ \"summary\": string, \"nextSteps\": string[], \"objections\": string[], ` +\n `\"stage\": \"lead\"|\"qualified\"|\"proposal\"|\"negotiation\"|\"won\"|\"lost\"|null }.\\n\\n${transcript}`;\n const raw = await callLlm(prompt, {\n tool: \"autofill\",\n ...(ctx?.slug ? { slug: ctx.slug } : {}),\n });\n const parsed = JSON.parse(raw) as Partial<AutofillResult> & { stage?: string | null };\n const base = {\n summary: parsed.summary ?? transcript.slice(0, 400),\n nextSteps: Array.isArray(parsed.nextSteps) ? parsed.nextSteps : [],\n objections: Array.isArray(parsed.objections) ? parsed.objections : [],\n };\n return parsed.stage ? { ...base, stage: parsed.stage as StageName } : base;\n } catch {\n return heuristicExtract(transcript);\n }\n}\n"],"mappings":";AAeA,MAAM,eAAe;AACrB,MAAM,eAAe;AAErB,SAAS,YAAY,MAAqC;CACxD,MAAM,IAAI,KAAK,YAAY;CAC3B,IAAI,EAAE,SAAS,YAAY,KAAK,EAAE,SAAS,QAAQ,GAAG,OAAO;CAC7D,IAAI,EAAE,SAAS,aAAa,KAAK,EAAE,SAAS,eAAe,GAAG,OAAO;CACrE,IAAI,EAAE,SAAS,UAAU,GAAG,OAAO;CACnC,IAAI,EAAE,SAAS,UAAU,KAAK,EAAE,SAAS,OAAO,GAAG,OAAO;CAC1D,IAAI,EAAE,SAAS,QAAQ,GAAG,OAAO;AAEnC;AAEA,SAAgB,iBAAiB,YAAoC;CACnE,MAAM,QAAQ,WACX,MAAM,IAAI,EACV,KAAK,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;CAEjB,MAAM,YAAY,MACf,QAAQ,MAAM,aAAa,KAAK,CAAC,CAAC,EAClC,KAAK,MAAM,EAAE,QAAQ,cAAc,EAAE,EAAE,KAAK,CAAC,EAC7C,OAAO,OAAO,EACd,MAAM,GAAG,EAAE;CAEd,MAAM,aAAa,MAAM,QAAQ,MAAM,aAAa,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,EAAE;CAExE,MAAM,OAAO;EAAE,SAAS,WAAW,MAAM,GAAG,GAAG;EAAG;EAAW;CAAW;CACxE,MAAM,QAAQ,YAAY,UAAU;CACpC,OAAO,UAAU,KAAA,IAAY,OAAO;EAAE,GAAG;EAAM;CAAM;AACvD;AAEA,eAAsB,gBACpB,YACA,KACyB;CACzB,IAAI;EACF,MAAM,EAAE,YAAY,MAAM,OAAO;EAKjC,MAAM,MAAM,MAAM,QAAQ,uNADwD,cAChD;GAChC,MAAM;GACN,GAAI,KAAK,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;EACxC,CAAC;EACD,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,MAAM,OAAO;GACX,SAAS,OAAO,WAAW,WAAW,MAAM,GAAG,GAAG;GAClD,WAAW,MAAM,QAAQ,OAAO,SAAS,IAAI,OAAO,YAAY,CAAC;GACjE,YAAY,MAAM,QAAQ,OAAO,UAAU,IAAI,OAAO,aAAa,CAAC;EACtE;EACA,OAAO,OAAO,QAAQ;GAAE,GAAG;GAAM,OAAO,OAAO;EAAmB,IAAI;CACxE,QAAQ;EACN,OAAO,iBAAiB,UAAU;CACpC;AACF"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { i as writeJsonFile } from "./json-store-WWsFzXub.js";
|
|
1
2
|
import { i as success, n as error, r as info, t as bold } from "./colors-BG07TZQz.js";
|
|
2
3
|
import { Command } from "commander";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import fs from "fs";
|
|
5
|
-
import { execSync } from "child_process";
|
|
6
6
|
import { createHash } from "crypto";
|
|
7
|
+
import { execSync } from "child_process";
|
|
7
8
|
//#region src/commands/backup.ts
|
|
8
9
|
function getConfigPath(dataDir) {
|
|
9
10
|
return path.join(dataDir, ".agentic", "config.json");
|
|
@@ -18,9 +19,7 @@ function readAgenticConfig(dataDir) {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
function writeAgenticConfig(dataDir, config) {
|
|
21
|
-
|
|
22
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
23
|
-
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
|
|
22
|
+
writeJsonFile(getConfigPath(dataDir), config);
|
|
24
23
|
}
|
|
25
24
|
function countDir(dir) {
|
|
26
25
|
let files = 0;
|
|
@@ -102,8 +101,7 @@ function appendBackupLog(dataDir, entry) {
|
|
|
102
101
|
entries = entries.filter((e) => e.filename !== entry.filename);
|
|
103
102
|
entries.unshift(entry);
|
|
104
103
|
if (entries.length > 100) entries = entries.slice(0, 100);
|
|
105
|
-
|
|
106
|
-
fs.writeFileSync(logPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
104
|
+
writeJsonFile(logPath, entries);
|
|
107
105
|
}
|
|
108
106
|
function readBackupLog(dataDir) {
|
|
109
107
|
const logPath = path.join(dataDir, ".agentic", "backup-log.json");
|
|
@@ -414,4 +412,4 @@ const restoreCommand = new Command("restore").argument("<path>", "Path to backup
|
|
|
414
412
|
//#endregion
|
|
415
413
|
export { restoreCommand as a, runRestore as c, runVerify as d, shouldRunScheduledBackup as f, readBackupLog as i, runRestoreDrill as l, verifyBackupFile as m, listBackupsInDir as n, runBackup as o, uploadBackup as p, pruneOldBackups as r, runBackupSchedule as s, backupCommand as t, runScheduledBackupIfDue as u };
|
|
416
414
|
|
|
417
|
-
//# sourceMappingURL=backup-
|
|
415
|
+
//# sourceMappingURL=backup-CTlIxUdO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-CTlIxUdO.js","names":[],"sources":["../src/commands/backup.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport { success, error, info, bold } from \"../ui/colors.js\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\n\nexport interface BackupManifest {\n version: \"1\";\n createdAt: string;\n dxcrmVersion: string;\n directories: string[];\n customerCount: number;\n fileCount: number;\n totalBytes: number;\n sha256: string;\n encrypted: boolean;\n retentionTier?: \"daily\" | \"weekly\" | \"monthly\";\n}\n\nexport interface BackupScheduleConfig {\n every: string;\n keep: number;\n weekly?: number;\n monthly?: number;\n lastBackup: string | null;\n remote?: string;\n}\n\nexport interface AgenticConfig {\n backupSchedule?: BackupScheduleConfig;\n}\n\nexport interface BackupEntry {\n filename: string;\n path: string;\n createdAt: string;\n sizeBytes: number;\n verified: boolean;\n encrypted: boolean;\n customerCount: number;\n fileCount: number;\n}\n\n// ─── Config helpers ────────────────────────────────────────────────────────────\n\nfunction getConfigPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"config.json\");\n}\n\nfunction readAgenticConfig(dataDir: string): AgenticConfig {\n const filePath = getConfigPath(dataDir);\n if (!fs.existsSync(filePath)) return {};\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\") as string) as AgenticConfig;\n } catch {\n return {};\n }\n}\n\nfunction writeAgenticConfig(dataDir: string, config: AgenticConfig): void {\n writeJsonFile(getConfigPath(dataDir), config);\n}\n\n// ─── Manifest ─────────────────────────────────────────────────────────────────\n\nfunction countDir(dir: string): { files: number; bytes: number } {\n let files = 0;\n let bytes = 0;\n if (!fs.existsSync(dir)) return { files, bytes };\n const walk = (d: string) => {\n try {\n for (const entry of fs.readdirSync(d)) {\n const full = path.join(d, entry);\n try {\n const stat = fs.statSync(full);\n if (stat.isDirectory()) walk(full);\n else {\n files++;\n bytes += stat.size;\n }\n } catch {\n /* skip */\n }\n }\n } catch {\n /* skip */\n }\n };\n walk(dir);\n return { files, bytes };\n}\n\nfunction countCustomers(dataDir: string): number {\n const dir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(dir)) return 0;\n try {\n return fs.readdirSync(dir).filter((f) => {\n try {\n return fs.statSync(path.join(dir, f)).isDirectory();\n } catch {\n return false;\n }\n }).length;\n } catch {\n return 0;\n }\n}\n\nfunction sha256File(filePath: string): string {\n if (!fs.existsSync(filePath)) return \"\";\n const hash = createHash(\"sha256\");\n hash.update(fs.readFileSync(filePath));\n return hash.digest(\"hex\");\n}\n\nfunction buildManifest(\n dataDir: string,\n dirs: string[],\n zipPath: string,\n encrypted: boolean\n): BackupManifest {\n let totalFiles = 0;\n let totalBytes = 0;\n for (const d of dirs) {\n const full = path.join(dataDir, d);\n const { files, bytes } = countDir(full);\n totalFiles += files;\n totalBytes += bytes;\n }\n return {\n version: \"1\",\n createdAt: new Date().toISOString(),\n dxcrmVersion: \"0.1.0\",\n directories: dirs,\n customerCount: countCustomers(dataDir),\n fileCount: totalFiles,\n totalBytes,\n sha256: sha256File(zipPath),\n encrypted,\n };\n}\n\n// ─── Manifest log ──────────────────────────────────────────────────────────────\n\nfunction appendBackupLog(dataDir: string, entry: BackupEntry): void {\n const logPath = path.join(dataDir, \".agentic\", \"backup-log.json\");\n let entries: BackupEntry[] = [];\n if (fs.existsSync(logPath)) {\n try {\n entries = JSON.parse(fs.readFileSync(logPath, \"utf-8\") as string) as BackupEntry[];\n } catch {\n entries = [];\n }\n }\n // Deduplicate by filename — update existing entry if same file backed up again\n entries = entries.filter((e) => e.filename !== entry.filename);\n entries.unshift(entry);\n // Keep last 100 entries\n if (entries.length > 100) entries = entries.slice(0, 100);\n writeJsonFile(logPath, entries);\n}\n\nexport function readBackupLog(dataDir: string): BackupEntry[] {\n const logPath = path.join(dataDir, \".agentic\", \"backup-log.json\");\n if (!fs.existsSync(logPath)) return [];\n try {\n return JSON.parse(fs.readFileSync(logPath, \"utf-8\") as string) as BackupEntry[];\n } catch {\n return [];\n }\n}\n\n// ─── runBackup ────────────────────────────────────────────────────────────────\n\nexport async function runBackup(\n output?: string,\n dataDir?: string,\n opts: { encrypt?: boolean; remote?: string } = {}\n): Promise<BackupManifest | null> {\n const dir = dataDir ?? process.cwd();\n const customersDir = path.join(dir, \"customers\");\n\n if (!fs.existsSync(customersDir)) {\n console.error(error(\"✗ No customers directory found.\"));\n process.exit(1);\n }\n\n const zipPath =\n output ?? path.join(dir, `dxcrm-backup-${new Date().toISOString().slice(0, 10)}.zip`);\n\n // Determine which directories to include\n const includeDirs = [\"customers/\"];\n if (fs.existsSync(path.join(dir, \".agentic\"))) {\n includeDirs.push(\".agentic/\");\n }\n\n try {\n execSync(`zip -r \"${zipPath}\" ${includeDirs.join(\" \")}`, { cwd: dir });\n\n // Build manifest and append to zip\n const manifest = buildManifest(dir, includeDirs, zipPath, opts.encrypt ?? false);\n const manifestPath = path.join(dir, \".dxcrm-manifest-tmp.json\");\n fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), \"utf-8\");\n try {\n execSync(`zip -j \"${zipPath}\" \"${manifestPath}\"`, { cwd: dir });\n } catch {\n /* non-fatal */\n }\n fs.unlinkSync(manifestPath);\n\n // Verify integrity\n const verified = verifyBackupFile(zipPath);\n\n const entry: BackupEntry = {\n filename: path.basename(zipPath),\n path: zipPath,\n createdAt: manifest.createdAt,\n sizeBytes: fs.existsSync(zipPath) ? fs.statSync(zipPath).size : 0,\n verified,\n encrypted: opts.encrypt ?? false,\n customerCount: manifest.customerCount,\n fileCount: manifest.fileCount,\n };\n appendBackupLog(dir, entry);\n\n // Remote upload\n if (opts.remote) {\n await uploadBackup(zipPath, opts.remote);\n }\n\n console.log(success(`✓ Backup saved: ${zipPath}`));\n console.log(\n info(\n ` Customers: ${manifest.customerCount} Files: ${manifest.fileCount} Size: ${(manifest.totalBytes / 1024 / 1024).toFixed(1)} MB`\n )\n );\n if (!verified) console.log(info(\" ⚠ Integrity check failed — backup may be incomplete\"));\n\n return manifest;\n } catch (err) {\n console.error(error(`✗ Backup failed: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\n// ─── Verify ───────────────────────────────────────────────────────────────────\n\nexport function verifyBackupFile(zipPath: string): boolean {\n if (!fs.existsSync(zipPath)) return false;\n try {\n execSync(`unzip -t \"${zipPath}\"`, { stdio: \"pipe\" });\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function runVerify(zipPath: string): Promise<void> {\n if (!fs.existsSync(zipPath)) {\n console.error(error(`✗ File not found: ${zipPath}`));\n process.exit(1);\n }\n\n console.log(info(`Verifying ${path.basename(zipPath)}...`));\n const ok = verifyBackupFile(zipPath);\n\n if (ok) {\n const size = fs.statSync(zipPath).size;\n const sha = sha256File(zipPath);\n console.log(success(\"✓ ZIP integrity OK\"));\n console.log(info(` Size: ${(size / 1024 / 1024).toFixed(1)} MB`));\n console.log(info(` SHA-256: ${sha}`));\n } else {\n console.error(error(\"✗ Integrity check failed\"));\n process.exit(1);\n }\n}\n\n// ─── Remote Upload ────────────────────────────────────────────────────────────\n\nexport async function uploadBackup(localPath: string, remote: string): Promise<void> {\n if (remote.startsWith(\"s3://\")) {\n // Requires AWS CLI or @aws-sdk/client-s3 to be installed\n try {\n execSync(`aws s3 cp \"${localPath}\" \"${remote}${path.basename(localPath)}\"`, {\n stdio: \"pipe\",\n });\n console.log(info(` ✓ Uploaded to ${remote}${path.basename(localPath)}`));\n } catch (err) {\n console.error(\n error(\n ` ✗ S3 upload failed (install aws-cli or @aws-sdk/client-s3): ${(err as Error).message}`\n )\n );\n }\n } else if (remote.startsWith(\"rsync://\")) {\n const dest = remote.replace(\"rsync://\", \"\");\n try {\n execSync(`rsync -az \"${localPath}\" \"${dest}\"`, { stdio: \"pipe\" });\n console.log(info(` ✓ Synced to ${dest}`));\n } catch (err) {\n console.error(error(` ✗ rsync failed: ${(err as Error).message}`));\n }\n } else {\n // Local directory copy\n try {\n const destPath = path.join(remote, path.basename(localPath));\n fs.mkdirSync(remote, { recursive: true });\n fs.copyFileSync(localPath, destPath);\n console.log(info(` ✓ Copied to ${destPath}`));\n } catch (err) {\n console.error(error(` ✗ Copy failed: ${(err as Error).message}`));\n }\n }\n}\n\n// ─── List Backups ─────────────────────────────────────────────────────────────\n\nexport function listBackupsInDir(dir: string): BackupEntry[] {\n if (!fs.existsSync(dir)) return [];\n try {\n return fs\n .readdirSync(dir)\n .filter((f) => f.match(/^dxcrm-backup-.*\\.(zip|dxbak)$/))\n .map((f) => {\n const fullPath = path.join(dir, f);\n const stat = fs.statSync(fullPath);\n return {\n filename: f,\n path: fullPath,\n createdAt: stat.mtime.toISOString(),\n sizeBytes: stat.size,\n verified: false,\n encrypted: f.endsWith(\".dxbak\"),\n customerCount: 0,\n fileCount: 0,\n } satisfies BackupEntry;\n })\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n } catch {\n return [];\n }\n}\n\n// ─── Retention Policy ─────────────────────────────────────────────────────────\n\nexport interface RetentionConfig {\n daily?: number;\n weekly?: number;\n monthly?: number;\n}\n\nexport function pruneOldBackups(dir: string, keep: number, retention?: RetentionConfig): void {\n const files = fs\n .readdirSync(dir)\n .filter((f) => f.match(/^dxcrm-backup-\\d{4}-\\d{2}-\\d{2}.*\\.(zip|dxbak)$/))\n .sort();\n\n if (!retention) {\n // Legacy: keep last N\n const toDelete = files.slice(0, Math.max(0, files.length - keep));\n for (const f of toDelete) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n return;\n }\n\n // Grandfathering: daily → weekly → monthly\n const daily = retention.daily ?? keep;\n const weekly = retention.weekly ?? 0;\n const monthly = retention.monthly ?? 0;\n\n const kept = new Set<string>();\n\n // Keep last N daily\n for (const f of files.slice(-daily)) kept.add(f);\n\n // Keep last backup of each week (up to 'weekly' weeks)\n if (weekly > 0) {\n const byWeek = new Map<string, string>();\n for (const f of files) {\n const dateMatch = f.match(/dxcrm-backup-(\\d{4}-\\d{2}-\\d{2})/);\n if (!dateMatch?.[1]) continue;\n const d = new Date(dateMatch[1]);\n // ISO week: year + week number\n const week = `${d.getFullYear()}-W${String(Math.ceil((d.getDate() + new Date(d.getFullYear(), 0, 1).getDay()) / 7)).padStart(2, \"0\")}`;\n byWeek.set(week, f); // last backup of the week wins\n }\n Array.from(byWeek.values())\n .slice(-weekly)\n .forEach((f) => kept.add(f));\n }\n\n // Keep last backup of each month (up to 'monthly' months)\n if (monthly > 0) {\n const byMonth = new Map<string, string>();\n for (const f of files) {\n const dateMatch = f.match(/dxcrm-backup-(\\d{4}-\\d{2})/);\n if (!dateMatch?.[1]) continue;\n byMonth.set(dateMatch[1], f); // last backup of the month wins\n }\n Array.from(byMonth.values())\n .slice(-monthly)\n .forEach((f) => kept.add(f));\n }\n\n for (const f of files) {\n if (!kept.has(f)) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n }\n}\n\n// ─── Schedule ─────────────────────────────────────────────────────────────────\n\nexport async function runBackupSchedule(\n opts: {\n every?: string;\n keep?: string;\n weekly?: string;\n monthly?: string;\n remote?: string;\n status?: boolean;\n clear?: boolean;\n },\n dataDir?: string\n): Promise<void> {\n const dir = dataDir ?? process.cwd();\n\n if (opts.clear) {\n const config = readAgenticConfig(dir);\n delete config.backupSchedule;\n writeAgenticConfig(dir, config);\n console.log(success(\"✓ Backup schedule cleared.\"));\n return;\n }\n\n if (!opts.every && !opts.status) {\n console.error(error(\"✗ --every is required (e.g. --every day)\"));\n process.exit(1);\n return;\n }\n\n if (opts.every) {\n const keep = opts.keep ? parseInt(opts.keep, 10) : 7;\n const config = readAgenticConfig(dir);\n config.backupSchedule = {\n every: opts.every,\n keep,\n ...(opts.weekly ? { weekly: parseInt(opts.weekly, 10) } : {}),\n ...(opts.monthly ? { monthly: parseInt(opts.monthly, 10) } : {}),\n ...(opts.remote ? { remote: opts.remote } : {}),\n lastBackup: null,\n };\n writeAgenticConfig(dir, config);\n if (!opts.status) {\n console.log(\n success(\n `✓ Backup schedule set: every ${opts.every}, keep ${keep} daily${opts.weekly ? ` / ${opts.weekly} weekly` : \"\"}${opts.monthly ? ` / ${opts.monthly} monthly` : \"\"}.`\n )\n );\n }\n }\n\n if (opts.status) {\n const config = readAgenticConfig(dir);\n const sched = config.backupSchedule;\n if (!sched) {\n console.log(info(\"No backup schedule configured.\"));\n } else {\n console.log(bold(\"Backup Schedule:\"));\n console.log(` every: ${sched.every}`);\n console.log(` keep: ${sched.keep} daily backups`);\n if (sched.weekly) console.log(` weekly: ${sched.weekly} weekly backups`);\n if (sched.monthly) console.log(` monthly: ${sched.monthly} monthly backups`);\n if (sched.remote) console.log(` remote: ${sched.remote}`);\n console.log(` lastBackup: ${sched.lastBackup ?? \"never\"}`);\n }\n }\n}\n\nexport function shouldRunScheduledBackup(dataDir: string): boolean {\n const config = readAgenticConfig(dataDir);\n const sched = config.backupSchedule;\n if (!sched) return false;\n if (!sched.lastBackup) return true;\n const last = new Date(sched.lastBackup).getTime();\n const oneDayMs = 24 * 60 * 60 * 1000;\n return Date.now() - last >= oneDayMs;\n}\n\nexport async function runScheduledBackupIfDue(dataDir: string): Promise<void> {\n if (!shouldRunScheduledBackup(dataDir)) return;\n const config = readAgenticConfig(dataDir);\n const sched = config.backupSchedule!;\n const customersDir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(customersDir)) return;\n\n const zipPath = path.join(dataDir, `dxcrm-backup-${new Date().toISOString().slice(0, 10)}.zip`);\n const includeDirs = [\"customers/\"];\n if (fs.existsSync(path.join(dataDir, \".agentic\"))) includeDirs.push(\".agentic/\");\n\n try {\n execSync(`zip -r \"${zipPath}\" ${includeDirs.join(\" \")}`, { cwd: dataDir });\n\n const retention: RetentionConfig | undefined =\n (sched.weekly ?? sched.monthly)\n ? {\n daily: sched.keep,\n ...(sched.weekly ? { weekly: sched.weekly } : {}),\n ...(sched.monthly ? { monthly: sched.monthly } : {}),\n }\n : undefined;\n pruneOldBackups(dataDir, sched.keep, retention);\n\n if (sched.remote) {\n await uploadBackup(zipPath, sched.remote).catch(() => {\n /* non-fatal */\n });\n }\n\n config.backupSchedule!.lastBackup = new Date().toISOString();\n writeAgenticConfig(dataDir, config);\n process.stderr.write(`[daemon] Scheduled backup saved: ${zipPath}\\n`);\n } catch (err) {\n process.stderr.write(`[daemon] Scheduled backup failed: ${(err as Error).message}\\n`);\n }\n}\n\n// ─── Restore ──────────────────────────────────────────────────────────────────\n\nexport async function runRestore(zipPath: string, dataDir?: string): Promise<void> {\n const dir = dataDir ?? process.cwd();\n try {\n execSync(`unzip -o \"${path.resolve(zipPath)}\" -d \"${dir}\"`, { cwd: dir });\n console.log(success(\"✓ Restore complete.\"));\n } catch (err) {\n console.error(error(`✗ Restore failed: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\nexport interface RestoreDrillReport {\n ok: boolean;\n verified: boolean;\n hasCustomers: boolean;\n hasAgentic: boolean;\n reason?: string;\n}\n\n/**\n * Restore-drill: verify a backup is actually restorable WITHOUT touching live\n * data — checks integrity (unzip -t) and that the archive contains the expected\n * top-level state (customers/, .agentic/). Returns a report for monitoring.\n */\nexport async function runRestoreDrill(\n zipPath: string,\n opts: { silent?: boolean } = {}\n): Promise<RestoreDrillReport> {\n const resolved = path.resolve(zipPath);\n if (!fs.existsSync(resolved)) {\n if (!opts.silent) console.error(error(`✗ File not found: ${zipPath}`));\n return {\n ok: false,\n verified: false,\n hasCustomers: false,\n hasAgentic: false,\n reason: \"not_found\",\n };\n }\n\n const verified = verifyBackupFile(resolved);\n let hasCustomers = false;\n let hasAgentic = false;\n if (verified) {\n try {\n const listing = execSync(`unzip -l \"${resolved}\"`, { stdio: \"pipe\" }).toString();\n hasCustomers = listing.includes(\"customers/\");\n hasAgentic = listing.includes(\".agentic/\");\n } catch {\n /* listing failed — treated as incomplete */\n }\n }\n\n const ok = verified && hasCustomers;\n if (!opts.silent) {\n if (ok) {\n console.log(\n success(\n `✓ Restore drill OK — integrity verified; customers/${hasAgentic ? \" + .agentic/\" : \"\"} present`\n )\n );\n } else {\n console.error(\n error(`✗ Restore drill failed (verified=${verified}, customers=${hasCustomers})`)\n );\n }\n }\n return { ok, verified, hasCustomers, hasAgentic };\n}\n\n// ─── Commands ─────────────────────────────────────────────────────────────────\n\nconst scheduleSubCommand = new Command(\"schedule\")\n .description(\"Configure automatic backup schedule\")\n .option(\"--every <interval>\", \"Backup interval (e.g. day)\")\n .option(\"--keep <n>\", \"Daily backups to keep (default: 7)\")\n .option(\"--weekly <n>\", \"Weekly backups to keep (e.g. 4)\")\n .option(\"--monthly <n>\", \"Monthly backups to keep (e.g. 12)\")\n .option(\"--remote <url>\", \"Remote destination (s3://, rsync://, or local path)\")\n .option(\"--status\", \"Show current schedule\")\n .option(\"--clear\", \"Remove backup schedule\")\n .action((opts) => runBackupSchedule(opts, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd()));\n\nconst verifySubCommand = new Command(\"verify\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Verify backup integrity (SHA-256 + zip test)\")\n .action((zipPath: string) => runVerify(zipPath));\n\nconst drillSubCommand = new Command(\"drill\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Restore-drill: verify a backup is restorable without touching live data\")\n .action(async (zipPath: string) => {\n const report = await runRestoreDrill(zipPath);\n if (!report.ok) process.exitCode = 1;\n });\n\nconst listSubCommand = new Command(\"list\").description(\"List available backups\").action(() => {\n const dir = process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd();\n const entries = readBackupLog(dir);\n const fileEntries = listBackupsInDir(dir);\n const combined = entries.length > 0 ? entries : fileEntries;\n if (combined.length === 0) {\n console.log(info(\"No backups found.\"));\n return;\n }\n for (const e of combined) {\n const enc = e.encrypted ? \" [encrypted]\" : \"\";\n const ver = e.verified ? \" ✓\" : \"\";\n const mb = e.sizeBytes > 0 ? ` ${(e.sizeBytes / 1024 / 1024).toFixed(1)} MB` : \"\";\n console.log(` ${bold(e.filename)}${enc}${ver}${mb} ${e.createdAt.slice(0, 10)}`);\n }\n});\n\nexport const backupCommand = new Command(\"backup\")\n .argument(\"[output]\", \"Output path for backup zip\")\n .description(\"Backup customers/ + .agentic/ directories\")\n .option(\"--encrypt\", \"Encrypt the backup (AES-256-GCM)\")\n .option(\"--remote <url>\", \"Also upload to remote (s3://, rsync://, or path)\")\n .action((output?: string, opts?: { encrypt?: boolean; remote?: string }) => {\n void runBackup(output, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd(), opts ?? {});\n });\n\nbackupCommand.addCommand(scheduleSubCommand);\nbackupCommand.addCommand(verifySubCommand);\nbackupCommand.addCommand(drillSubCommand);\nbackupCommand.addCommand(listSubCommand);\n\nexport const restoreCommand = new Command(\"restore\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Restore from backup zip\")\n .action((zipPath: string) => runRestore(zipPath, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd()));\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as computeCustomerHealth } from "./relationship-health-
|
|
1
|
+
import { i as listCustomerSlugs } from "./customer-dir-CkMMXhb0.js";
|
|
2
|
+
import { t as computeCustomerHealth } from "./relationship-health-ZZNXR1RZ.js";
|
|
3
3
|
//#region src/core/churn.ts
|
|
4
4
|
function levelFromScore(score) {
|
|
5
5
|
if (score >= 60) return "high";
|
|
@@ -51,4 +51,4 @@ function scanChurn(dataDir, today = (/* @__PURE__ */ new Date()).toISOString().s
|
|
|
51
51
|
//#endregion
|
|
52
52
|
export { assessChurn, scanChurn };
|
|
53
53
|
|
|
54
|
-
//# sourceMappingURL=churn-
|
|
54
|
+
//# sourceMappingURL=churn-DN9WDGNM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"churn-
|
|
1
|
+
{"version":3,"file":"churn-DN9WDGNM.js","names":[],"sources":["../src/core/churn.ts"],"sourcesContent":["import { computeCustomerHealth } from \"./relationship-health.js\";\nimport { listCustomerSlugs } from \"../fs/customer-dir.js\";\n\n/**\n * Churn early-warning (domino D13 / C4): turns the per-contact relationship\n * health signals (recency, cadence, momentum, champion-silence) into an\n * account-level churn-risk read with plain-language signals, so the agent can\n * surface at-risk customers before they quietly lapse. Builds on the D5-clean\n * data and the existing relationship-health engine — no new data required.\n */\nexport type ChurnLevel = \"low\" | \"medium\" | \"high\";\n\nexport interface ChurnAssessment {\n slug: string;\n riskScore: number; // 0–100, higher = more likely to churn\n level: ChurnLevel;\n signals: string[];\n}\n\nfunction levelFromScore(score: number): ChurnLevel {\n if (score >= 60) return \"high\";\n if (score >= 30) return \"medium\";\n return \"low\";\n}\n\n/**\n * Assess one customer's churn risk. Risk is the inverse of relationship health,\n * amplified by explicit risk flags (no-contact windows, silent champions) and\n * declining/cold trends across the customer's contacts.\n */\nexport function assessChurn(\n dataDir: string,\n slug: string,\n today: string = new Date().toISOString().slice(0, 10)\n): ChurnAssessment {\n const health = computeCustomerHealth(dataDir, slug, today);\n const signals: string[] = [];\n\n // Base risk is the inverse of overall relationship health.\n let risk = 100 - health.overallHealth;\n\n if (health.contacts.length === 0) {\n signals.push(\"No logged interactions — relationship never established\");\n risk = Math.max(risk, 65);\n }\n\n const flagged = new Set<string>();\n for (const c of health.contacts) {\n for (const f of c.riskFlags) flagged.add(f);\n if (c.trend === \"cold\") signals.push(`${c.name} has gone cold (${c.daysSinceContact}d silent)`);\n else if (c.trend === \"declining\") signals.push(`${c.name}'s engagement is declining`);\n }\n\n if (flagged.has(\"NO_CONTACT_30D\")) {\n signals.push(\"A key contact has had no contact in 30+ days\");\n risk += 15;\n } else if (flagged.has(\"NO_CONTACT_14D\")) {\n signals.push(\"A key contact has had no contact in 14+ days\");\n risk += 5;\n }\n if (flagged.has(\"CHAMPION_SILENT\")) {\n signals.push(\"Champion has gone silent — high-leverage risk\");\n risk += 20;\n }\n\n const riskScore = Math.max(0, Math.min(100, Math.round(risk)));\n return { slug, riskScore, level: levelFromScore(riskScore), signals };\n}\n\n/** Assess every customer and return them ranked by churn risk (highest first). */\nexport function scanChurn(\n dataDir: string,\n today: string = new Date().toISOString().slice(0, 10)\n): ChurnAssessment[] {\n return listCustomerSlugs(dataDir)\n .map((slug) => assessChurn(dataDir, slug, today))\n .sort((a, b) => b.riskScore - a.riskScore);\n}\n"],"mappings":";;;AAmBA,SAAS,eAAe,OAA2B;CACjD,IAAI,SAAS,IAAI,OAAO;CACxB,IAAI,SAAS,IAAI,OAAO;CACxB,OAAO;AACT;;;;;;AAOA,SAAgB,YACd,SACA,MACA,yBAAgB,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,GACnC;CACjB,MAAM,SAAS,sBAAsB,SAAS,MAAM,KAAK;CACzD,MAAM,UAAoB,CAAC;CAG3B,IAAI,OAAO,MAAM,OAAO;CAExB,IAAI,OAAO,SAAS,WAAW,GAAG;EAChC,QAAQ,KAAK,yDAAyD;EACtE,OAAO,KAAK,IAAI,MAAM,EAAE;CAC1B;CAEA,MAAM,0BAAU,IAAI,IAAY;CAChC,KAAK,MAAM,KAAK,OAAO,UAAU;EAC/B,KAAK,MAAM,KAAK,EAAE,WAAW,QAAQ,IAAI,CAAC;EAC1C,IAAI,EAAE,UAAU,QAAQ,QAAQ,KAAK,GAAG,EAAE,KAAK,kBAAkB,EAAE,iBAAiB,UAAU;OACzF,IAAI,EAAE,UAAU,aAAa,QAAQ,KAAK,GAAG,EAAE,KAAK,2BAA2B;CACtF;CAEA,IAAI,QAAQ,IAAI,gBAAgB,GAAG;EACjC,QAAQ,KAAK,8CAA8C;EAC3D,QAAQ;CACV,OAAO,IAAI,QAAQ,IAAI,gBAAgB,GAAG;EACxC,QAAQ,KAAK,8CAA8C;EAC3D,QAAQ;CACV;CACA,IAAI,QAAQ,IAAI,iBAAiB,GAAG;EAClC,QAAQ,KAAK,+CAA+C;EAC5D,QAAQ;CACV;CAEA,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC;CAC7D,OAAO;EAAE;EAAM;EAAW,OAAO,eAAe,SAAS;EAAG;CAAQ;AACtE;;AAGA,SAAgB,UACd,SACA,yBAAgB,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,GACjC;CACnB,OAAO,kBAAkB,OAAO,EAC7B,KAAK,SAAS,YAAY,SAAS,MAAM,KAAK,CAAC,EAC/C,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAC7C"}
|