@datasynx/agentic-crm 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cli.js +101 -8
  2. package/dist/cli.js.map +1 -1
  3. package/dist/daemon/worker.js +3 -3
  4. package/dist/funnel-B2mwpZE1.js +89 -0
  5. package/dist/funnel-B2mwpZE1.js.map +1 -0
  6. package/dist/funnel-CJ7fy7hG.js +2 -0
  7. package/dist/{index-DcDaz_cu.d.cts → index-BAutNcAT.d.cts} +11 -11
  8. package/dist/index-BAutNcAT.d.cts.map +1 -0
  9. package/dist/index.d.cts +11 -11
  10. package/dist/index.d.cts.map +1 -1
  11. package/dist/{knowledge-base-Cc0niBFf.js → knowledge-base-yo-BLUcB.js} +3 -1
  12. package/dist/knowledge-base-yo-BLUcB.js.map +1 -0
  13. package/dist/{login-yt9OOQQk.js → login-dc_Hqosw.js} +2 -2
  14. package/dist/{login-yt9OOQQk.js.map → login-dc_Hqosw.js.map} +1 -1
  15. package/dist/mailbox-config-BU3vib2T.js +2 -0
  16. package/dist/{mailbox-config-Dn2xTn9N.js → mailbox-config-trjLPHPG.js} +2 -2
  17. package/dist/{mailbox-config-Dn2xTn9N.js.map → mailbox-config-trjLPHPG.js.map} +1 -1
  18. package/dist/{mailbox-poll-B8dvFAXT.js → mailbox-poll-Ban7C3X0.js} +3 -3
  19. package/dist/{mailbox-poll-B8dvFAXT.js.map → mailbox-poll-Ban7C3X0.js.map} +1 -1
  20. package/dist/mcp-CdTJWTJf.d.cts.map +1 -1
  21. package/dist/mcp-CdTJWTJf.d.ts.map +1 -1
  22. package/dist/mcp.cjs +415 -137
  23. package/dist/mcp.cjs.map +1 -1
  24. package/dist/mcp.d.cts.map +1 -1
  25. package/dist/mcp.d.ts.map +1 -1
  26. package/dist/mcp.js +415 -137
  27. package/dist/mcp.js.map +1 -1
  28. package/dist/{server-BbInMUgp.js → server-DAcwmRPE.js} +195 -133
  29. package/dist/server-DAcwmRPE.js.map +1 -0
  30. package/dist/{token-resolver-D98qPOOf.js → token-resolver-CL8-CSgZ.js} +2 -2
  31. package/dist/{token-resolver-D98qPOOf.js.map → token-resolver-CL8-CSgZ.js.map} +1 -1
  32. package/dist/{token-store-B0h0USqe.js → token-store-BPDwePNT.js} +13 -2
  33. package/dist/token-store-BPDwePNT.js.map +1 -0
  34. package/dist/token-store-D3HCeXmE.js +2 -0
  35. package/dist/velocity-BtX1l5Gh.js +2 -0
  36. package/dist/velocity-C2l4cW0K.js +123 -0
  37. package/dist/velocity-C2l4cW0K.js.map +1 -0
  38. package/package.json +1 -1
  39. package/dist/index-DcDaz_cu.d.cts.map +0 -1
  40. package/dist/knowledge-base-Cc0niBFf.js.map +0 -1
  41. package/dist/mailbox-config-Bu-J1O4I.js +0 -2
  42. package/dist/server-BbInMUgp.js.map +0 -1
  43. package/dist/token-store-B0h0USqe.js.map +0 -1
  44. package/dist/token-store-CEmz8d-0.js +0 -2
package/dist/mcp.js CHANGED
@@ -445,6 +445,8 @@ Config: \`.agentic/rbac.json\` | Actor: \`DXCRM_ACTOR\` env var
445
445
  | get_logs | Query/aggregate the structured application log (level, component, errors) | admin |
446
446
  | get_diagnostics | Self-diagnostic health check (data integrity, temp files, log errors, backups) | admin |
447
447
  | get_pipeline_changes | Pipeline time-travel: what changed (won/lost/moved/value) since a date | any |
448
+ | get_pipeline_velocity | Stage dwell times, sales cycle, and stalled deals from snapshot history | any |
449
+ | get_pipeline_funnel | Conversion funnel & win rate: where deals leak out of the pipeline | any |
448
450
  | define_custom_object | Define a runtime custom object type with typed fields (no migration) | admin |
449
451
  | create_record | Create a record of a custom object (validated against its schema) | rep+ |
450
452
  | list_records | List records of a custom object | any |
@@ -1423,7 +1425,7 @@ async function buildContext(dataDir, slug) {
1423
1425
  }
1424
1426
  //#endregion
1425
1427
  //#region src/mcp/tools/get-customer-context.ts
1426
- const DATA_DIR$54 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1428
+ const DATA_DIR$56 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1427
1429
  function triggerOnQuerySync(dataDir, slug) {
1428
1430
  const auth = getGmailAuth();
1429
1431
  if (!auth) return;
@@ -1444,7 +1446,7 @@ function triggerOnQuerySync(dataDir, slug) {
1444
1446
  }).then(() => updateSlugSyncState(dataDir, slug, { lastGmailSync: (/* @__PURE__ */ new Date()).toISOString() })).catch(() => {})).catch(() => {});
1445
1447
  } catch {}
1446
1448
  }
1447
- async function handleGetCustomerContext(input, dataDir = DATA_DIR$54) {
1449
+ async function handleGetCustomerContext(input, dataDir = DATA_DIR$56) {
1448
1450
  const targetSlug = input.slug ?? getSession()?.customerSlug;
1449
1451
  if (!targetSlug) return {
1450
1452
  content: [{
@@ -1580,8 +1582,8 @@ async function searchKnowledge(dataDir, slug, query, limit) {
1580
1582
  }
1581
1583
  //#endregion
1582
1584
  //#region src/mcp/tools/search-customer-knowledge.ts
1583
- const DATA_DIR$53 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1584
- async function handleSearchCustomerKnowledge(input, dataDir = DATA_DIR$53) {
1585
+ const DATA_DIR$55 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1586
+ async function handleSearchCustomerKnowledge(input, dataDir = DATA_DIR$55) {
1585
1587
  const limit = input.limit ?? 5;
1586
1588
  try {
1587
1589
  const results = await searchKnowledge(dataDir, input.slug, input.query, limit);
@@ -1629,14 +1631,14 @@ If no results: returns empty array with a helpful sync suggestion.`,
1629
1631
  }
1630
1632
  //#endregion
1631
1633
  //#region src/mcp/tools/list-customers.ts
1632
- const DATA_DIR$52 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1634
+ const DATA_DIR$54 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1633
1635
  function extractLastInteractionDate(interactionsPath) {
1634
1636
  if (!fs.existsSync(interactionsPath)) return void 0;
1635
1637
  const content = fs.readFileSync(interactionsPath, "utf-8");
1636
1638
  const match = /^## (\d{4}-\d{2}-\d{2})/m.exec(content);
1637
1639
  return match ? match[1] : void 0;
1638
1640
  }
1639
- async function handleListCustomers(input, dataDir = DATA_DIR$52) {
1641
+ async function handleListCustomers(input, dataDir = DATA_DIR$54) {
1640
1642
  const customersDir = path.join(dataDir, "customers");
1641
1643
  const customers = [];
1642
1644
  if (!fs.existsSync(customersDir)) return { content: [{
@@ -2149,8 +2151,8 @@ async function updateHealthFromInteraction(dataDir, slug) {
2149
2151
  }
2150
2152
  //#endregion
2151
2153
  //#region src/mcp/tools/log-interaction.ts
2152
- const DATA_DIR$51 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2153
- async function handleLogInteraction(input, dataDir = DATA_DIR$51) {
2154
+ const DATA_DIR$53 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2155
+ async function handleLogInteraction(input, dataDir = DATA_DIR$53) {
2154
2156
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2155
2157
  const interactionDate = input.date ?? today;
2156
2158
  const sourceRef = input.source ?? `agent://log/${Date.now()}`;
@@ -2259,8 +2261,8 @@ var update_deal_exports = /* @__PURE__ */ __exportAll({
2259
2261
  handleUpdateDeal: () => handleUpdateDeal,
2260
2262
  registerUpdateDeal: () => registerUpdateDeal
2261
2263
  });
2262
- const DATA_DIR$50 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2263
- async function handleUpdateDeal(input, dataDir = DATA_DIR$50) {
2264
+ const DATA_DIR$52 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2265
+ async function handleUpdateDeal(input, dataDir = DATA_DIR$52) {
2264
2266
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2265
2267
  const deal = {
2266
2268
  name: input.dealName,
@@ -2343,12 +2345,12 @@ Returns: { success: boolean, deal: object }`,
2343
2345
  }
2344
2346
  //#endregion
2345
2347
  //#region src/mcp/tools/export-customer.ts
2346
- const DATA_DIR$49 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2348
+ const DATA_DIR$51 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2347
2349
  function countInteractions(content) {
2348
2350
  const matches = content.match(/^## \d{4}-\d{2}-\d{2}/gm);
2349
2351
  return matches ? matches.length : 0;
2350
2352
  }
2351
- async function handleExportCustomer(input, dataDir = DATA_DIR$49) {
2353
+ async function handleExportCustomer(input, dataDir = DATA_DIR$51) {
2352
2354
  enforceRbac(dataDir, "export_customer");
2353
2355
  const customerDir = path.join(dataDir, "customers", input.slug);
2354
2356
  if (!fs.existsSync(customerDir)) return {
@@ -2461,8 +2463,8 @@ Returns:
2461
2463
  }
2462
2464
  //#endregion
2463
2465
  //#region src/mcp/tools/update-customer-facts.ts
2464
- const DATA_DIR$48 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2465
- async function handleUpdateCustomerFacts(input, dataDir = DATA_DIR$48) {
2466
+ const DATA_DIR$50 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2467
+ async function handleUpdateCustomerFacts(input, dataDir = DATA_DIR$50) {
2466
2468
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2467
2469
  try {
2468
2470
  enforceRbac(dataDir, "update_customer_facts");
@@ -2640,8 +2642,8 @@ function scoreDealForToday(deal, todayDate) {
2640
2642
  }
2641
2643
  //#endregion
2642
2644
  //#region src/mcp/tools/get-deal-health.ts
2643
- const DATA_DIR$47 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2644
- async function handleGetDealHealth(input, dataDir = DATA_DIR$47) {
2645
+ const DATA_DIR$49 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2646
+ async function handleGetDealHealth(input, dataDir = DATA_DIR$49) {
2645
2647
  try {
2646
2648
  const deals = await readPipeline(dataDir, input.slug);
2647
2649
  const today = /* @__PURE__ */ new Date();
@@ -2690,8 +2692,8 @@ Returns: { slug, deals: [{ deal, stage, score, grade, signals, warnings }] }`,
2690
2692
  }
2691
2693
  //#endregion
2692
2694
  //#region src/mcp/tools/get-pipeline-forecast.ts
2693
- const DATA_DIR$46 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2694
- async function handleGetPipelineForecast(input, dataDir = DATA_DIR$46) {
2695
+ const DATA_DIR$48 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2696
+ async function handleGetPipelineForecast(input, dataDir = DATA_DIR$48) {
2695
2697
  try {
2696
2698
  const slugs = listCustomerSlugs(dataDir).filter((d) => !input.filter || d.includes(input.filter));
2697
2699
  const allDeals = [];
@@ -2752,8 +2754,8 @@ Returns: { deals: [...], totalWeightedValue: number, byStage: { stage: { count,
2752
2754
  }
2753
2755
  //#endregion
2754
2756
  //#region src/mcp/tools/summarize-meeting.ts
2755
- const DATA_DIR$45 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2756
- async function handleSummarizeMeeting(input, dataDir = DATA_DIR$45) {
2757
+ const DATA_DIR$47 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2758
+ async function handleSummarizeMeeting(input, dataDir = DATA_DIR$47) {
2757
2759
  try {
2758
2760
  let summary = input.transcript.slice(0, 400);
2759
2761
  let nextSteps = [];
@@ -2876,8 +2878,8 @@ function getPipelineStages(dataDir) {
2876
2878
  }
2877
2879
  //#endregion
2878
2880
  //#region src/mcp/tools/get-pipeline-stages.ts
2879
- const DATA_DIR$44 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2880
- async function handleGetPipelineStages(_input, dataDir = DATA_DIR$44) {
2881
+ const DATA_DIR$46 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2882
+ async function handleGetPipelineStages(_input, dataDir = DATA_DIR$46) {
2881
2883
  const stages = getPipelineStages(dataDir);
2882
2884
  return { content: [{
2883
2885
  type: "text",
@@ -2905,8 +2907,8 @@ async function searchAcrossCustomers(dataDir, query, limit = 5, excludeSlug) {
2905
2907
  }
2906
2908
  //#endregion
2907
2909
  //#region src/mcp/tools/get-market-intelligence.ts
2908
- const DATA_DIR$43 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2909
- async function handleGetMarketIntelligence(input, dataDir = DATA_DIR$43) {
2910
+ const DATA_DIR$45 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2911
+ async function handleGetMarketIntelligence(input, dataDir = DATA_DIR$45) {
2910
2912
  const excludeSlug = input.excludeCurrentCustomer ? input.slug : void 0;
2911
2913
  const all = listCustomerSlugs(dataDir);
2912
2914
  const totalCustomersSearched = excludeSlug ? all.filter((s) => s !== excludeSlug).length : all.length;
@@ -2937,7 +2939,7 @@ function registerGetMarketIntelligence(server) {
2937
2939
  }
2938
2940
  //#endregion
2939
2941
  //#region src/mcp/tools/get-relationship-graph.ts
2940
- const DATA_DIR$42 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2942
+ const DATA_DIR$44 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2941
2943
  function summarizeNode(n) {
2942
2944
  return {
2943
2945
  id: n.id,
@@ -2945,7 +2947,7 @@ function summarizeNode(n) {
2945
2947
  email: n.properties["email"]
2946
2948
  };
2947
2949
  }
2948
- async function handleGetRelationshipGraph(input, dataDir = DATA_DIR$42) {
2950
+ async function handleGetRelationshipGraph(input, dataDir = DATA_DIR$44) {
2949
2951
  try {
2950
2952
  const graph = readGraph(dataDir, input.slug);
2951
2953
  const stakeholders = getStakeholders(graph);
@@ -3013,9 +3015,9 @@ Returns: {
3013
3015
  }
3014
3016
  //#endregion
3015
3017
  //#region src/mcp/tools/get-relationship-health.ts
3016
- const DATA_DIR$41 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3018
+ const DATA_DIR$43 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3017
3019
  const MAX_HEALTH_AGE_MS = 3600 * 1e3;
3018
- async function handleGetRelationshipHealth(input, dataDir = DATA_DIR$41) {
3020
+ async function handleGetRelationshipHealth(input, dataDir = DATA_DIR$43) {
3019
3021
  try {
3020
3022
  let health = readHealth(dataDir, input.slug);
3021
3023
  if (health === null || Date.now() - new Date(health.updatedAt).getTime() > MAX_HEALTH_AGE_MS) {
@@ -3684,8 +3686,8 @@ async function runDealAgent(config, dataDir, llmFn = callLlm) {
3684
3686
  }
3685
3687
  //#endregion
3686
3688
  //#region src/mcp/tools/run-deal-agent.ts
3687
- const DATA_DIR$40 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3688
- async function handleRunDealAgent(input, dataDir = DATA_DIR$40) {
3689
+ const DATA_DIR$42 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3690
+ async function handleRunDealAgent(input, dataDir = DATA_DIR$42) {
3689
3691
  try {
3690
3692
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3691
3693
  const result = await runDealAgent({
@@ -3752,8 +3754,8 @@ Returns: { assessment, riskLevel, plan[], actionsQueued[], actionsExecuted[], tr
3752
3754
  }
3753
3755
  //#endregion
3754
3756
  //#region src/mcp/tools/approve-agent-action.ts
3755
- const DATA_DIR$39 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3756
- async function handleApproveAgentAction(input, dataDir = DATA_DIR$39) {
3757
+ const DATA_DIR$41 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3758
+ async function handleApproveAgentAction(input, dataDir = DATA_DIR$41) {
3757
3759
  try {
3758
3760
  const queue = readAgentQueue(dataDir, input.slug);
3759
3761
  const idx = queue.pendingActions.findIndex((a) => a.actionId === input.actionId);
@@ -4013,8 +4015,8 @@ async function buildSimulationInput(dataDir, horizon, today, externalSignals = [
4013
4015
  }
4014
4016
  //#endregion
4015
4017
  //#region src/mcp/tools/simulate-revenue.ts
4016
- const DATA_DIR$38 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4017
- async function handleSimulateRevenue(input, dataDir = DATA_DIR$38) {
4018
+ const DATA_DIR$40 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4019
+ async function handleSimulateRevenue(input, dataDir = DATA_DIR$40) {
4018
4020
  try {
4019
4021
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4020
4022
  const horizon = input.horizon ?? "quarter";
@@ -4072,8 +4074,8 @@ Returns: { forecast: { p10, p50, p90, expected, stdDev, atRiskRevenue, byCloseMo
4072
4074
  }
4073
4075
  //#endregion
4074
4076
  //#region src/mcp/tools/get-playbook.ts
4075
- const DATA_DIR$37 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4076
- async function handleGetPlaybook(input, dataDir = DATA_DIR$37) {
4077
+ const DATA_DIR$39 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4078
+ async function handleGetPlaybook(input, dataDir = DATA_DIR$39) {
4077
4079
  try {
4078
4080
  const playbooks = listPlaybooks(dataDir, input.slug);
4079
4081
  if (!(input.stage !== void 0 || input.value !== void 0 || input.healthScore !== void 0)) return { content: [{
@@ -4158,12 +4160,12 @@ Returns: { matches: [{ name, score, trigger, successRate, usedCount, content }],
4158
4160
  ...healthScore !== void 0 ? { healthScore } : {},
4159
4161
  ...daysSinceContact !== void 0 ? { daysSinceContact } : {},
4160
4162
  ...championPresent !== void 0 ? { championPresent } : {}
4161
- }, DATA_DIR$37));
4163
+ }, DATA_DIR$39));
4162
4164
  }
4163
4165
  //#endregion
4164
4166
  //#region src/mcp/tools/create-playbook.ts
4165
- const DATA_DIR$36 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4166
- async function handleCreatePlaybook(input, dataDir = DATA_DIR$36) {
4167
+ const DATA_DIR$38 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4168
+ async function handleCreatePlaybook(input, dataDir = DATA_DIR$38) {
4167
4169
  try {
4168
4170
  const name = toKebabCase(input.name);
4169
4171
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -4236,12 +4238,12 @@ Returns: { success: true, playbook: { name, trigger, successRate, path } }`,
4236
4238
  trigger,
4237
4239
  content,
4238
4240
  ...successRate !== void 0 ? { successRate } : {}
4239
- }, DATA_DIR$36));
4241
+ }, DATA_DIR$38));
4240
4242
  }
4241
4243
  //#endregion
4242
4244
  //#region src/mcp/tools/list-playbooks.ts
4243
- const DATA_DIR$35 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4244
- async function handleListPlaybooks(input, dataDir = DATA_DIR$35) {
4245
+ const DATA_DIR$37 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4246
+ async function handleListPlaybooks(input, dataDir = DATA_DIR$37) {
4245
4247
  try {
4246
4248
  const playbooks = listPlaybooks(dataDir, input.slug);
4247
4249
  return { content: [{
@@ -4280,12 +4282,12 @@ Args:
4280
4282
 
4281
4283
  Returns: { playbooks: [{ name, trigger, successRate, usedCount, lastUpdated }], count, slug }`,
4282
4284
  inputSchema: z.object({ slug: z.string().describe("Customer ID") })
4283
- }, async ({ slug }) => handleListPlaybooks({ slug }, DATA_DIR$35));
4285
+ }, async ({ slug }) => handleListPlaybooks({ slug }, DATA_DIR$37));
4284
4286
  }
4285
4287
  //#endregion
4286
4288
  //#region src/mcp/tools/distill-playbook.ts
4287
- const DATA_DIR$34 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4288
- async function handleDistillPlaybook(input, dataDir = DATA_DIR$34, llmFn = callLlm) {
4289
+ const DATA_DIR$36 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4290
+ async function handleDistillPlaybook(input, dataDir = DATA_DIR$36, llmFn = callLlm) {
4289
4291
  try {
4290
4292
  const result = await distillPlaybook(dataDir, input.slug, input.dealName, input.outcome, llmFn);
4291
4293
  if (!result.ok) {
@@ -4344,7 +4346,7 @@ Returns: { success: true, playbook: { name, trigger, successRate, path }, reason
4344
4346
  slug,
4345
4347
  dealName,
4346
4348
  outcome
4347
- }, DATA_DIR$34));
4349
+ }, DATA_DIR$36));
4348
4350
  }
4349
4351
  //#endregion
4350
4352
  //#region src/core/goal-engine.ts
@@ -4562,8 +4564,8 @@ function getActiveGoals(dataDir) {
4562
4564
  }
4563
4565
  //#endregion
4564
4566
  //#region src/mcp/tools/pursue-goal.ts
4565
- const DATA_DIR$33 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4566
- async function handlePursueGoal(input, dataDir = DATA_DIR$33, options = {}) {
4567
+ const DATA_DIR$35 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4568
+ async function handlePursueGoal(input, dataDir = DATA_DIR$35, options = {}) {
4567
4569
  try {
4568
4570
  enforceRbac(dataDir, "pursue_goal");
4569
4571
  const goal = await pursueGoal(dataDir, {
@@ -4626,12 +4628,12 @@ Returns: { goalId, description, target, deadline, decomposition: { analysis, cur
4626
4628
  goal,
4627
4629
  deadline,
4628
4630
  ...context !== void 0 ? { context } : {}
4629
- }, DATA_DIR$33));
4631
+ }, DATA_DIR$35));
4630
4632
  }
4631
4633
  //#endregion
4632
4634
  //#region src/mcp/tools/get-goal-status.ts
4633
- const DATA_DIR$32 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4634
- async function handleGetGoalStatus(input, dataDir = DATA_DIR$32) {
4635
+ const DATA_DIR$34 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4636
+ async function handleGetGoalStatus(input, dataDir = DATA_DIR$34) {
4635
4637
  try {
4636
4638
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4637
4639
  const allGoals = input.goalId ? readGoals(dataDir).filter((g) => g.id === input.goalId) : getActiveGoals(dataDir);
@@ -4690,17 +4692,17 @@ Args:
4690
4692
 
4691
4693
  Returns: { goals: [{ id, description, target, progress, status, deadline, daysRemaining, subGoals }], activeCount, completedCount }`,
4692
4694
  inputSchema: z.object({ goalId: z.string().optional().describe("Specific goal ID (omit for all active goals)") })
4693
- }, async ({ goalId }) => handleGetGoalStatus({ ...goalId !== void 0 ? { goalId } : {} }, DATA_DIR$32));
4695
+ }, async ({ goalId }) => handleGetGoalStatus({ ...goalId !== void 0 ? { goalId } : {} }, DATA_DIR$34));
4694
4696
  }
4695
4697
  //#endregion
4696
4698
  //#region src/mcp/tools/register-push-subscription.ts
4697
- const DATA_DIR$31 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4699
+ const DATA_DIR$33 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4698
4700
  const VALID_PROVIDERS = [
4699
4701
  "gmail",
4700
4702
  "microsoft-graph",
4701
4703
  "slack"
4702
4704
  ];
4703
- async function handleRegisterPushSubscription(input, dataDir = DATA_DIR$31) {
4705
+ async function handleRegisterPushSubscription(input, dataDir = DATA_DIR$33) {
4704
4706
  try {
4705
4707
  if (!VALID_PROVIDERS.includes(input.provider)) return { content: [{
4706
4708
  type: "text",
@@ -4786,12 +4788,12 @@ Returns: { subscriptionId, provider, slug, status, expiresAt, createdAt, warning
4786
4788
  ...microsoftResource !== void 0 ? { microsoftResource } : {},
4787
4789
  ...slackTeamId !== void 0 ? { slackTeamId } : {},
4788
4790
  ...slackChannelId !== void 0 ? { slackChannelId } : {}
4789
- }, DATA_DIR$31));
4791
+ }, DATA_DIR$33));
4790
4792
  }
4791
4793
  //#endregion
4792
4794
  //#region src/mcp/tools/get-push-status.ts
4793
- const DATA_DIR$30 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4794
- async function handleGetPushStatus(input, dataDir = DATA_DIR$30) {
4795
+ const DATA_DIR$32 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4796
+ async function handleGetPushStatus(input, dataDir = DATA_DIR$32) {
4795
4797
  try {
4796
4798
  let subs = await readSubscriptions(dataDir);
4797
4799
  if (input.slug) subs = subs.filter((s) => s.slug === input.slug);
@@ -4863,7 +4865,7 @@ Returns: { subscriptions: [{ id, provider, slug, status, expiresAt, expiresInHou
4863
4865
  }, async ({ slug, provider }) => handleGetPushStatus({
4864
4866
  ...slug !== void 0 ? { slug } : {},
4865
4867
  ...provider !== void 0 ? { provider } : {}
4866
- }, DATA_DIR$30));
4868
+ }, DATA_DIR$32));
4867
4869
  }
4868
4870
  //#endregion
4869
4871
  //#region src/core/org-intelligence.ts
@@ -4929,8 +4931,8 @@ function deriveRecommendation(people, missingRoles) {
4929
4931
  }
4930
4932
  //#endregion
4931
4933
  //#region src/mcp/tools/get-org-intelligence.ts
4932
- const DATA_DIR$29 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4933
- async function handleGetOrgIntelligence(input, dataDir = DATA_DIR$29) {
4934
+ const DATA_DIR$31 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4935
+ async function handleGetOrgIntelligence(input, dataDir = DATA_DIR$31) {
4934
4936
  try {
4935
4937
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4936
4938
  const map = buildStakeholderMap(dataDir, input.slug, today, input.dealName);
@@ -5063,8 +5065,8 @@ function buildExecutiveSummary(slug, dealName, stakeholders, overallHealth, sim,
5063
5065
  }
5064
5066
  //#endregion
5065
5067
  //#region src/mcp/tools/open-deal-room.ts
5066
- const DATA_DIR$28 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5067
- async function handleOpenDealRoom(input, dataDir = DATA_DIR$28) {
5068
+ const DATA_DIR$30 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5069
+ async function handleOpenDealRoom(input, dataDir = DATA_DIR$30) {
5068
5070
  try {
5069
5071
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5070
5072
  const brief = await buildDealRoom(dataDir, input.slug, input.dealName, today);
@@ -5147,8 +5149,8 @@ async function buildDailyBriefing(dataDir, today) {
5147
5149
  }
5148
5150
  //#endregion
5149
5151
  //#region src/mcp/tools/get-proactive-briefing.ts
5150
- const DATA_DIR$27 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5151
- async function handleGetProactiveBriefing(input, dataDir = DATA_DIR$27) {
5152
+ const DATA_DIR$29 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5153
+ async function handleGetProactiveBriefing(input, dataDir = DATA_DIR$29) {
5152
5154
  try {
5153
5155
  const briefing = await buildDailyBriefing(dataDir, input.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
5154
5156
  return { content: [{
@@ -5248,15 +5250,15 @@ function getTemplate(dataDir, id) {
5248
5250
  }
5249
5251
  //#endregion
5250
5252
  //#region src/mcp/tools/list-email-templates.ts
5251
- const DATA_DIR$26 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5252
- async function handleListEmailTemplates(input, dataDir = DATA_DIR$26) {
5253
+ const DATA_DIR$28 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5254
+ async function handleListEmailTemplates(input, dataDir = DATA_DIR$28) {
5253
5255
  const summary = listTemplates(dataDir, input.category ? { category: input.category } : {}).map(({ body: _body, ...meta }) => meta);
5254
5256
  return { content: [{
5255
5257
  type: "text",
5256
5258
  text: JSON.stringify(summary, null, 2)
5257
5259
  }] };
5258
5260
  }
5259
- function registerListEmailTemplates(server, dataDir = DATA_DIR$26) {
5261
+ function registerListEmailTemplates(server, dataDir = DATA_DIR$28) {
5260
5262
  server.registerTool("list_email_templates", {
5261
5263
  description: "List available email templates. Optionally filter by category (e.g. 'outreach', 'followup', 'support').",
5262
5264
  inputSchema: z.object({ category: z.string().optional().describe("Filter by category") })
@@ -5290,8 +5292,8 @@ async function buildVariablesFromCustomer(dataDir, slug) {
5290
5292
  }
5291
5293
  //#endregion
5292
5294
  //#region src/mcp/tools/get-email-template.ts
5293
- const DATA_DIR$25 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5294
- async function handleGetEmailTemplate(input, dataDir = DATA_DIR$25) {
5295
+ const DATA_DIR$27 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5296
+ async function handleGetEmailTemplate(input, dataDir = DATA_DIR$27) {
5295
5297
  const tmpl = getTemplate(dataDir, input.id);
5296
5298
  if (!tmpl) return { content: [{
5297
5299
  type: "text",
@@ -5307,7 +5309,7 @@ async function handleGetEmailTemplate(input, dataDir = DATA_DIR$25) {
5307
5309
  }, null, 2)
5308
5310
  }] };
5309
5311
  }
5310
- function registerGetEmailTemplate(server, dataDir = DATA_DIR$25) {
5312
+ function registerGetEmailTemplate(server, dataDir = DATA_DIR$27) {
5311
5313
  server.registerTool("get_email_template", {
5312
5314
  description: "Get a specific email template by ID, including its body and detected variables.",
5313
5315
  inputSchema: z.object({ id: z.string().describe("Template ID (e.g. 'enterprise-intro')") })
@@ -5315,8 +5317,8 @@ function registerGetEmailTemplate(server, dataDir = DATA_DIR$25) {
5315
5317
  }
5316
5318
  //#endregion
5317
5319
  //#region src/mcp/tools/draft-email.ts
5318
- const DATA_DIR$24 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5319
- async function handleDraftEmail(input, dataDir = DATA_DIR$24) {
5320
+ const DATA_DIR$26 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5321
+ async function handleDraftEmail(input, dataDir = DATA_DIR$26) {
5320
5322
  const tmpl = getTemplate(dataDir, input.templateId);
5321
5323
  if (!tmpl) return { content: [{
5322
5324
  type: "text",
@@ -5360,7 +5362,7 @@ async function handleDraftEmail(input, dataDir = DATA_DIR$24) {
5360
5362
  }, null, 2)
5361
5363
  }] };
5362
5364
  }
5363
- function registerDraftEmail(server, dataDir = DATA_DIR$24) {
5365
+ function registerDraftEmail(server, dataDir = DATA_DIR$26) {
5364
5366
  server.registerTool("draft_email", {
5365
5367
  description: `Draft a personalized email for a customer using a stored template.
5366
5368
  Variables are auto-filled from the customer's main_facts.md. Override any variable manually.
@@ -5468,8 +5470,8 @@ async function updateEnrollment(dataDir, id, updates) {
5468
5470
  }
5469
5471
  //#endregion
5470
5472
  //#region src/mcp/tools/enroll-in-sequence.ts
5471
- const DATA_DIR$23 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5472
- async function handleEnrollInSequence(input, dataDir = DATA_DIR$23) {
5473
+ const DATA_DIR$25 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5474
+ async function handleEnrollInSequence(input, dataDir = DATA_DIR$25) {
5473
5475
  const sequence = getSequence(dataDir, input.sequenceId);
5474
5476
  if (!sequence) return { content: [{
5475
5477
  type: "text",
@@ -5501,7 +5503,7 @@ async function handleEnrollInSequence(input, dataDir = DATA_DIR$23) {
5501
5503
  })
5502
5504
  }] };
5503
5505
  }
5504
- function registerEnrollInSequence(server, dataDir = DATA_DIR$23) {
5506
+ function registerEnrollInSequence(server, dataDir = DATA_DIR$25) {
5505
5507
  server.registerTool("enroll_in_sequence", {
5506
5508
  description: `Enroll a contact in an email sequence. Validates that the sequence and its first template exist.
5507
5509
  Returns: { enrollmentId, sequenceName, totalSteps }`,
@@ -5518,8 +5520,8 @@ Returns: { enrollmentId, sequenceName, totalSteps }`,
5518
5520
  }
5519
5521
  //#endregion
5520
5522
  //#region src/mcp/tools/list-sequence-enrollments.ts
5521
- const DATA_DIR$22 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5522
- async function handleListSequenceEnrollments(input, dataDir = DATA_DIR$22) {
5523
+ const DATA_DIR$24 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5524
+ async function handleListSequenceEnrollments(input, dataDir = DATA_DIR$24) {
5523
5525
  let enrollments = readEnrollments(dataDir);
5524
5526
  if (input.slug !== void 0) enrollments = enrollments.filter((e) => e.slug === input.slug);
5525
5527
  if (input.status !== void 0) enrollments = enrollments.filter((e) => e.status === input.status);
@@ -5528,7 +5530,7 @@ async function handleListSequenceEnrollments(input, dataDir = DATA_DIR$22) {
5528
5530
  text: JSON.stringify({ enrollments }, null, 2)
5529
5531
  }] };
5530
5532
  }
5531
- function registerListSequenceEnrollments(server, dataDir = DATA_DIR$22) {
5533
+ function registerListSequenceEnrollments(server, dataDir = DATA_DIR$24) {
5532
5534
  server.registerTool("list_sequence_enrollments", {
5533
5535
  description: `List email sequence enrollments. Filter by customer slug or status.
5534
5536
  Returns: { enrollments: SequenceEnrollment[] }`,
@@ -5547,8 +5549,8 @@ Returns: { enrollments: SequenceEnrollment[] }`,
5547
5549
  }
5548
5550
  //#endregion
5549
5551
  //#region src/mcp/tools/unenroll-from-sequence.ts
5550
- const DATA_DIR$21 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5551
- async function handleUnenrollFromSequence(input, dataDir = DATA_DIR$21) {
5552
+ const DATA_DIR$23 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5553
+ async function handleUnenrollFromSequence(input, dataDir = DATA_DIR$23) {
5552
5554
  if (!await updateEnrollment(dataDir, input.enrollmentId, { status: "paused" })) return { content: [{
5553
5555
  type: "text",
5554
5556
  text: JSON.stringify({
@@ -5561,7 +5563,7 @@ async function handleUnenrollFromSequence(input, dataDir = DATA_DIR$21) {
5561
5563
  text: JSON.stringify({ success: true })
5562
5564
  }] };
5563
5565
  }
5564
- function registerUnenrollFromSequence(server, dataDir = DATA_DIR$21) {
5566
+ function registerUnenrollFromSequence(server, dataDir = DATA_DIR$23) {
5565
5567
  server.registerTool("unenroll_from_sequence", {
5566
5568
  description: `Unenroll (pause) a contact from an email sequence. Sets status to "paused" (soft delete).
5567
5569
  Returns: { success: boolean }`,
@@ -5570,8 +5572,8 @@ Returns: { success: boolean }`,
5570
5572
  }
5571
5573
  //#endregion
5572
5574
  //#region src/mcp/tools/list-sequences.ts
5573
- const DATA_DIR$20 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5574
- async function handleListSequences(_input, dataDir = DATA_DIR$20) {
5575
+ const DATA_DIR$22 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5576
+ async function handleListSequences(_input, dataDir = DATA_DIR$22) {
5575
5577
  const sequences = listSequences(dataDir);
5576
5578
  const enrollments = readEnrollments(dataDir);
5577
5579
  const result = sequences.map((seq) => ({
@@ -5585,7 +5587,7 @@ async function handleListSequences(_input, dataDir = DATA_DIR$20) {
5585
5587
  text: JSON.stringify({ sequences: result }, null, 2)
5586
5588
  }] };
5587
5589
  }
5588
- function registerListSequences(server, dataDir = DATA_DIR$20) {
5590
+ function registerListSequences(server, dataDir = DATA_DIR$22) {
5589
5591
  server.registerTool("list_sequences", {
5590
5592
  description: `List all email sequences with step count and enrollment count.
5591
5593
  Returns: { sequences: Array<{ id, name, stepCount, enrollmentCount }> }`,
@@ -5720,8 +5722,8 @@ async function generateQuote(dataDir, input) {
5720
5722
  }
5721
5723
  //#endregion
5722
5724
  //#region src/mcp/tools/generate-quote.ts
5723
- const DATA_DIR$19 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5724
- async function handleGenerateQuote(input, dataDir = DATA_DIR$19) {
5725
+ const DATA_DIR$21 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5726
+ async function handleGenerateQuote(input, dataDir = DATA_DIR$21) {
5725
5727
  try {
5726
5728
  const quote = await generateQuote(dataDir, input);
5727
5729
  return { content: [{
@@ -5745,7 +5747,7 @@ async function handleGenerateQuote(input, dataDir = DATA_DIR$19) {
5745
5747
  }] };
5746
5748
  }
5747
5749
  }
5748
- function registerGenerateQuote(server, dataDir = DATA_DIR$19) {
5750
+ function registerGenerateQuote(server, dataDir = DATA_DIR$21) {
5749
5751
  server.registerTool("generate_quote", {
5750
5752
  description: `Generate a professional HTML quote/offer for a customer deal.
5751
5753
  Calculates subtotal, VAT, and total. Saves JSON + HTML to .agentic/quotes/.
@@ -5773,8 +5775,8 @@ Returns: { quoteNumber, htmlPath, total, currency, validUntil }`,
5773
5775
  }
5774
5776
  //#endregion
5775
5777
  //#region src/mcp/tools/get-quote-status.ts
5776
- const DATA_DIR$18 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5777
- async function handleGetQuoteStatus(input, dataDir = DATA_DIR$18) {
5778
+ const DATA_DIR$20 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5779
+ async function handleGetQuoteStatus(input, dataDir = DATA_DIR$20) {
5778
5780
  if (input.quoteNumber) {
5779
5781
  const quote = readQuote(dataDir, input.quoteNumber);
5780
5782
  if (!quote) return { content: [{
@@ -5792,7 +5794,7 @@ async function handleGetQuoteStatus(input, dataDir = DATA_DIR$18) {
5792
5794
  text: JSON.stringify({ quotes }, null, 2)
5793
5795
  }] };
5794
5796
  }
5795
- function registerGetQuoteStatus(server, dataDir = DATA_DIR$18) {
5797
+ function registerGetQuoteStatus(server, dataDir = DATA_DIR$20) {
5796
5798
  server.registerTool("get_quote_status", {
5797
5799
  description: `Get quote status and details. Filter by quoteNumber (single quote) or slug (all quotes for a customer).
5798
5800
  Returns quote with status: draft | sent | viewed | accepted | declined`,
@@ -5807,7 +5809,7 @@ Returns quote with status: draft | sent | viewed | accepted | declined`,
5807
5809
  }
5808
5810
  //#endregion
5809
5811
  //#region src/mcp/tools/get-booking-link.ts
5810
- const DATA_DIR$17 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5812
+ const DATA_DIR$19 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5811
5813
  function loadCalendlyConfig(dataDir) {
5812
5814
  const p = path.join(dataDir, ".agentic", "integrations", "calendly.yaml");
5813
5815
  if (!fs.existsSync(p)) return {};
@@ -5830,7 +5832,7 @@ function readCustomerFacts(dataDir, slug) {
5830
5832
  ...email ? { email } : {}
5831
5833
  };
5832
5834
  }
5833
- async function handleGetBookingLink(input, dataDir = DATA_DIR$17) {
5835
+ async function handleGetBookingLink(input, dataDir = DATA_DIR$19) {
5834
5836
  const config = loadCalendlyConfig(dataDir);
5835
5837
  const apiKey = config.apiKey ?? process.env["CALENDLY_API_KEY"] ?? "";
5836
5838
  if (!apiKey) return { content: [{
@@ -5858,7 +5860,7 @@ async function handleGetBookingLink(input, dataDir = DATA_DIR$17) {
5858
5860
  }] };
5859
5861
  }
5860
5862
  }
5861
- function registerGetBookingLink(server, dataDir = DATA_DIR$17) {
5863
+ function registerGetBookingLink(server, dataDir = DATA_DIR$19) {
5862
5864
  server.registerTool("get_booking_link", {
5863
5865
  description: `Get a Calendly booking link for a customer. Optionally pre-fills the customer's name/email.
5864
5866
  Requires CALENDLY_API_KEY env var or .agentic/integrations/calendly.yaml config.
@@ -6028,8 +6030,8 @@ function calcSlaDue(createdDate, priority, rules) {
6028
6030
  }
6029
6031
  //#endregion
6030
6032
  //#region src/mcp/tools/create-ticket.ts
6031
- const DATA_DIR$16 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6032
- async function handleCreateTicket(input, dataDir = DATA_DIR$16) {
6033
+ const DATA_DIR$18 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6034
+ async function handleCreateTicket(input, dataDir = DATA_DIR$18) {
6033
6035
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6034
6036
  const rules = loadSlaRules(dataDir);
6035
6037
  const priority = input.priority ?? "normal";
@@ -6051,7 +6053,7 @@ async function handleCreateTicket(input, dataDir = DATA_DIR$16) {
6051
6053
  text: JSON.stringify({ ticket }, null, 2)
6052
6054
  }] };
6053
6055
  }
6054
- function registerCreateTicket(server, dataDir = DATA_DIR$16) {
6056
+ function registerCreateTicket(server, dataDir = DATA_DIR$18) {
6055
6057
  server.registerTool("create_ticket", {
6056
6058
  description: `Create a support ticket for a customer. Auto-calculates SLA due date based on priority.
6057
6059
  Returns: { ticket } with id T-NNN, status=open, slaDue`,
@@ -6077,8 +6079,8 @@ Returns: { ticket } with id T-NNN, status=open, slaDue`,
6077
6079
  }
6078
6080
  //#endregion
6079
6081
  //#region src/mcp/tools/update-ticket.ts
6080
- const DATA_DIR$15 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6081
- async function handleUpdateTicket(input, dataDir = DATA_DIR$15) {
6082
+ const DATA_DIR$17 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6083
+ async function handleUpdateTicket(input, dataDir = DATA_DIR$17) {
6082
6084
  const ticket = (await readTickets(dataDir, input.slug)).find((t) => t.id === input.ticketId);
6083
6085
  if (!ticket) return { content: [{
6084
6086
  type: "text",
@@ -6097,7 +6099,7 @@ async function handleUpdateTicket(input, dataDir = DATA_DIR$15) {
6097
6099
  text: JSON.stringify({ ticket: updated }, null, 2)
6098
6100
  }] };
6099
6101
  }
6100
- function registerUpdateTicket(server, dataDir = DATA_DIR$15) {
6102
+ function registerUpdateTicket(server, dataDir = DATA_DIR$17) {
6101
6103
  server.registerTool("update_ticket", {
6102
6104
  description: `Update a ticket's status or assignee. Setting status=resolved auto-sets resolved date.
6103
6105
  Returns: { ticket }`,
@@ -6122,8 +6124,8 @@ Returns: { ticket }`,
6122
6124
  }
6123
6125
  //#endregion
6124
6126
  //#region src/mcp/tools/list-tickets.ts
6125
- const DATA_DIR$14 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6126
- async function handleListTickets(input, dataDir = DATA_DIR$14) {
6127
+ const DATA_DIR$16 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6128
+ async function handleListTickets(input, dataDir = DATA_DIR$16) {
6127
6129
  const results = await listAllTickets(dataDir, {
6128
6130
  ...input.slug !== void 0 ? { slug: input.slug } : {},
6129
6131
  ...input.status !== void 0 ? { status: input.status } : {},
@@ -6135,7 +6137,7 @@ async function handleListTickets(input, dataDir = DATA_DIR$14) {
6135
6137
  text: JSON.stringify({ tickets: results }, null, 2)
6136
6138
  }] };
6137
6139
  }
6138
- function registerListTickets(server, dataDir = DATA_DIR$14) {
6140
+ function registerListTickets(server, dataDir = DATA_DIR$16) {
6139
6141
  server.registerTool("list_tickets", {
6140
6142
  description: `List support tickets. Filter by customer, status, priority, or assignee. Sorted by priority then date.
6141
6143
  Returns: { tickets: Array<{ slug, ticket }> }`,
@@ -6165,8 +6167,8 @@ Returns: { tickets: Array<{ slug, ticket }> }`,
6165
6167
  }
6166
6168
  //#endregion
6167
6169
  //#region src/mcp/tools/close-ticket.ts
6168
- const DATA_DIR$13 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6169
- async function handleCloseTicket(input, dataDir = DATA_DIR$13) {
6170
+ const DATA_DIR$15 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6171
+ async function handleCloseTicket(input, dataDir = DATA_DIR$15) {
6170
6172
  const ticket = (await readTickets(dataDir, input.slug)).find((t) => t.id === input.ticketId);
6171
6173
  if (!ticket) return { content: [{
6172
6174
  type: "text",
@@ -6193,7 +6195,7 @@ async function handleCloseTicket(input, dataDir = DATA_DIR$13) {
6193
6195
  text: JSON.stringify({ ticket: updated }, null, 2)
6194
6196
  }] };
6195
6197
  }
6196
- function registerCloseTicket(server, dataDir = DATA_DIR$13) {
6198
+ function registerCloseTicket(server, dataDir = DATA_DIR$15) {
6197
6199
  server.registerTool("close_ticket", {
6198
6200
  description: `Close a support ticket. Optionally logs the resolution as an interaction.
6199
6201
  Returns: { ticket } with status=closed`,
@@ -6347,8 +6349,8 @@ async function savePendingSurvey(dataDir, surveyId, slug, contactEmail, token) {
6347
6349
  }
6348
6350
  //#endregion
6349
6351
  //#region src/mcp/tools/send-nps-survey.ts
6350
- const DATA_DIR$12 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6351
- async function handleSendNpsSurvey(input, dataDir = DATA_DIR$12) {
6352
+ const DATA_DIR$14 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6353
+ async function handleSendNpsSurvey(input, dataDir = DATA_DIR$14) {
6352
6354
  const survey = getSurvey(dataDir, input.surveyId);
6353
6355
  if (!survey) return { content: [{
6354
6356
  type: "text",
@@ -6369,7 +6371,7 @@ async function handleSendNpsSurvey(input, dataDir = DATA_DIR$12) {
6369
6371
  }, null, 2)
6370
6372
  }] };
6371
6373
  }
6372
- function registerSendNpsSurvey(server, dataDir = DATA_DIR$12) {
6374
+ function registerSendNpsSurvey(server, dataDir = DATA_DIR$14) {
6373
6375
  server.registerTool("send_nps_survey", {
6374
6376
  description: `Generate an NPS/CSAT survey email for a customer contact. Returns subject, HTML body, and a token-based response URL.
6375
6377
  Does NOT send automatically — returns draft for review.
@@ -6389,8 +6391,8 @@ Returns: { token, subject, body, surveyUrl }`,
6389
6391
  }
6390
6392
  //#endregion
6391
6393
  //#region src/mcp/tools/get-survey-results.ts
6392
- const DATA_DIR$11 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6393
- async function handleGetSurveyResults(input, dataDir = DATA_DIR$11) {
6394
+ const DATA_DIR$13 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6395
+ async function handleGetSurveyResults(input, dataDir = DATA_DIR$13) {
6394
6396
  const responses = loadSurveyResponses(dataDir, input.surveyId, input.slug);
6395
6397
  const nps = calcNpsScore(responses);
6396
6398
  const promoters = responses.filter((r) => r.score >= 9).length;
@@ -6416,7 +6418,7 @@ async function handleGetSurveyResults(input, dataDir = DATA_DIR$11) {
6416
6418
  }, null, 2)
6417
6419
  }] };
6418
6420
  }
6419
- function registerGetSurveyResults(server, dataDir = DATA_DIR$11) {
6421
+ function registerGetSurveyResults(server, dataDir = DATA_DIR$13) {
6420
6422
  server.registerTool("get_survey_results", {
6421
6423
  description: `Get NPS/CSAT survey results with score breakdown. Calculates Net Promoter Score.
6422
6424
  Returns: { npsScore, totalResponses, promoters, passives, detractors, responses[] }`,
@@ -6517,8 +6519,8 @@ function getKbMetaForExport(article) {
6517
6519
  }
6518
6520
  //#endregion
6519
6521
  //#region src/mcp/tools/search-knowledge-base.ts
6520
- const DATA_DIR$10 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6521
- async function handleSearchKnowledgeBase(input, dataDir = DATA_DIR$10) {
6522
+ const DATA_DIR$12 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6523
+ async function handleSearchKnowledgeBase(input, dataDir = DATA_DIR$12) {
6522
6524
  const results = searchKbSimple(dataDir, input.query, { ...input.publicOnly ? { publicOnly: true } : {} });
6523
6525
  const limited = (input.category ? results.filter((a) => a.category === input.category) : results).slice(0, input.limit ?? 10);
6524
6526
  return { content: [{
@@ -6533,7 +6535,7 @@ async function handleSearchKnowledgeBase(input, dataDir = DATA_DIR$10) {
6533
6535
  }, null, 2)
6534
6536
  }] };
6535
6537
  }
6536
- function registerSearchKnowledgeBase(server, dataDir = DATA_DIR$10) {
6538
+ function registerSearchKnowledgeBase(server, dataDir = DATA_DIR$12) {
6537
6539
  server.registerTool("search_knowledge_base", {
6538
6540
  description: `Search the knowledge base for articles. Text search on title, body, and tags.
6539
6541
  Returns: { count, articles[] } with excerpts`,
@@ -6552,8 +6554,8 @@ Returns: { count, articles[] } with excerpts`,
6552
6554
  }
6553
6555
  //#endregion
6554
6556
  //#region src/mcp/tools/create-kb-article.ts
6555
- const DATA_DIR$9 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6556
- async function handleCreateKbArticle(input, dataDir = DATA_DIR$9) {
6557
+ const DATA_DIR$11 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6558
+ async function handleCreateKbArticle(input, dataDir = DATA_DIR$11) {
6557
6559
  if (getKbArticle(dataDir, input.id)) return { content: [{
6558
6560
  type: "text",
6559
6561
  text: JSON.stringify({ error: `Article '${input.id}' already exists` })
@@ -6581,7 +6583,7 @@ async function handleCreateKbArticle(input, dataDir = DATA_DIR$9) {
6581
6583
  }, null, 2)
6582
6584
  }] };
6583
6585
  }
6584
- function registerCreateKbArticle(server, dataDir = DATA_DIR$9) {
6586
+ function registerCreateKbArticle(server, dataDir = DATA_DIR$11) {
6585
6587
  server.registerTool("create_kb_article", {
6586
6588
  description: `Create a new knowledge base article. Articles are stored as Markdown files in .agentic/knowledge-base/.
6587
6589
  Returns: { id, title, category, path }`,
@@ -6606,8 +6608,8 @@ Returns: { id, title, category, path }`,
6606
6608
  }
6607
6609
  //#endregion
6608
6610
  //#region src/mcp/tools/backup-now.ts
6609
- const DATA_DIR$8 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6610
- async function handleBackupNow(input, dataDir = DATA_DIR$8) {
6611
+ const DATA_DIR$10 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6612
+ async function handleBackupNow(input, dataDir = DATA_DIR$10) {
6611
6613
  const zipPath = path.join(dataDir, `dxcrm-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.zip`);
6612
6614
  const manifest = await runBackup(zipPath, dataDir, { ...input.remote ? { remote: input.remote } : {} }).catch(() => null);
6613
6615
  if (!manifest) return { content: [{
@@ -6644,8 +6646,8 @@ function registerBackupNow(server) {
6644
6646
  }
6645
6647
  //#endregion
6646
6648
  //#region src/mcp/tools/list-backups.ts
6647
- const DATA_DIR$7 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6648
- async function handleListBackups(input, dataDir = DATA_DIR$7) {
6649
+ const DATA_DIR$9 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6650
+ async function handleListBackups(input, dataDir = DATA_DIR$9) {
6649
6651
  const logEntries = readBackupLog(dataDir);
6650
6652
  const fileEntries = listBackupsInDir(dataDir);
6651
6653
  const entries = logEntries.length > 0 ? logEntries : fileEntries;
@@ -6679,8 +6681,8 @@ function registerListBackups(server) {
6679
6681
  }
6680
6682
  //#endregion
6681
6683
  //#region src/mcp/tools/trigger-sync.ts
6682
- const DATA_DIR$6 = process.cwd();
6683
- async function handleTriggerSync(input, dataDir = DATA_DIR$6) {
6684
+ const DATA_DIR$8 = process.cwd();
6685
+ async function handleTriggerSync(input, dataDir = DATA_DIR$8) {
6684
6686
  const auth = getGmailAuth();
6685
6687
  if (!auth) return { content: [{
6686
6688
  type: "text",
@@ -6774,8 +6776,8 @@ Returns: { success: boolean, synced: number, skipped: number, customers: [...],
6774
6776
  }
6775
6777
  //#endregion
6776
6778
  //#region src/mcp/tools/get-audit-log.ts
6777
- const DATA_DIR$5 = process.cwd();
6778
- async function handleGetAuditLog(input, dataDir = DATA_DIR$5) {
6779
+ const DATA_DIR$7 = process.cwd();
6780
+ async function handleGetAuditLog(input, dataDir = DATA_DIR$7) {
6779
6781
  const entries = readAuditLog(dataDir);
6780
6782
  const filterOpts = { limit: input.limit ?? 50 };
6781
6783
  if (input.slug !== void 0) filterOpts.slug = input.slug;
@@ -6817,8 +6819,8 @@ Returns: { total: number, returned: number, entries: [{timestamp, actor, tool, s
6817
6819
  }
6818
6820
  //#endregion
6819
6821
  //#region src/mcp/tools/get-logs.ts
6820
- const DATA_DIR$4 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6821
- async function handleGetLogs(input, dataDir = DATA_DIR$4) {
6822
+ const DATA_DIR$6 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6823
+ async function handleGetLogs(input, dataDir = DATA_DIR$6) {
6822
6824
  const query = {
6823
6825
  ...input.level !== void 0 ? { level: input.level } : {},
6824
6826
  ...input.component !== void 0 ? { component: input.component } : {},
@@ -6980,8 +6982,8 @@ async function runDiagnostics(dataDir) {
6980
6982
  }
6981
6983
  //#endregion
6982
6984
  //#region src/mcp/tools/get-diagnostics.ts
6983
- const DATA_DIR$3 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6984
- async function handleGetDiagnostics(input, dataDir = DATA_DIR$3) {
6985
+ const DATA_DIR$5 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6986
+ async function handleGetDiagnostics(input, dataDir = DATA_DIR$5) {
6985
6987
  let cleaned = 0;
6986
6988
  if (input.fix) {
6987
6989
  const { cleanupTempFiles } = await Promise.resolve().then(() => doctor_exports);
@@ -7020,14 +7022,14 @@ function snapshotsDir(dataDir) {
7020
7022
  function snapshotPath(dataDir, id) {
7021
7023
  return path.join(snapshotsDir(dataDir), `${id}.json`);
7022
7024
  }
7023
- function dealKey(d) {
7025
+ function dealKey$1(d) {
7024
7026
  return `${d.slug}::${d.name}`;
7025
7027
  }
7026
- function isOpen(stage) {
7028
+ function isOpen$1(stage) {
7027
7029
  return stage !== "won" && stage !== "lost";
7028
7030
  }
7029
7031
  function openValue(deals) {
7030
- return deals.filter((d) => isOpen(d.stage)).reduce((sum, d) => sum + d.value, 0);
7032
+ return deals.filter((d) => isOpen$1(d.stage)).reduce((sum, d) => sum + d.value, 0);
7031
7033
  }
7032
7034
  /** Build a live snapshot of the current pipeline across all customers. */
7033
7035
  function collectDeals(dataDir) {
@@ -7049,6 +7051,18 @@ function snapshotIds(dataDir) {
7049
7051
  function loadSnapshot(dataDir, id) {
7050
7052
  return readJsonFile(snapshotPath(dataDir, id), null);
7051
7053
  }
7054
+ function listSnapshots(dataDir) {
7055
+ return snapshotIds(dataDir).flatMap((id) => {
7056
+ const snap = loadSnapshot(dataDir, id);
7057
+ if (!snap) return [];
7058
+ return [{
7059
+ id,
7060
+ takenAt: snap.takenAt,
7061
+ dealCount: snap.deals.length,
7062
+ openValue: openValue(snap.deals)
7063
+ }];
7064
+ });
7065
+ }
7052
7066
  /** The most recent snapshot whose id is at or before `iso` (YYYY-MM-DD). */
7053
7067
  function latestSnapshotAtOrBefore(dataDir, iso) {
7054
7068
  const id = snapshotIds(dataDir).filter((s) => s <= iso).pop();
@@ -7056,8 +7070,8 @@ function latestSnapshotAtOrBefore(dataDir, iso) {
7056
7070
  }
7057
7071
  /** Compute what changed between two snapshots (before → after). */
7058
7072
  function diffSnapshots(before, after) {
7059
- const beforeByKey = new Map(before.deals.map((d) => [dealKey(d), d]));
7060
- const afterByKey = new Map(after.deals.map((d) => [dealKey(d), d]));
7073
+ const beforeByKey = new Map(before.deals.map((d) => [dealKey$1(d), d]));
7074
+ const afterByKey = new Map(after.deals.map((d) => [dealKey$1(d), d]));
7061
7075
  const added = [];
7062
7076
  const removed = [];
7063
7077
  const advanced = [];
@@ -7131,11 +7145,11 @@ function diffAgainstNow(dataDir, since, today = (/* @__PURE__ */ new Date()).toI
7131
7145
  }
7132
7146
  //#endregion
7133
7147
  //#region src/mcp/tools/get-pipeline-changes.ts
7134
- const DATA_DIR$2 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
7148
+ const DATA_DIR$4 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
7135
7149
  function daysAgoIso(days) {
7136
7150
  return (/* @__PURE__ */ new Date(Date.now() - days * 864e5)).toISOString().slice(0, 10);
7137
7151
  }
7138
- async function handleGetPipelineChanges(input, dataDir = DATA_DIR$2) {
7152
+ async function handleGetPipelineChanges(input, dataDir = DATA_DIR$4) {
7139
7153
  const since = input.since ?? daysAgoIso(input.days ?? 7);
7140
7154
  const diff = diffAgainstNow(dataDir, since);
7141
7155
  const payload = diff ? diff : { error: `No pipeline snapshot at or before ${since}. Snapshots accrue daily via the daemon.` };
@@ -7170,6 +7184,268 @@ or { error } when no baseline snapshot exists yet.`,
7170
7184
  });
7171
7185
  }
7172
7186
  //#endregion
7187
+ //#region src/core/velocity.ts
7188
+ const DEFAULT_STALLED_DAYS = 14;
7189
+ const OPEN_STAGES = [
7190
+ "lead",
7191
+ "qualified",
7192
+ "proposal",
7193
+ "negotiation"
7194
+ ];
7195
+ function dealKey(d) {
7196
+ return `${d.slug}::${d.name}`;
7197
+ }
7198
+ function isOpen(stage) {
7199
+ return stage !== "won" && stage !== "lost";
7200
+ }
7201
+ /** Whole days between two YYYY-MM-DD ids (b - a). */
7202
+ function daysBetween(a, b) {
7203
+ const ms = Date.parse(`${b}T00:00:00Z`) - Date.parse(`${a}T00:00:00Z`);
7204
+ return Math.round(ms / 864e5);
7205
+ }
7206
+ function stalledThreshold(opts) {
7207
+ if (opts?.stalledDays !== void 0) return opts.stalledDays;
7208
+ const n = parseInt(process.env["DXCRM_STALLED_DAYS"] ?? "", 10);
7209
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_STALLED_DAYS;
7210
+ }
7211
+ /** Per-deal chronological list of (date, stage) observations. */
7212
+ function buildTimelines(snaps) {
7213
+ const timelines = /* @__PURE__ */ new Map();
7214
+ for (const snap of snaps) for (const deal of snap.deals) {
7215
+ const key = dealKey(deal);
7216
+ const points = timelines.get(key) ?? [];
7217
+ points.push({
7218
+ date: snap.id,
7219
+ stage: deal.stage,
7220
+ deal
7221
+ });
7222
+ timelines.set(key, points);
7223
+ }
7224
+ return timelines;
7225
+ }
7226
+ function analyzeVelocity(dataDir, opts) {
7227
+ const threshold = stalledThreshold(opts);
7228
+ const metas = listSnapshots(dataDir);
7229
+ if (metas.length === 0) return {
7230
+ fromId: null,
7231
+ toId: null,
7232
+ snapshotCount: 0,
7233
+ stageDurations: [],
7234
+ avgSalesCycleDays: null,
7235
+ wonCount: 0,
7236
+ stalledDeals: [],
7237
+ stalledThresholdDays: threshold
7238
+ };
7239
+ const snaps = metas.flatMap((m) => {
7240
+ const s = loadSnapshot(dataDir, m.id);
7241
+ return s ? [s] : [];
7242
+ });
7243
+ const latestId = snaps[snaps.length - 1].id;
7244
+ const timelines = buildTimelines(snaps);
7245
+ const dwellTotals = /* @__PURE__ */ new Map();
7246
+ const cycleDurations = [];
7247
+ let wonCount = 0;
7248
+ const stalledDeals = [];
7249
+ for (const points of timelines.values()) {
7250
+ const firstSeen = points[0].date;
7251
+ let stageEnteredAt = points[0].date;
7252
+ let currentStage = points[0].stage;
7253
+ for (let i = 1; i < points.length; i++) {
7254
+ const p = points[i];
7255
+ if (p.stage !== currentStage) {
7256
+ const acc = dwellTotals.get(currentStage) ?? {
7257
+ total: 0,
7258
+ samples: 0
7259
+ };
7260
+ acc.total += daysBetween(stageEnteredAt, p.date);
7261
+ acc.samples += 1;
7262
+ dwellTotals.set(currentStage, acc);
7263
+ if (p.stage === "won") {
7264
+ wonCount += 1;
7265
+ cycleDurations.push(daysBetween(firstSeen, p.date));
7266
+ }
7267
+ currentStage = p.stage;
7268
+ stageEnteredAt = p.date;
7269
+ }
7270
+ }
7271
+ const last = points[points.length - 1];
7272
+ if (last.date === latestId && isOpen(last.stage)) {
7273
+ const daysInStage = daysBetween(stageEnteredAt, latestId);
7274
+ if (daysInStage > threshold) stalledDeals.push({
7275
+ slug: last.deal.slug,
7276
+ name: last.deal.name,
7277
+ stage: last.stage,
7278
+ daysInStage,
7279
+ value: last.deal.value
7280
+ });
7281
+ }
7282
+ }
7283
+ const stageDurations = OPEN_STAGES.flatMap((stage) => {
7284
+ const acc = dwellTotals.get(stage);
7285
+ if (!acc || acc.samples === 0) return [];
7286
+ return [{
7287
+ stage,
7288
+ avgDays: Math.round(acc.total / acc.samples),
7289
+ samples: acc.samples
7290
+ }];
7291
+ });
7292
+ const avgSalesCycleDays = cycleDurations.length > 0 ? Math.round(cycleDurations.reduce((a, b) => a + b, 0) / cycleDurations.length) : null;
7293
+ stalledDeals.sort((a, b) => b.daysInStage - a.daysInStage);
7294
+ return {
7295
+ fromId: snaps[0].id,
7296
+ toId: latestId,
7297
+ snapshotCount: snaps.length,
7298
+ stageDurations,
7299
+ avgSalesCycleDays,
7300
+ wonCount,
7301
+ stalledDeals,
7302
+ stalledThresholdDays: threshold
7303
+ };
7304
+ }
7305
+ //#endregion
7306
+ //#region src/mcp/tools/get-pipeline-velocity.ts
7307
+ const DATA_DIR$3 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
7308
+ async function handleGetPipelineVelocity(input, dataDir = DATA_DIR$3) {
7309
+ const opts = {};
7310
+ if (input.stalledDays !== void 0) opts.stalledDays = input.stalledDays;
7311
+ const report = analyzeVelocity(dataDir, opts);
7312
+ return { content: [{
7313
+ type: "text",
7314
+ text: JSON.stringify(report, null, 2)
7315
+ }] };
7316
+ }
7317
+ function registerGetPipelineVelocity(server) {
7318
+ server.registerTool("get_pipeline_velocity", {
7319
+ title: "Get Pipeline Velocity",
7320
+ description: `Pipeline velocity analytics from the daily snapshot history.
7321
+ Reconstructs each deal's stage journey to answer "where do deals get stuck?"
7322
+ and "which deals are rotting?".
7323
+
7324
+ Args:
7325
+ stalledDays: A deal open in the same stage longer than this is "stalled"
7326
+ (default 14).
7327
+
7328
+ Returns: { fromId, toId, snapshotCount, stageDurations[{stage,avgDays,samples}],
7329
+ avgSalesCycleDays, wonCount, stalledDeals[{slug,name,stage,daysInStage,value}],
7330
+ stalledThresholdDays }. snapshotCount is 0 until the daemon has taken snapshots.`,
7331
+ inputSchema: z.object({ stalledDays: z.number().int().min(1).max(365).optional().describe("Days in one stage before a deal counts as stalled (default 14)") })
7332
+ }, async ({ stalledDays }) => {
7333
+ const input = {};
7334
+ if (stalledDays !== void 0) input.stalledDays = stalledDays;
7335
+ return handleGetPipelineVelocity(input);
7336
+ });
7337
+ }
7338
+ //#endregion
7339
+ //#region src/core/funnel.ts
7340
+ const FUNNEL_STAGES = [
7341
+ "lead",
7342
+ "qualified",
7343
+ "proposal",
7344
+ "negotiation",
7345
+ "won"
7346
+ ];
7347
+ const STAGE_INDEX = Object.fromEntries(FUNNEL_STAGES.map((s, i) => [s, i]));
7348
+ function emptyReport(snapshotCount) {
7349
+ return {
7350
+ fromId: null,
7351
+ toId: null,
7352
+ snapshotCount,
7353
+ stages: [],
7354
+ wonCount: 0,
7355
+ lostCount: 0,
7356
+ winRatePct: null,
7357
+ biggestLeak: null
7358
+ };
7359
+ }
7360
+ function analyzeFunnel(dataDir) {
7361
+ const metas = listSnapshots(dataDir);
7362
+ if (metas.length === 0) return emptyReport(0);
7363
+ const snaps = metas.flatMap((m) => {
7364
+ const s = loadSnapshot(dataDir, m.id);
7365
+ return s ? [s] : [];
7366
+ });
7367
+ const progress = /* @__PURE__ */ new Map();
7368
+ for (const snap of snaps) for (const deal of snap.deals) {
7369
+ const key = `${deal.slug}::${deal.name}`;
7370
+ const p = progress.get(key) ?? {
7371
+ maxIndex: -1,
7372
+ won: false,
7373
+ lost: false
7374
+ };
7375
+ if (deal.stage === "lost") p.lost = true;
7376
+ else {
7377
+ const idx = STAGE_INDEX[deal.stage];
7378
+ if (idx !== void 0 && idx > p.maxIndex) p.maxIndex = idx;
7379
+ if (deal.stage === "won") p.won = true;
7380
+ }
7381
+ progress.set(key, p);
7382
+ }
7383
+ const reached = new Array(FUNNEL_STAGES.length).fill(0);
7384
+ let wonCount = 0;
7385
+ let lostCount = 0;
7386
+ for (const p of progress.values()) {
7387
+ for (let i = 0; i <= p.maxIndex; i++) reached[i] = (reached[i] ?? 0) + 1;
7388
+ if (p.won) wonCount += 1;
7389
+ else if (p.lost) lostCount += 1;
7390
+ }
7391
+ const stages = FUNNEL_STAGES.map((stage, i) => {
7392
+ const here = reached[i] ?? 0;
7393
+ const next = reached[i + 1];
7394
+ return {
7395
+ stage,
7396
+ reached: here,
7397
+ conversionPctToNext: next === void 0 || here === 0 ? null : Math.round(next / here * 100)
7398
+ };
7399
+ });
7400
+ let biggestLeak = null;
7401
+ for (let i = 0; i < FUNNEL_STAGES.length - 1; i++) {
7402
+ const conv = stages[i].conversionPctToNext;
7403
+ if (conv === null) continue;
7404
+ if (biggestLeak === null || conv < biggestLeak.conversionPct) biggestLeak = {
7405
+ from: FUNNEL_STAGES[i],
7406
+ to: FUNNEL_STAGES[i + 1],
7407
+ conversionPct: conv
7408
+ };
7409
+ }
7410
+ const closed = wonCount + lostCount;
7411
+ const winRatePct = closed > 0 ? Math.round(wonCount / closed * 100) : null;
7412
+ return {
7413
+ fromId: snaps[0].id,
7414
+ toId: snaps[snaps.length - 1].id,
7415
+ snapshotCount: snaps.length,
7416
+ stages,
7417
+ wonCount,
7418
+ lostCount,
7419
+ winRatePct,
7420
+ biggestLeak
7421
+ };
7422
+ }
7423
+ //#endregion
7424
+ //#region src/mcp/tools/get-pipeline-funnel.ts
7425
+ const DATA_DIR$2 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
7426
+ async function handleGetPipelineFunnel(_input, dataDir = DATA_DIR$2) {
7427
+ const report = analyzeFunnel(dataDir);
7428
+ return { content: [{
7429
+ type: "text",
7430
+ text: JSON.stringify(report, null, 2)
7431
+ }] };
7432
+ }
7433
+ function registerGetPipelineFunnel(server) {
7434
+ server.registerTool("get_pipeline_funnel", {
7435
+ title: "Get Pipeline Funnel",
7436
+ description: `Pipeline conversion funnel & win-rate from the daily snapshot history.
7437
+ For each deal it tracks the furthest stage reached and the win/lost outcome,
7438
+ then builds a cumulative funnel. Answers "where do deals leak out of my
7439
+ pipeline?" and "what's my win rate?".
7440
+
7441
+ Returns: { fromId, toId, snapshotCount,
7442
+ stages[{stage, reached, conversionPctToNext}], wonCount, lostCount, winRatePct,
7443
+ biggestLeak{from,to,conversionPct} }. snapshotCount is 0 until the daemon has
7444
+ taken snapshots.`,
7445
+ inputSchema: z.object({})
7446
+ }, async () => handleGetPipelineFunnel({}));
7447
+ }
7448
+ //#endregion
7173
7449
  //#region src/mcp/prompts.ts
7174
7450
  /**
7175
7451
  * CRM playbook prompts exposed via MCP `prompts/list` + `prompts/get`.
@@ -7588,6 +7864,8 @@ function createMcpServer() {
7588
7864
  registerGetLogs(server);
7589
7865
  registerGetDiagnostics(server);
7590
7866
  registerGetPipelineChanges(server);
7867
+ registerGetPipelineVelocity(server);
7868
+ registerGetPipelineFunnel(server);
7591
7869
  registerCustomObjectTools(server);
7592
7870
  registerPrompts(server);
7593
7871
  registerResources(server);