@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.cjs CHANGED
@@ -452,6 +452,8 @@ Config: \`.agentic/rbac.json\` | Actor: \`DXCRM_ACTOR\` env var
452
452
  | get_logs | Query/aggregate the structured application log (level, component, errors) | admin |
453
453
  | get_diagnostics | Self-diagnostic health check (data integrity, temp files, log errors, backups) | admin |
454
454
  | get_pipeline_changes | Pipeline time-travel: what changed (won/lost/moved/value) since a date | any |
455
+ | get_pipeline_velocity | Stage dwell times, sales cycle, and stalled deals from snapshot history | any |
456
+ | get_pipeline_funnel | Conversion funnel & win rate: where deals leak out of the pipeline | any |
455
457
  | define_custom_object | Define a runtime custom object type with typed fields (no migration) | admin |
456
458
  | create_record | Create a record of a custom object (validated against its schema) | rep+ |
457
459
  | list_records | List records of a custom object | any |
@@ -1430,7 +1432,7 @@ async function buildContext(dataDir, slug) {
1430
1432
  }
1431
1433
  //#endregion
1432
1434
  //#region src/mcp/tools/get-customer-context.ts
1433
- const DATA_DIR$54 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1435
+ const DATA_DIR$56 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1434
1436
  function triggerOnQuerySync(dataDir, slug) {
1435
1437
  const auth = getGmailAuth();
1436
1438
  if (!auth) return;
@@ -1451,7 +1453,7 @@ function triggerOnQuerySync(dataDir, slug) {
1451
1453
  }).then(() => updateSlugSyncState(dataDir, slug, { lastGmailSync: (/* @__PURE__ */ new Date()).toISOString() })).catch(() => {})).catch(() => {});
1452
1454
  } catch {}
1453
1455
  }
1454
- async function handleGetCustomerContext(input, dataDir = DATA_DIR$54) {
1456
+ async function handleGetCustomerContext(input, dataDir = DATA_DIR$56) {
1455
1457
  const targetSlug = input.slug ?? require_session_store.getSession()?.customerSlug;
1456
1458
  if (!targetSlug) return {
1457
1459
  content: [{
@@ -1587,8 +1589,8 @@ async function searchKnowledge(dataDir, slug, query, limit) {
1587
1589
  }
1588
1590
  //#endregion
1589
1591
  //#region src/mcp/tools/search-customer-knowledge.ts
1590
- const DATA_DIR$53 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1591
- async function handleSearchCustomerKnowledge(input, dataDir = DATA_DIR$53) {
1592
+ const DATA_DIR$55 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1593
+ async function handleSearchCustomerKnowledge(input, dataDir = DATA_DIR$55) {
1592
1594
  const limit = input.limit ?? 5;
1593
1595
  try {
1594
1596
  const results = await searchKnowledge(dataDir, input.slug, input.query, limit);
@@ -1636,14 +1638,14 @@ If no results: returns empty array with a helpful sync suggestion.`,
1636
1638
  }
1637
1639
  //#endregion
1638
1640
  //#region src/mcp/tools/list-customers.ts
1639
- const DATA_DIR$52 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1641
+ const DATA_DIR$54 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
1640
1642
  function extractLastInteractionDate(interactionsPath) {
1641
1643
  if (!fs.default.existsSync(interactionsPath)) return void 0;
1642
1644
  const content = fs.default.readFileSync(interactionsPath, "utf-8");
1643
1645
  const match = /^## (\d{4}-\d{2}-\d{2})/m.exec(content);
1644
1646
  return match ? match[1] : void 0;
1645
1647
  }
1646
- async function handleListCustomers(input, dataDir = DATA_DIR$52) {
1648
+ async function handleListCustomers(input, dataDir = DATA_DIR$54) {
1647
1649
  const customersDir = path.default.join(dataDir, "customers");
1648
1650
  const customers = [];
1649
1651
  if (!fs.default.existsSync(customersDir)) return { content: [{
@@ -2156,8 +2158,8 @@ async function updateHealthFromInteraction(dataDir, slug) {
2156
2158
  }
2157
2159
  //#endregion
2158
2160
  //#region src/mcp/tools/log-interaction.ts
2159
- const DATA_DIR$51 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2160
- async function handleLogInteraction(input, dataDir = DATA_DIR$51) {
2161
+ const DATA_DIR$53 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2162
+ async function handleLogInteraction(input, dataDir = DATA_DIR$53) {
2161
2163
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2162
2164
  const interactionDate = input.date ?? today;
2163
2165
  const sourceRef = input.source ?? `agent://log/${Date.now()}`;
@@ -2266,8 +2268,8 @@ var update_deal_exports = /* @__PURE__ */ require_chunk.__exportAll({
2266
2268
  handleUpdateDeal: () => handleUpdateDeal,
2267
2269
  registerUpdateDeal: () => registerUpdateDeal
2268
2270
  });
2269
- const DATA_DIR$50 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2270
- async function handleUpdateDeal(input, dataDir = DATA_DIR$50) {
2271
+ const DATA_DIR$52 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2272
+ async function handleUpdateDeal(input, dataDir = DATA_DIR$52) {
2271
2273
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2272
2274
  const deal = {
2273
2275
  name: input.dealName,
@@ -2350,12 +2352,12 @@ Returns: { success: boolean, deal: object }`,
2350
2352
  }
2351
2353
  //#endregion
2352
2354
  //#region src/mcp/tools/export-customer.ts
2353
- const DATA_DIR$49 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2355
+ const DATA_DIR$51 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2354
2356
  function countInteractions(content) {
2355
2357
  const matches = content.match(/^## \d{4}-\d{2}-\d{2}/gm);
2356
2358
  return matches ? matches.length : 0;
2357
2359
  }
2358
- async function handleExportCustomer(input, dataDir = DATA_DIR$49) {
2360
+ async function handleExportCustomer(input, dataDir = DATA_DIR$51) {
2359
2361
  require_session_store.enforceRbac(dataDir, "export_customer");
2360
2362
  const customerDir = path.default.join(dataDir, "customers", input.slug);
2361
2363
  if (!fs.default.existsSync(customerDir)) return {
@@ -2468,8 +2470,8 @@ Returns:
2468
2470
  }
2469
2471
  //#endregion
2470
2472
  //#region src/mcp/tools/update-customer-facts.ts
2471
- const DATA_DIR$48 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2472
- async function handleUpdateCustomerFacts(input, dataDir = DATA_DIR$48) {
2473
+ const DATA_DIR$50 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2474
+ async function handleUpdateCustomerFacts(input, dataDir = DATA_DIR$50) {
2473
2475
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2474
2476
  try {
2475
2477
  require_session_store.enforceRbac(dataDir, "update_customer_facts");
@@ -2647,8 +2649,8 @@ function scoreDealForToday(deal, todayDate) {
2647
2649
  }
2648
2650
  //#endregion
2649
2651
  //#region src/mcp/tools/get-deal-health.ts
2650
- const DATA_DIR$47 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2651
- async function handleGetDealHealth(input, dataDir = DATA_DIR$47) {
2652
+ const DATA_DIR$49 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2653
+ async function handleGetDealHealth(input, dataDir = DATA_DIR$49) {
2652
2654
  try {
2653
2655
  const deals = await require_pipeline_writer.readPipeline(dataDir, input.slug);
2654
2656
  const today = /* @__PURE__ */ new Date();
@@ -2697,8 +2699,8 @@ Returns: { slug, deals: [{ deal, stage, score, grade, signals, warnings }] }`,
2697
2699
  }
2698
2700
  //#endregion
2699
2701
  //#region src/mcp/tools/get-pipeline-forecast.ts
2700
- const DATA_DIR$46 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2701
- async function handleGetPipelineForecast(input, dataDir = DATA_DIR$46) {
2702
+ const DATA_DIR$48 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2703
+ async function handleGetPipelineForecast(input, dataDir = DATA_DIR$48) {
2702
2704
  try {
2703
2705
  const slugs = require_session_store.listCustomerSlugs(dataDir).filter((d) => !input.filter || d.includes(input.filter));
2704
2706
  const allDeals = [];
@@ -2759,8 +2761,8 @@ Returns: { deals: [...], totalWeightedValue: number, byStage: { stage: { count,
2759
2761
  }
2760
2762
  //#endregion
2761
2763
  //#region src/mcp/tools/summarize-meeting.ts
2762
- const DATA_DIR$45 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2763
- async function handleSummarizeMeeting(input, dataDir = DATA_DIR$45) {
2764
+ const DATA_DIR$47 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2765
+ async function handleSummarizeMeeting(input, dataDir = DATA_DIR$47) {
2764
2766
  try {
2765
2767
  let summary = input.transcript.slice(0, 400);
2766
2768
  let nextSteps = [];
@@ -2883,8 +2885,8 @@ function getPipelineStages(dataDir) {
2883
2885
  }
2884
2886
  //#endregion
2885
2887
  //#region src/mcp/tools/get-pipeline-stages.ts
2886
- const DATA_DIR$44 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2887
- async function handleGetPipelineStages(_input, dataDir = DATA_DIR$44) {
2888
+ const DATA_DIR$46 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2889
+ async function handleGetPipelineStages(_input, dataDir = DATA_DIR$46) {
2888
2890
  const stages = getPipelineStages(dataDir);
2889
2891
  return { content: [{
2890
2892
  type: "text",
@@ -2912,8 +2914,8 @@ async function searchAcrossCustomers(dataDir, query, limit = 5, excludeSlug) {
2912
2914
  }
2913
2915
  //#endregion
2914
2916
  //#region src/mcp/tools/get-market-intelligence.ts
2915
- const DATA_DIR$43 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2916
- async function handleGetMarketIntelligence(input, dataDir = DATA_DIR$43) {
2917
+ const DATA_DIR$45 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2918
+ async function handleGetMarketIntelligence(input, dataDir = DATA_DIR$45) {
2917
2919
  const excludeSlug = input.excludeCurrentCustomer ? input.slug : void 0;
2918
2920
  const all = require_session_store.listCustomerSlugs(dataDir);
2919
2921
  const totalCustomersSearched = excludeSlug ? all.filter((s) => s !== excludeSlug).length : all.length;
@@ -2944,7 +2946,7 @@ function registerGetMarketIntelligence(server) {
2944
2946
  }
2945
2947
  //#endregion
2946
2948
  //#region src/mcp/tools/get-relationship-graph.ts
2947
- const DATA_DIR$42 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2949
+ const DATA_DIR$44 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
2948
2950
  function summarizeNode(n) {
2949
2951
  return {
2950
2952
  id: n.id,
@@ -2952,7 +2954,7 @@ function summarizeNode(n) {
2952
2954
  email: n.properties["email"]
2953
2955
  };
2954
2956
  }
2955
- async function handleGetRelationshipGraph(input, dataDir = DATA_DIR$42) {
2957
+ async function handleGetRelationshipGraph(input, dataDir = DATA_DIR$44) {
2956
2958
  try {
2957
2959
  const graph = readGraph(dataDir, input.slug);
2958
2960
  const stakeholders = getStakeholders(graph);
@@ -3020,9 +3022,9 @@ Returns: {
3020
3022
  }
3021
3023
  //#endregion
3022
3024
  //#region src/mcp/tools/get-relationship-health.ts
3023
- const DATA_DIR$41 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3025
+ const DATA_DIR$43 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3024
3026
  const MAX_HEALTH_AGE_MS = 3600 * 1e3;
3025
- async function handleGetRelationshipHealth(input, dataDir = DATA_DIR$41) {
3027
+ async function handleGetRelationshipHealth(input, dataDir = DATA_DIR$43) {
3026
3028
  try {
3027
3029
  let health = readHealth(dataDir, input.slug);
3028
3030
  if (health === null || Date.now() - new Date(health.updatedAt).getTime() > MAX_HEALTH_AGE_MS) {
@@ -3691,8 +3693,8 @@ async function runDealAgent(config, dataDir, llmFn = require_llm.callLlm) {
3691
3693
  }
3692
3694
  //#endregion
3693
3695
  //#region src/mcp/tools/run-deal-agent.ts
3694
- const DATA_DIR$40 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3695
- async function handleRunDealAgent(input, dataDir = DATA_DIR$40) {
3696
+ const DATA_DIR$42 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3697
+ async function handleRunDealAgent(input, dataDir = DATA_DIR$42) {
3696
3698
  try {
3697
3699
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3698
3700
  const result = await runDealAgent({
@@ -3759,8 +3761,8 @@ Returns: { assessment, riskLevel, plan[], actionsQueued[], actionsExecuted[], tr
3759
3761
  }
3760
3762
  //#endregion
3761
3763
  //#region src/mcp/tools/approve-agent-action.ts
3762
- const DATA_DIR$39 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3763
- async function handleApproveAgentAction(input, dataDir = DATA_DIR$39) {
3764
+ const DATA_DIR$41 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
3765
+ async function handleApproveAgentAction(input, dataDir = DATA_DIR$41) {
3764
3766
  try {
3765
3767
  const queue = readAgentQueue(dataDir, input.slug);
3766
3768
  const idx = queue.pendingActions.findIndex((a) => a.actionId === input.actionId);
@@ -4020,8 +4022,8 @@ async function buildSimulationInput(dataDir, horizon, today, externalSignals = [
4020
4022
  }
4021
4023
  //#endregion
4022
4024
  //#region src/mcp/tools/simulate-revenue.ts
4023
- const DATA_DIR$38 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4024
- async function handleSimulateRevenue(input, dataDir = DATA_DIR$38) {
4025
+ const DATA_DIR$40 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4026
+ async function handleSimulateRevenue(input, dataDir = DATA_DIR$40) {
4025
4027
  try {
4026
4028
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4027
4029
  const horizon = input.horizon ?? "quarter";
@@ -4079,8 +4081,8 @@ Returns: { forecast: { p10, p50, p90, expected, stdDev, atRiskRevenue, byCloseMo
4079
4081
  }
4080
4082
  //#endregion
4081
4083
  //#region src/mcp/tools/get-playbook.ts
4082
- const DATA_DIR$37 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4083
- async function handleGetPlaybook(input, dataDir = DATA_DIR$37) {
4084
+ const DATA_DIR$39 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4085
+ async function handleGetPlaybook(input, dataDir = DATA_DIR$39) {
4084
4086
  try {
4085
4087
  const playbooks = listPlaybooks(dataDir, input.slug);
4086
4088
  if (!(input.stage !== void 0 || input.value !== void 0 || input.healthScore !== void 0)) return { content: [{
@@ -4165,12 +4167,12 @@ Returns: { matches: [{ name, score, trigger, successRate, usedCount, content }],
4165
4167
  ...healthScore !== void 0 ? { healthScore } : {},
4166
4168
  ...daysSinceContact !== void 0 ? { daysSinceContact } : {},
4167
4169
  ...championPresent !== void 0 ? { championPresent } : {}
4168
- }, DATA_DIR$37));
4170
+ }, DATA_DIR$39));
4169
4171
  }
4170
4172
  //#endregion
4171
4173
  //#region src/mcp/tools/create-playbook.ts
4172
- const DATA_DIR$36 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4173
- async function handleCreatePlaybook(input, dataDir = DATA_DIR$36) {
4174
+ const DATA_DIR$38 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4175
+ async function handleCreatePlaybook(input, dataDir = DATA_DIR$38) {
4174
4176
  try {
4175
4177
  const name = toKebabCase(input.name);
4176
4178
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -4243,12 +4245,12 @@ Returns: { success: true, playbook: { name, trigger, successRate, path } }`,
4243
4245
  trigger,
4244
4246
  content,
4245
4247
  ...successRate !== void 0 ? { successRate } : {}
4246
- }, DATA_DIR$36));
4248
+ }, DATA_DIR$38));
4247
4249
  }
4248
4250
  //#endregion
4249
4251
  //#region src/mcp/tools/list-playbooks.ts
4250
- const DATA_DIR$35 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4251
- async function handleListPlaybooks(input, dataDir = DATA_DIR$35) {
4252
+ const DATA_DIR$37 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4253
+ async function handleListPlaybooks(input, dataDir = DATA_DIR$37) {
4252
4254
  try {
4253
4255
  const playbooks = listPlaybooks(dataDir, input.slug);
4254
4256
  return { content: [{
@@ -4287,12 +4289,12 @@ Args:
4287
4289
 
4288
4290
  Returns: { playbooks: [{ name, trigger, successRate, usedCount, lastUpdated }], count, slug }`,
4289
4291
  inputSchema: zod.z.object({ slug: zod.z.string().describe("Customer ID") })
4290
- }, async ({ slug }) => handleListPlaybooks({ slug }, DATA_DIR$35));
4292
+ }, async ({ slug }) => handleListPlaybooks({ slug }, DATA_DIR$37));
4291
4293
  }
4292
4294
  //#endregion
4293
4295
  //#region src/mcp/tools/distill-playbook.ts
4294
- const DATA_DIR$34 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4295
- async function handleDistillPlaybook(input, dataDir = DATA_DIR$34, llmFn = require_llm.callLlm) {
4296
+ const DATA_DIR$36 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4297
+ async function handleDistillPlaybook(input, dataDir = DATA_DIR$36, llmFn = require_llm.callLlm) {
4296
4298
  try {
4297
4299
  const result = await distillPlaybook(dataDir, input.slug, input.dealName, input.outcome, llmFn);
4298
4300
  if (!result.ok) {
@@ -4351,7 +4353,7 @@ Returns: { success: true, playbook: { name, trigger, successRate, path }, reason
4351
4353
  slug,
4352
4354
  dealName,
4353
4355
  outcome
4354
- }, DATA_DIR$34));
4356
+ }, DATA_DIR$36));
4355
4357
  }
4356
4358
  //#endregion
4357
4359
  //#region src/core/goal-engine.ts
@@ -4569,8 +4571,8 @@ function getActiveGoals(dataDir) {
4569
4571
  }
4570
4572
  //#endregion
4571
4573
  //#region src/mcp/tools/pursue-goal.ts
4572
- const DATA_DIR$33 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4573
- async function handlePursueGoal(input, dataDir = DATA_DIR$33, options = {}) {
4574
+ const DATA_DIR$35 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4575
+ async function handlePursueGoal(input, dataDir = DATA_DIR$35, options = {}) {
4574
4576
  try {
4575
4577
  require_session_store.enforceRbac(dataDir, "pursue_goal");
4576
4578
  const goal = await pursueGoal(dataDir, {
@@ -4633,12 +4635,12 @@ Returns: { goalId, description, target, deadline, decomposition: { analysis, cur
4633
4635
  goal,
4634
4636
  deadline,
4635
4637
  ...context !== void 0 ? { context } : {}
4636
- }, DATA_DIR$33));
4638
+ }, DATA_DIR$35));
4637
4639
  }
4638
4640
  //#endregion
4639
4641
  //#region src/mcp/tools/get-goal-status.ts
4640
- const DATA_DIR$32 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4641
- async function handleGetGoalStatus(input, dataDir = DATA_DIR$32) {
4642
+ const DATA_DIR$34 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4643
+ async function handleGetGoalStatus(input, dataDir = DATA_DIR$34) {
4642
4644
  try {
4643
4645
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4644
4646
  const allGoals = input.goalId ? readGoals(dataDir).filter((g) => g.id === input.goalId) : getActiveGoals(dataDir);
@@ -4697,17 +4699,17 @@ Args:
4697
4699
 
4698
4700
  Returns: { goals: [{ id, description, target, progress, status, deadline, daysRemaining, subGoals }], activeCount, completedCount }`,
4699
4701
  inputSchema: zod.z.object({ goalId: zod.z.string().optional().describe("Specific goal ID (omit for all active goals)") })
4700
- }, async ({ goalId }) => handleGetGoalStatus({ ...goalId !== void 0 ? { goalId } : {} }, DATA_DIR$32));
4702
+ }, async ({ goalId }) => handleGetGoalStatus({ ...goalId !== void 0 ? { goalId } : {} }, DATA_DIR$34));
4701
4703
  }
4702
4704
  //#endregion
4703
4705
  //#region src/mcp/tools/register-push-subscription.ts
4704
- const DATA_DIR$31 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4706
+ const DATA_DIR$33 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4705
4707
  const VALID_PROVIDERS = [
4706
4708
  "gmail",
4707
4709
  "microsoft-graph",
4708
4710
  "slack"
4709
4711
  ];
4710
- async function handleRegisterPushSubscription(input, dataDir = DATA_DIR$31) {
4712
+ async function handleRegisterPushSubscription(input, dataDir = DATA_DIR$33) {
4711
4713
  try {
4712
4714
  if (!VALID_PROVIDERS.includes(input.provider)) return { content: [{
4713
4715
  type: "text",
@@ -4793,12 +4795,12 @@ Returns: { subscriptionId, provider, slug, status, expiresAt, createdAt, warning
4793
4795
  ...microsoftResource !== void 0 ? { microsoftResource } : {},
4794
4796
  ...slackTeamId !== void 0 ? { slackTeamId } : {},
4795
4797
  ...slackChannelId !== void 0 ? { slackChannelId } : {}
4796
- }, DATA_DIR$31));
4798
+ }, DATA_DIR$33));
4797
4799
  }
4798
4800
  //#endregion
4799
4801
  //#region src/mcp/tools/get-push-status.ts
4800
- const DATA_DIR$30 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4801
- async function handleGetPushStatus(input, dataDir = DATA_DIR$30) {
4802
+ const DATA_DIR$32 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4803
+ async function handleGetPushStatus(input, dataDir = DATA_DIR$32) {
4802
4804
  try {
4803
4805
  let subs = await readSubscriptions(dataDir);
4804
4806
  if (input.slug) subs = subs.filter((s) => s.slug === input.slug);
@@ -4870,7 +4872,7 @@ Returns: { subscriptions: [{ id, provider, slug, status, expiresAt, expiresInHou
4870
4872
  }, async ({ slug, provider }) => handleGetPushStatus({
4871
4873
  ...slug !== void 0 ? { slug } : {},
4872
4874
  ...provider !== void 0 ? { provider } : {}
4873
- }, DATA_DIR$30));
4875
+ }, DATA_DIR$32));
4874
4876
  }
4875
4877
  //#endregion
4876
4878
  //#region src/core/org-intelligence.ts
@@ -4936,8 +4938,8 @@ function deriveRecommendation(people, missingRoles) {
4936
4938
  }
4937
4939
  //#endregion
4938
4940
  //#region src/mcp/tools/get-org-intelligence.ts
4939
- const DATA_DIR$29 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4940
- async function handleGetOrgIntelligence(input, dataDir = DATA_DIR$29) {
4941
+ const DATA_DIR$31 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
4942
+ async function handleGetOrgIntelligence(input, dataDir = DATA_DIR$31) {
4941
4943
  try {
4942
4944
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4943
4945
  const map = buildStakeholderMap(dataDir, input.slug, today, input.dealName);
@@ -5070,8 +5072,8 @@ function buildExecutiveSummary(slug, dealName, stakeholders, overallHealth, sim,
5070
5072
  }
5071
5073
  //#endregion
5072
5074
  //#region src/mcp/tools/open-deal-room.ts
5073
- const DATA_DIR$28 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5074
- async function handleOpenDealRoom(input, dataDir = DATA_DIR$28) {
5075
+ const DATA_DIR$30 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5076
+ async function handleOpenDealRoom(input, dataDir = DATA_DIR$30) {
5075
5077
  try {
5076
5078
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5077
5079
  const brief = await buildDealRoom(dataDir, input.slug, input.dealName, today);
@@ -5154,8 +5156,8 @@ async function buildDailyBriefing(dataDir, today) {
5154
5156
  }
5155
5157
  //#endregion
5156
5158
  //#region src/mcp/tools/get-proactive-briefing.ts
5157
- const DATA_DIR$27 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5158
- async function handleGetProactiveBriefing(input, dataDir = DATA_DIR$27) {
5159
+ const DATA_DIR$29 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5160
+ async function handleGetProactiveBriefing(input, dataDir = DATA_DIR$29) {
5159
5161
  try {
5160
5162
  const briefing = await buildDailyBriefing(dataDir, input.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
5161
5163
  return { content: [{
@@ -5255,15 +5257,15 @@ function getTemplate(dataDir, id) {
5255
5257
  }
5256
5258
  //#endregion
5257
5259
  //#region src/mcp/tools/list-email-templates.ts
5258
- const DATA_DIR$26 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5259
- async function handleListEmailTemplates(input, dataDir = DATA_DIR$26) {
5260
+ const DATA_DIR$28 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5261
+ async function handleListEmailTemplates(input, dataDir = DATA_DIR$28) {
5260
5262
  const summary = listTemplates(dataDir, input.category ? { category: input.category } : {}).map(({ body: _body, ...meta }) => meta);
5261
5263
  return { content: [{
5262
5264
  type: "text",
5263
5265
  text: JSON.stringify(summary, null, 2)
5264
5266
  }] };
5265
5267
  }
5266
- function registerListEmailTemplates(server, dataDir = DATA_DIR$26) {
5268
+ function registerListEmailTemplates(server, dataDir = DATA_DIR$28) {
5267
5269
  server.registerTool("list_email_templates", {
5268
5270
  description: "List available email templates. Optionally filter by category (e.g. 'outreach', 'followup', 'support').",
5269
5271
  inputSchema: zod.z.object({ category: zod.z.string().optional().describe("Filter by category") })
@@ -5297,8 +5299,8 @@ async function buildVariablesFromCustomer(dataDir, slug) {
5297
5299
  }
5298
5300
  //#endregion
5299
5301
  //#region src/mcp/tools/get-email-template.ts
5300
- const DATA_DIR$25 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5301
- async function handleGetEmailTemplate(input, dataDir = DATA_DIR$25) {
5302
+ const DATA_DIR$27 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5303
+ async function handleGetEmailTemplate(input, dataDir = DATA_DIR$27) {
5302
5304
  const tmpl = getTemplate(dataDir, input.id);
5303
5305
  if (!tmpl) return { content: [{
5304
5306
  type: "text",
@@ -5314,7 +5316,7 @@ async function handleGetEmailTemplate(input, dataDir = DATA_DIR$25) {
5314
5316
  }, null, 2)
5315
5317
  }] };
5316
5318
  }
5317
- function registerGetEmailTemplate(server, dataDir = DATA_DIR$25) {
5319
+ function registerGetEmailTemplate(server, dataDir = DATA_DIR$27) {
5318
5320
  server.registerTool("get_email_template", {
5319
5321
  description: "Get a specific email template by ID, including its body and detected variables.",
5320
5322
  inputSchema: zod.z.object({ id: zod.z.string().describe("Template ID (e.g. 'enterprise-intro')") })
@@ -5322,8 +5324,8 @@ function registerGetEmailTemplate(server, dataDir = DATA_DIR$25) {
5322
5324
  }
5323
5325
  //#endregion
5324
5326
  //#region src/mcp/tools/draft-email.ts
5325
- const DATA_DIR$24 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5326
- async function handleDraftEmail(input, dataDir = DATA_DIR$24) {
5327
+ const DATA_DIR$26 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5328
+ async function handleDraftEmail(input, dataDir = DATA_DIR$26) {
5327
5329
  const tmpl = getTemplate(dataDir, input.templateId);
5328
5330
  if (!tmpl) return { content: [{
5329
5331
  type: "text",
@@ -5367,7 +5369,7 @@ async function handleDraftEmail(input, dataDir = DATA_DIR$24) {
5367
5369
  }, null, 2)
5368
5370
  }] };
5369
5371
  }
5370
- function registerDraftEmail(server, dataDir = DATA_DIR$24) {
5372
+ function registerDraftEmail(server, dataDir = DATA_DIR$26) {
5371
5373
  server.registerTool("draft_email", {
5372
5374
  description: `Draft a personalized email for a customer using a stored template.
5373
5375
  Variables are auto-filled from the customer's main_facts.md. Override any variable manually.
@@ -5475,8 +5477,8 @@ async function updateEnrollment(dataDir, id, updates) {
5475
5477
  }
5476
5478
  //#endregion
5477
5479
  //#region src/mcp/tools/enroll-in-sequence.ts
5478
- const DATA_DIR$23 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5479
- async function handleEnrollInSequence(input, dataDir = DATA_DIR$23) {
5480
+ const DATA_DIR$25 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5481
+ async function handleEnrollInSequence(input, dataDir = DATA_DIR$25) {
5480
5482
  const sequence = getSequence(dataDir, input.sequenceId);
5481
5483
  if (!sequence) return { content: [{
5482
5484
  type: "text",
@@ -5508,7 +5510,7 @@ async function handleEnrollInSequence(input, dataDir = DATA_DIR$23) {
5508
5510
  })
5509
5511
  }] };
5510
5512
  }
5511
- function registerEnrollInSequence(server, dataDir = DATA_DIR$23) {
5513
+ function registerEnrollInSequence(server, dataDir = DATA_DIR$25) {
5512
5514
  server.registerTool("enroll_in_sequence", {
5513
5515
  description: `Enroll a contact in an email sequence. Validates that the sequence and its first template exist.
5514
5516
  Returns: { enrollmentId, sequenceName, totalSteps }`,
@@ -5525,8 +5527,8 @@ Returns: { enrollmentId, sequenceName, totalSteps }`,
5525
5527
  }
5526
5528
  //#endregion
5527
5529
  //#region src/mcp/tools/list-sequence-enrollments.ts
5528
- const DATA_DIR$22 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5529
- async function handleListSequenceEnrollments(input, dataDir = DATA_DIR$22) {
5530
+ const DATA_DIR$24 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5531
+ async function handleListSequenceEnrollments(input, dataDir = DATA_DIR$24) {
5530
5532
  let enrollments = readEnrollments(dataDir);
5531
5533
  if (input.slug !== void 0) enrollments = enrollments.filter((e) => e.slug === input.slug);
5532
5534
  if (input.status !== void 0) enrollments = enrollments.filter((e) => e.status === input.status);
@@ -5535,7 +5537,7 @@ async function handleListSequenceEnrollments(input, dataDir = DATA_DIR$22) {
5535
5537
  text: JSON.stringify({ enrollments }, null, 2)
5536
5538
  }] };
5537
5539
  }
5538
- function registerListSequenceEnrollments(server, dataDir = DATA_DIR$22) {
5540
+ function registerListSequenceEnrollments(server, dataDir = DATA_DIR$24) {
5539
5541
  server.registerTool("list_sequence_enrollments", {
5540
5542
  description: `List email sequence enrollments. Filter by customer slug or status.
5541
5543
  Returns: { enrollments: SequenceEnrollment[] }`,
@@ -5554,8 +5556,8 @@ Returns: { enrollments: SequenceEnrollment[] }`,
5554
5556
  }
5555
5557
  //#endregion
5556
5558
  //#region src/mcp/tools/unenroll-from-sequence.ts
5557
- const DATA_DIR$21 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5558
- async function handleUnenrollFromSequence(input, dataDir = DATA_DIR$21) {
5559
+ const DATA_DIR$23 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5560
+ async function handleUnenrollFromSequence(input, dataDir = DATA_DIR$23) {
5559
5561
  if (!await updateEnrollment(dataDir, input.enrollmentId, { status: "paused" })) return { content: [{
5560
5562
  type: "text",
5561
5563
  text: JSON.stringify({
@@ -5568,7 +5570,7 @@ async function handleUnenrollFromSequence(input, dataDir = DATA_DIR$21) {
5568
5570
  text: JSON.stringify({ success: true })
5569
5571
  }] };
5570
5572
  }
5571
- function registerUnenrollFromSequence(server, dataDir = DATA_DIR$21) {
5573
+ function registerUnenrollFromSequence(server, dataDir = DATA_DIR$23) {
5572
5574
  server.registerTool("unenroll_from_sequence", {
5573
5575
  description: `Unenroll (pause) a contact from an email sequence. Sets status to "paused" (soft delete).
5574
5576
  Returns: { success: boolean }`,
@@ -5577,8 +5579,8 @@ Returns: { success: boolean }`,
5577
5579
  }
5578
5580
  //#endregion
5579
5581
  //#region src/mcp/tools/list-sequences.ts
5580
- const DATA_DIR$20 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5581
- async function handleListSequences(_input, dataDir = DATA_DIR$20) {
5582
+ const DATA_DIR$22 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5583
+ async function handleListSequences(_input, dataDir = DATA_DIR$22) {
5582
5584
  const sequences = listSequences(dataDir);
5583
5585
  const enrollments = readEnrollments(dataDir);
5584
5586
  const result = sequences.map((seq) => ({
@@ -5592,7 +5594,7 @@ async function handleListSequences(_input, dataDir = DATA_DIR$20) {
5592
5594
  text: JSON.stringify({ sequences: result }, null, 2)
5593
5595
  }] };
5594
5596
  }
5595
- function registerListSequences(server, dataDir = DATA_DIR$20) {
5597
+ function registerListSequences(server, dataDir = DATA_DIR$22) {
5596
5598
  server.registerTool("list_sequences", {
5597
5599
  description: `List all email sequences with step count and enrollment count.
5598
5600
  Returns: { sequences: Array<{ id, name, stepCount, enrollmentCount }> }`,
@@ -5727,8 +5729,8 @@ async function generateQuote(dataDir, input) {
5727
5729
  }
5728
5730
  //#endregion
5729
5731
  //#region src/mcp/tools/generate-quote.ts
5730
- const DATA_DIR$19 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5731
- async function handleGenerateQuote(input, dataDir = DATA_DIR$19) {
5732
+ const DATA_DIR$21 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5733
+ async function handleGenerateQuote(input, dataDir = DATA_DIR$21) {
5732
5734
  try {
5733
5735
  const quote = await generateQuote(dataDir, input);
5734
5736
  return { content: [{
@@ -5752,7 +5754,7 @@ async function handleGenerateQuote(input, dataDir = DATA_DIR$19) {
5752
5754
  }] };
5753
5755
  }
5754
5756
  }
5755
- function registerGenerateQuote(server, dataDir = DATA_DIR$19) {
5757
+ function registerGenerateQuote(server, dataDir = DATA_DIR$21) {
5756
5758
  server.registerTool("generate_quote", {
5757
5759
  description: `Generate a professional HTML quote/offer for a customer deal.
5758
5760
  Calculates subtotal, VAT, and total. Saves JSON + HTML to .agentic/quotes/.
@@ -5780,8 +5782,8 @@ Returns: { quoteNumber, htmlPath, total, currency, validUntil }`,
5780
5782
  }
5781
5783
  //#endregion
5782
5784
  //#region src/mcp/tools/get-quote-status.ts
5783
- const DATA_DIR$18 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5784
- async function handleGetQuoteStatus(input, dataDir = DATA_DIR$18) {
5785
+ const DATA_DIR$20 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5786
+ async function handleGetQuoteStatus(input, dataDir = DATA_DIR$20) {
5785
5787
  if (input.quoteNumber) {
5786
5788
  const quote = readQuote(dataDir, input.quoteNumber);
5787
5789
  if (!quote) return { content: [{
@@ -5799,7 +5801,7 @@ async function handleGetQuoteStatus(input, dataDir = DATA_DIR$18) {
5799
5801
  text: JSON.stringify({ quotes }, null, 2)
5800
5802
  }] };
5801
5803
  }
5802
- function registerGetQuoteStatus(server, dataDir = DATA_DIR$18) {
5804
+ function registerGetQuoteStatus(server, dataDir = DATA_DIR$20) {
5803
5805
  server.registerTool("get_quote_status", {
5804
5806
  description: `Get quote status and details. Filter by quoteNumber (single quote) or slug (all quotes for a customer).
5805
5807
  Returns quote with status: draft | sent | viewed | accepted | declined`,
@@ -5814,7 +5816,7 @@ Returns quote with status: draft | sent | viewed | accepted | declined`,
5814
5816
  }
5815
5817
  //#endregion
5816
5818
  //#region src/mcp/tools/get-booking-link.ts
5817
- const DATA_DIR$17 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5819
+ const DATA_DIR$19 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
5818
5820
  function loadCalendlyConfig(dataDir) {
5819
5821
  const p = path.default.join(dataDir, ".agentic", "integrations", "calendly.yaml");
5820
5822
  if (!fs.default.existsSync(p)) return {};
@@ -5837,7 +5839,7 @@ function readCustomerFacts(dataDir, slug) {
5837
5839
  ...email ? { email } : {}
5838
5840
  };
5839
5841
  }
5840
- async function handleGetBookingLink(input, dataDir = DATA_DIR$17) {
5842
+ async function handleGetBookingLink(input, dataDir = DATA_DIR$19) {
5841
5843
  const config = loadCalendlyConfig(dataDir);
5842
5844
  const apiKey = config.apiKey ?? process.env["CALENDLY_API_KEY"] ?? "";
5843
5845
  if (!apiKey) return { content: [{
@@ -5865,7 +5867,7 @@ async function handleGetBookingLink(input, dataDir = DATA_DIR$17) {
5865
5867
  }] };
5866
5868
  }
5867
5869
  }
5868
- function registerGetBookingLink(server, dataDir = DATA_DIR$17) {
5870
+ function registerGetBookingLink(server, dataDir = DATA_DIR$19) {
5869
5871
  server.registerTool("get_booking_link", {
5870
5872
  description: `Get a Calendly booking link for a customer. Optionally pre-fills the customer's name/email.
5871
5873
  Requires CALENDLY_API_KEY env var or .agentic/integrations/calendly.yaml config.
@@ -6035,8 +6037,8 @@ function calcSlaDue(createdDate, priority, rules) {
6035
6037
  }
6036
6038
  //#endregion
6037
6039
  //#region src/mcp/tools/create-ticket.ts
6038
- const DATA_DIR$16 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6039
- async function handleCreateTicket(input, dataDir = DATA_DIR$16) {
6040
+ const DATA_DIR$18 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6041
+ async function handleCreateTicket(input, dataDir = DATA_DIR$18) {
6040
6042
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6041
6043
  const rules = loadSlaRules(dataDir);
6042
6044
  const priority = input.priority ?? "normal";
@@ -6058,7 +6060,7 @@ async function handleCreateTicket(input, dataDir = DATA_DIR$16) {
6058
6060
  text: JSON.stringify({ ticket }, null, 2)
6059
6061
  }] };
6060
6062
  }
6061
- function registerCreateTicket(server, dataDir = DATA_DIR$16) {
6063
+ function registerCreateTicket(server, dataDir = DATA_DIR$18) {
6062
6064
  server.registerTool("create_ticket", {
6063
6065
  description: `Create a support ticket for a customer. Auto-calculates SLA due date based on priority.
6064
6066
  Returns: { ticket } with id T-NNN, status=open, slaDue`,
@@ -6084,8 +6086,8 @@ Returns: { ticket } with id T-NNN, status=open, slaDue`,
6084
6086
  }
6085
6087
  //#endregion
6086
6088
  //#region src/mcp/tools/update-ticket.ts
6087
- const DATA_DIR$15 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6088
- async function handleUpdateTicket(input, dataDir = DATA_DIR$15) {
6089
+ const DATA_DIR$17 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6090
+ async function handleUpdateTicket(input, dataDir = DATA_DIR$17) {
6089
6091
  const ticket = (await readTickets(dataDir, input.slug)).find((t) => t.id === input.ticketId);
6090
6092
  if (!ticket) return { content: [{
6091
6093
  type: "text",
@@ -6104,7 +6106,7 @@ async function handleUpdateTicket(input, dataDir = DATA_DIR$15) {
6104
6106
  text: JSON.stringify({ ticket: updated }, null, 2)
6105
6107
  }] };
6106
6108
  }
6107
- function registerUpdateTicket(server, dataDir = DATA_DIR$15) {
6109
+ function registerUpdateTicket(server, dataDir = DATA_DIR$17) {
6108
6110
  server.registerTool("update_ticket", {
6109
6111
  description: `Update a ticket's status or assignee. Setting status=resolved auto-sets resolved date.
6110
6112
  Returns: { ticket }`,
@@ -6129,8 +6131,8 @@ Returns: { ticket }`,
6129
6131
  }
6130
6132
  //#endregion
6131
6133
  //#region src/mcp/tools/list-tickets.ts
6132
- const DATA_DIR$14 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6133
- async function handleListTickets(input, dataDir = DATA_DIR$14) {
6134
+ const DATA_DIR$16 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6135
+ async function handleListTickets(input, dataDir = DATA_DIR$16) {
6134
6136
  const results = await listAllTickets(dataDir, {
6135
6137
  ...input.slug !== void 0 ? { slug: input.slug } : {},
6136
6138
  ...input.status !== void 0 ? { status: input.status } : {},
@@ -6142,7 +6144,7 @@ async function handleListTickets(input, dataDir = DATA_DIR$14) {
6142
6144
  text: JSON.stringify({ tickets: results }, null, 2)
6143
6145
  }] };
6144
6146
  }
6145
- function registerListTickets(server, dataDir = DATA_DIR$14) {
6147
+ function registerListTickets(server, dataDir = DATA_DIR$16) {
6146
6148
  server.registerTool("list_tickets", {
6147
6149
  description: `List support tickets. Filter by customer, status, priority, or assignee. Sorted by priority then date.
6148
6150
  Returns: { tickets: Array<{ slug, ticket }> }`,
@@ -6172,8 +6174,8 @@ Returns: { tickets: Array<{ slug, ticket }> }`,
6172
6174
  }
6173
6175
  //#endregion
6174
6176
  //#region src/mcp/tools/close-ticket.ts
6175
- const DATA_DIR$13 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6176
- async function handleCloseTicket(input, dataDir = DATA_DIR$13) {
6177
+ const DATA_DIR$15 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6178
+ async function handleCloseTicket(input, dataDir = DATA_DIR$15) {
6177
6179
  const ticket = (await readTickets(dataDir, input.slug)).find((t) => t.id === input.ticketId);
6178
6180
  if (!ticket) return { content: [{
6179
6181
  type: "text",
@@ -6200,7 +6202,7 @@ async function handleCloseTicket(input, dataDir = DATA_DIR$13) {
6200
6202
  text: JSON.stringify({ ticket: updated }, null, 2)
6201
6203
  }] };
6202
6204
  }
6203
- function registerCloseTicket(server, dataDir = DATA_DIR$13) {
6205
+ function registerCloseTicket(server, dataDir = DATA_DIR$15) {
6204
6206
  server.registerTool("close_ticket", {
6205
6207
  description: `Close a support ticket. Optionally logs the resolution as an interaction.
6206
6208
  Returns: { ticket } with status=closed`,
@@ -6354,8 +6356,8 @@ async function savePendingSurvey(dataDir, surveyId, slug, contactEmail, token) {
6354
6356
  }
6355
6357
  //#endregion
6356
6358
  //#region src/mcp/tools/send-nps-survey.ts
6357
- const DATA_DIR$12 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6358
- async function handleSendNpsSurvey(input, dataDir = DATA_DIR$12) {
6359
+ const DATA_DIR$14 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6360
+ async function handleSendNpsSurvey(input, dataDir = DATA_DIR$14) {
6359
6361
  const survey = getSurvey(dataDir, input.surveyId);
6360
6362
  if (!survey) return { content: [{
6361
6363
  type: "text",
@@ -6376,7 +6378,7 @@ async function handleSendNpsSurvey(input, dataDir = DATA_DIR$12) {
6376
6378
  }, null, 2)
6377
6379
  }] };
6378
6380
  }
6379
- function registerSendNpsSurvey(server, dataDir = DATA_DIR$12) {
6381
+ function registerSendNpsSurvey(server, dataDir = DATA_DIR$14) {
6380
6382
  server.registerTool("send_nps_survey", {
6381
6383
  description: `Generate an NPS/CSAT survey email for a customer contact. Returns subject, HTML body, and a token-based response URL.
6382
6384
  Does NOT send automatically — returns draft for review.
@@ -6396,8 +6398,8 @@ Returns: { token, subject, body, surveyUrl }`,
6396
6398
  }
6397
6399
  //#endregion
6398
6400
  //#region src/mcp/tools/get-survey-results.ts
6399
- const DATA_DIR$11 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6400
- async function handleGetSurveyResults(input, dataDir = DATA_DIR$11) {
6401
+ const DATA_DIR$13 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6402
+ async function handleGetSurveyResults(input, dataDir = DATA_DIR$13) {
6401
6403
  const responses = loadSurveyResponses(dataDir, input.surveyId, input.slug);
6402
6404
  const nps = calcNpsScore(responses);
6403
6405
  const promoters = responses.filter((r) => r.score >= 9).length;
@@ -6423,7 +6425,7 @@ async function handleGetSurveyResults(input, dataDir = DATA_DIR$11) {
6423
6425
  }, null, 2)
6424
6426
  }] };
6425
6427
  }
6426
- function registerGetSurveyResults(server, dataDir = DATA_DIR$11) {
6428
+ function registerGetSurveyResults(server, dataDir = DATA_DIR$13) {
6427
6429
  server.registerTool("get_survey_results", {
6428
6430
  description: `Get NPS/CSAT survey results with score breakdown. Calculates Net Promoter Score.
6429
6431
  Returns: { npsScore, totalResponses, promoters, passives, detractors, responses[] }`,
@@ -6524,8 +6526,8 @@ function getKbMetaForExport(article) {
6524
6526
  }
6525
6527
  //#endregion
6526
6528
  //#region src/mcp/tools/search-knowledge-base.ts
6527
- const DATA_DIR$10 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6528
- async function handleSearchKnowledgeBase(input, dataDir = DATA_DIR$10) {
6529
+ const DATA_DIR$12 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6530
+ async function handleSearchKnowledgeBase(input, dataDir = DATA_DIR$12) {
6529
6531
  const results = searchKbSimple(dataDir, input.query, { ...input.publicOnly ? { publicOnly: true } : {} });
6530
6532
  const limited = (input.category ? results.filter((a) => a.category === input.category) : results).slice(0, input.limit ?? 10);
6531
6533
  return { content: [{
@@ -6540,7 +6542,7 @@ async function handleSearchKnowledgeBase(input, dataDir = DATA_DIR$10) {
6540
6542
  }, null, 2)
6541
6543
  }] };
6542
6544
  }
6543
- function registerSearchKnowledgeBase(server, dataDir = DATA_DIR$10) {
6545
+ function registerSearchKnowledgeBase(server, dataDir = DATA_DIR$12) {
6544
6546
  server.registerTool("search_knowledge_base", {
6545
6547
  description: `Search the knowledge base for articles. Text search on title, body, and tags.
6546
6548
  Returns: { count, articles[] } with excerpts`,
@@ -6559,8 +6561,8 @@ Returns: { count, articles[] } with excerpts`,
6559
6561
  }
6560
6562
  //#endregion
6561
6563
  //#region src/mcp/tools/create-kb-article.ts
6562
- const DATA_DIR$9 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6563
- async function handleCreateKbArticle(input, dataDir = DATA_DIR$9) {
6564
+ const DATA_DIR$11 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6565
+ async function handleCreateKbArticle(input, dataDir = DATA_DIR$11) {
6564
6566
  if (getKbArticle(dataDir, input.id)) return { content: [{
6565
6567
  type: "text",
6566
6568
  text: JSON.stringify({ error: `Article '${input.id}' already exists` })
@@ -6588,7 +6590,7 @@ async function handleCreateKbArticle(input, dataDir = DATA_DIR$9) {
6588
6590
  }, null, 2)
6589
6591
  }] };
6590
6592
  }
6591
- function registerCreateKbArticle(server, dataDir = DATA_DIR$9) {
6593
+ function registerCreateKbArticle(server, dataDir = DATA_DIR$11) {
6592
6594
  server.registerTool("create_kb_article", {
6593
6595
  description: `Create a new knowledge base article. Articles are stored as Markdown files in .agentic/knowledge-base/.
6594
6596
  Returns: { id, title, category, path }`,
@@ -6613,8 +6615,8 @@ Returns: { id, title, category, path }`,
6613
6615
  }
6614
6616
  //#endregion
6615
6617
  //#region src/mcp/tools/backup-now.ts
6616
- const DATA_DIR$8 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6617
- async function handleBackupNow(input, dataDir = DATA_DIR$8) {
6618
+ const DATA_DIR$10 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6619
+ async function handleBackupNow(input, dataDir = DATA_DIR$10) {
6618
6620
  const zipPath = path.default.join(dataDir, `dxcrm-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.zip`);
6619
6621
  const manifest = await require_session_store.runBackup(zipPath, dataDir, { ...input.remote ? { remote: input.remote } : {} }).catch(() => null);
6620
6622
  if (!manifest) return { content: [{
@@ -6651,8 +6653,8 @@ function registerBackupNow(server) {
6651
6653
  }
6652
6654
  //#endregion
6653
6655
  //#region src/mcp/tools/list-backups.ts
6654
- const DATA_DIR$7 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6655
- async function handleListBackups(input, dataDir = DATA_DIR$7) {
6656
+ const DATA_DIR$9 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6657
+ async function handleListBackups(input, dataDir = DATA_DIR$9) {
6656
6658
  const logEntries = require_session_store.readBackupLog(dataDir);
6657
6659
  const fileEntries = require_session_store.listBackupsInDir(dataDir);
6658
6660
  const entries = logEntries.length > 0 ? logEntries : fileEntries;
@@ -6686,8 +6688,8 @@ function registerListBackups(server) {
6686
6688
  }
6687
6689
  //#endregion
6688
6690
  //#region src/mcp/tools/trigger-sync.ts
6689
- const DATA_DIR$6 = process.cwd();
6690
- async function handleTriggerSync(input, dataDir = DATA_DIR$6) {
6691
+ const DATA_DIR$8 = process.cwd();
6692
+ async function handleTriggerSync(input, dataDir = DATA_DIR$8) {
6691
6693
  const auth = getGmailAuth();
6692
6694
  if (!auth) return { content: [{
6693
6695
  type: "text",
@@ -6781,8 +6783,8 @@ Returns: { success: boolean, synced: number, skipped: number, customers: [...],
6781
6783
  }
6782
6784
  //#endregion
6783
6785
  //#region src/mcp/tools/get-audit-log.ts
6784
- const DATA_DIR$5 = process.cwd();
6785
- async function handleGetAuditLog(input, dataDir = DATA_DIR$5) {
6786
+ const DATA_DIR$7 = process.cwd();
6787
+ async function handleGetAuditLog(input, dataDir = DATA_DIR$7) {
6786
6788
  const entries = require_session_store.readAuditLog(dataDir);
6787
6789
  const filterOpts = { limit: input.limit ?? 50 };
6788
6790
  if (input.slug !== void 0) filterOpts.slug = input.slug;
@@ -6824,8 +6826,8 @@ Returns: { total: number, returned: number, entries: [{timestamp, actor, tool, s
6824
6826
  }
6825
6827
  //#endregion
6826
6828
  //#region src/mcp/tools/get-logs.ts
6827
- const DATA_DIR$4 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6828
- async function handleGetLogs(input, dataDir = DATA_DIR$4) {
6829
+ const DATA_DIR$6 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6830
+ async function handleGetLogs(input, dataDir = DATA_DIR$6) {
6829
6831
  const query = {
6830
6832
  ...input.level !== void 0 ? { level: input.level } : {},
6831
6833
  ...input.component !== void 0 ? { component: input.component } : {},
@@ -6987,8 +6989,8 @@ async function runDiagnostics(dataDir) {
6987
6989
  }
6988
6990
  //#endregion
6989
6991
  //#region src/mcp/tools/get-diagnostics.ts
6990
- const DATA_DIR$3 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6991
- async function handleGetDiagnostics(input, dataDir = DATA_DIR$3) {
6992
+ const DATA_DIR$5 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
6993
+ async function handleGetDiagnostics(input, dataDir = DATA_DIR$5) {
6992
6994
  let cleaned = 0;
6993
6995
  if (input.fix) {
6994
6996
  const { cleanupTempFiles } = await Promise.resolve().then(() => doctor_exports);
@@ -7027,14 +7029,14 @@ function snapshotsDir(dataDir) {
7027
7029
  function snapshotPath(dataDir, id) {
7028
7030
  return path.default.join(snapshotsDir(dataDir), `${id}.json`);
7029
7031
  }
7030
- function dealKey(d) {
7032
+ function dealKey$1(d) {
7031
7033
  return `${d.slug}::${d.name}`;
7032
7034
  }
7033
- function isOpen(stage) {
7035
+ function isOpen$1(stage) {
7034
7036
  return stage !== "won" && stage !== "lost";
7035
7037
  }
7036
7038
  function openValue(deals) {
7037
- return deals.filter((d) => isOpen(d.stage)).reduce((sum, d) => sum + d.value, 0);
7039
+ return deals.filter((d) => isOpen$1(d.stage)).reduce((sum, d) => sum + d.value, 0);
7038
7040
  }
7039
7041
  /** Build a live snapshot of the current pipeline across all customers. */
7040
7042
  function collectDeals(dataDir) {
@@ -7056,6 +7058,18 @@ function snapshotIds(dataDir) {
7056
7058
  function loadSnapshot(dataDir, id) {
7057
7059
  return require_session_store.readJsonFile(snapshotPath(dataDir, id), null);
7058
7060
  }
7061
+ function listSnapshots(dataDir) {
7062
+ return snapshotIds(dataDir).flatMap((id) => {
7063
+ const snap = loadSnapshot(dataDir, id);
7064
+ if (!snap) return [];
7065
+ return [{
7066
+ id,
7067
+ takenAt: snap.takenAt,
7068
+ dealCount: snap.deals.length,
7069
+ openValue: openValue(snap.deals)
7070
+ }];
7071
+ });
7072
+ }
7059
7073
  /** The most recent snapshot whose id is at or before `iso` (YYYY-MM-DD). */
7060
7074
  function latestSnapshotAtOrBefore(dataDir, iso) {
7061
7075
  const id = snapshotIds(dataDir).filter((s) => s <= iso).pop();
@@ -7063,8 +7077,8 @@ function latestSnapshotAtOrBefore(dataDir, iso) {
7063
7077
  }
7064
7078
  /** Compute what changed between two snapshots (before → after). */
7065
7079
  function diffSnapshots(before, after) {
7066
- const beforeByKey = new Map(before.deals.map((d) => [dealKey(d), d]));
7067
- const afterByKey = new Map(after.deals.map((d) => [dealKey(d), d]));
7080
+ const beforeByKey = new Map(before.deals.map((d) => [dealKey$1(d), d]));
7081
+ const afterByKey = new Map(after.deals.map((d) => [dealKey$1(d), d]));
7068
7082
  const added = [];
7069
7083
  const removed = [];
7070
7084
  const advanced = [];
@@ -7138,11 +7152,11 @@ function diffAgainstNow(dataDir, since, today = (/* @__PURE__ */ new Date()).toI
7138
7152
  }
7139
7153
  //#endregion
7140
7154
  //#region src/mcp/tools/get-pipeline-changes.ts
7141
- const DATA_DIR$2 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
7155
+ const DATA_DIR$4 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
7142
7156
  function daysAgoIso(days) {
7143
7157
  return (/* @__PURE__ */ new Date(Date.now() - days * 864e5)).toISOString().slice(0, 10);
7144
7158
  }
7145
- async function handleGetPipelineChanges(input, dataDir = DATA_DIR$2) {
7159
+ async function handleGetPipelineChanges(input, dataDir = DATA_DIR$4) {
7146
7160
  const since = input.since ?? daysAgoIso(input.days ?? 7);
7147
7161
  const diff = diffAgainstNow(dataDir, since);
7148
7162
  const payload = diff ? diff : { error: `No pipeline snapshot at or before ${since}. Snapshots accrue daily via the daemon.` };
@@ -7177,6 +7191,268 @@ or { error } when no baseline snapshot exists yet.`,
7177
7191
  });
7178
7192
  }
7179
7193
  //#endregion
7194
+ //#region src/core/velocity.ts
7195
+ const DEFAULT_STALLED_DAYS = 14;
7196
+ const OPEN_STAGES = [
7197
+ "lead",
7198
+ "qualified",
7199
+ "proposal",
7200
+ "negotiation"
7201
+ ];
7202
+ function dealKey(d) {
7203
+ return `${d.slug}::${d.name}`;
7204
+ }
7205
+ function isOpen(stage) {
7206
+ return stage !== "won" && stage !== "lost";
7207
+ }
7208
+ /** Whole days between two YYYY-MM-DD ids (b - a). */
7209
+ function daysBetween(a, b) {
7210
+ const ms = Date.parse(`${b}T00:00:00Z`) - Date.parse(`${a}T00:00:00Z`);
7211
+ return Math.round(ms / 864e5);
7212
+ }
7213
+ function stalledThreshold(opts) {
7214
+ if (opts?.stalledDays !== void 0) return opts.stalledDays;
7215
+ const n = parseInt(process.env["DXCRM_STALLED_DAYS"] ?? "", 10);
7216
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_STALLED_DAYS;
7217
+ }
7218
+ /** Per-deal chronological list of (date, stage) observations. */
7219
+ function buildTimelines(snaps) {
7220
+ const timelines = /* @__PURE__ */ new Map();
7221
+ for (const snap of snaps) for (const deal of snap.deals) {
7222
+ const key = dealKey(deal);
7223
+ const points = timelines.get(key) ?? [];
7224
+ points.push({
7225
+ date: snap.id,
7226
+ stage: deal.stage,
7227
+ deal
7228
+ });
7229
+ timelines.set(key, points);
7230
+ }
7231
+ return timelines;
7232
+ }
7233
+ function analyzeVelocity(dataDir, opts) {
7234
+ const threshold = stalledThreshold(opts);
7235
+ const metas = listSnapshots(dataDir);
7236
+ if (metas.length === 0) return {
7237
+ fromId: null,
7238
+ toId: null,
7239
+ snapshotCount: 0,
7240
+ stageDurations: [],
7241
+ avgSalesCycleDays: null,
7242
+ wonCount: 0,
7243
+ stalledDeals: [],
7244
+ stalledThresholdDays: threshold
7245
+ };
7246
+ const snaps = metas.flatMap((m) => {
7247
+ const s = loadSnapshot(dataDir, m.id);
7248
+ return s ? [s] : [];
7249
+ });
7250
+ const latestId = snaps[snaps.length - 1].id;
7251
+ const timelines = buildTimelines(snaps);
7252
+ const dwellTotals = /* @__PURE__ */ new Map();
7253
+ const cycleDurations = [];
7254
+ let wonCount = 0;
7255
+ const stalledDeals = [];
7256
+ for (const points of timelines.values()) {
7257
+ const firstSeen = points[0].date;
7258
+ let stageEnteredAt = points[0].date;
7259
+ let currentStage = points[0].stage;
7260
+ for (let i = 1; i < points.length; i++) {
7261
+ const p = points[i];
7262
+ if (p.stage !== currentStage) {
7263
+ const acc = dwellTotals.get(currentStage) ?? {
7264
+ total: 0,
7265
+ samples: 0
7266
+ };
7267
+ acc.total += daysBetween(stageEnteredAt, p.date);
7268
+ acc.samples += 1;
7269
+ dwellTotals.set(currentStage, acc);
7270
+ if (p.stage === "won") {
7271
+ wonCount += 1;
7272
+ cycleDurations.push(daysBetween(firstSeen, p.date));
7273
+ }
7274
+ currentStage = p.stage;
7275
+ stageEnteredAt = p.date;
7276
+ }
7277
+ }
7278
+ const last = points[points.length - 1];
7279
+ if (last.date === latestId && isOpen(last.stage)) {
7280
+ const daysInStage = daysBetween(stageEnteredAt, latestId);
7281
+ if (daysInStage > threshold) stalledDeals.push({
7282
+ slug: last.deal.slug,
7283
+ name: last.deal.name,
7284
+ stage: last.stage,
7285
+ daysInStage,
7286
+ value: last.deal.value
7287
+ });
7288
+ }
7289
+ }
7290
+ const stageDurations = OPEN_STAGES.flatMap((stage) => {
7291
+ const acc = dwellTotals.get(stage);
7292
+ if (!acc || acc.samples === 0) return [];
7293
+ return [{
7294
+ stage,
7295
+ avgDays: Math.round(acc.total / acc.samples),
7296
+ samples: acc.samples
7297
+ }];
7298
+ });
7299
+ const avgSalesCycleDays = cycleDurations.length > 0 ? Math.round(cycleDurations.reduce((a, b) => a + b, 0) / cycleDurations.length) : null;
7300
+ stalledDeals.sort((a, b) => b.daysInStage - a.daysInStage);
7301
+ return {
7302
+ fromId: snaps[0].id,
7303
+ toId: latestId,
7304
+ snapshotCount: snaps.length,
7305
+ stageDurations,
7306
+ avgSalesCycleDays,
7307
+ wonCount,
7308
+ stalledDeals,
7309
+ stalledThresholdDays: threshold
7310
+ };
7311
+ }
7312
+ //#endregion
7313
+ //#region src/mcp/tools/get-pipeline-velocity.ts
7314
+ const DATA_DIR$3 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
7315
+ async function handleGetPipelineVelocity(input, dataDir = DATA_DIR$3) {
7316
+ const opts = {};
7317
+ if (input.stalledDays !== void 0) opts.stalledDays = input.stalledDays;
7318
+ const report = analyzeVelocity(dataDir, opts);
7319
+ return { content: [{
7320
+ type: "text",
7321
+ text: JSON.stringify(report, null, 2)
7322
+ }] };
7323
+ }
7324
+ function registerGetPipelineVelocity(server) {
7325
+ server.registerTool("get_pipeline_velocity", {
7326
+ title: "Get Pipeline Velocity",
7327
+ description: `Pipeline velocity analytics from the daily snapshot history.
7328
+ Reconstructs each deal's stage journey to answer "where do deals get stuck?"
7329
+ and "which deals are rotting?".
7330
+
7331
+ Args:
7332
+ stalledDays: A deal open in the same stage longer than this is "stalled"
7333
+ (default 14).
7334
+
7335
+ Returns: { fromId, toId, snapshotCount, stageDurations[{stage,avgDays,samples}],
7336
+ avgSalesCycleDays, wonCount, stalledDeals[{slug,name,stage,daysInStage,value}],
7337
+ stalledThresholdDays }. snapshotCount is 0 until the daemon has taken snapshots.`,
7338
+ inputSchema: zod.z.object({ stalledDays: zod.z.number().int().min(1).max(365).optional().describe("Days in one stage before a deal counts as stalled (default 14)") })
7339
+ }, async ({ stalledDays }) => {
7340
+ const input = {};
7341
+ if (stalledDays !== void 0) input.stalledDays = stalledDays;
7342
+ return handleGetPipelineVelocity(input);
7343
+ });
7344
+ }
7345
+ //#endregion
7346
+ //#region src/core/funnel.ts
7347
+ const FUNNEL_STAGES = [
7348
+ "lead",
7349
+ "qualified",
7350
+ "proposal",
7351
+ "negotiation",
7352
+ "won"
7353
+ ];
7354
+ const STAGE_INDEX = Object.fromEntries(FUNNEL_STAGES.map((s, i) => [s, i]));
7355
+ function emptyReport(snapshotCount) {
7356
+ return {
7357
+ fromId: null,
7358
+ toId: null,
7359
+ snapshotCount,
7360
+ stages: [],
7361
+ wonCount: 0,
7362
+ lostCount: 0,
7363
+ winRatePct: null,
7364
+ biggestLeak: null
7365
+ };
7366
+ }
7367
+ function analyzeFunnel(dataDir) {
7368
+ const metas = listSnapshots(dataDir);
7369
+ if (metas.length === 0) return emptyReport(0);
7370
+ const snaps = metas.flatMap((m) => {
7371
+ const s = loadSnapshot(dataDir, m.id);
7372
+ return s ? [s] : [];
7373
+ });
7374
+ const progress = /* @__PURE__ */ new Map();
7375
+ for (const snap of snaps) for (const deal of snap.deals) {
7376
+ const key = `${deal.slug}::${deal.name}`;
7377
+ const p = progress.get(key) ?? {
7378
+ maxIndex: -1,
7379
+ won: false,
7380
+ lost: false
7381
+ };
7382
+ if (deal.stage === "lost") p.lost = true;
7383
+ else {
7384
+ const idx = STAGE_INDEX[deal.stage];
7385
+ if (idx !== void 0 && idx > p.maxIndex) p.maxIndex = idx;
7386
+ if (deal.stage === "won") p.won = true;
7387
+ }
7388
+ progress.set(key, p);
7389
+ }
7390
+ const reached = new Array(FUNNEL_STAGES.length).fill(0);
7391
+ let wonCount = 0;
7392
+ let lostCount = 0;
7393
+ for (const p of progress.values()) {
7394
+ for (let i = 0; i <= p.maxIndex; i++) reached[i] = (reached[i] ?? 0) + 1;
7395
+ if (p.won) wonCount += 1;
7396
+ else if (p.lost) lostCount += 1;
7397
+ }
7398
+ const stages = FUNNEL_STAGES.map((stage, i) => {
7399
+ const here = reached[i] ?? 0;
7400
+ const next = reached[i + 1];
7401
+ return {
7402
+ stage,
7403
+ reached: here,
7404
+ conversionPctToNext: next === void 0 || here === 0 ? null : Math.round(next / here * 100)
7405
+ };
7406
+ });
7407
+ let biggestLeak = null;
7408
+ for (let i = 0; i < FUNNEL_STAGES.length - 1; i++) {
7409
+ const conv = stages[i].conversionPctToNext;
7410
+ if (conv === null) continue;
7411
+ if (biggestLeak === null || conv < biggestLeak.conversionPct) biggestLeak = {
7412
+ from: FUNNEL_STAGES[i],
7413
+ to: FUNNEL_STAGES[i + 1],
7414
+ conversionPct: conv
7415
+ };
7416
+ }
7417
+ const closed = wonCount + lostCount;
7418
+ const winRatePct = closed > 0 ? Math.round(wonCount / closed * 100) : null;
7419
+ return {
7420
+ fromId: snaps[0].id,
7421
+ toId: snaps[snaps.length - 1].id,
7422
+ snapshotCount: snaps.length,
7423
+ stages,
7424
+ wonCount,
7425
+ lostCount,
7426
+ winRatePct,
7427
+ biggestLeak
7428
+ };
7429
+ }
7430
+ //#endregion
7431
+ //#region src/mcp/tools/get-pipeline-funnel.ts
7432
+ const DATA_DIR$2 = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
7433
+ async function handleGetPipelineFunnel(_input, dataDir = DATA_DIR$2) {
7434
+ const report = analyzeFunnel(dataDir);
7435
+ return { content: [{
7436
+ type: "text",
7437
+ text: JSON.stringify(report, null, 2)
7438
+ }] };
7439
+ }
7440
+ function registerGetPipelineFunnel(server) {
7441
+ server.registerTool("get_pipeline_funnel", {
7442
+ title: "Get Pipeline Funnel",
7443
+ description: `Pipeline conversion funnel & win-rate from the daily snapshot history.
7444
+ For each deal it tracks the furthest stage reached and the win/lost outcome,
7445
+ then builds a cumulative funnel. Answers "where do deals leak out of my
7446
+ pipeline?" and "what's my win rate?".
7447
+
7448
+ Returns: { fromId, toId, snapshotCount,
7449
+ stages[{stage, reached, conversionPctToNext}], wonCount, lostCount, winRatePct,
7450
+ biggestLeak{from,to,conversionPct} }. snapshotCount is 0 until the daemon has
7451
+ taken snapshots.`,
7452
+ inputSchema: zod.z.object({})
7453
+ }, async () => handleGetPipelineFunnel({}));
7454
+ }
7455
+ //#endregion
7180
7456
  //#region src/mcp/prompts.ts
7181
7457
  /**
7182
7458
  * CRM playbook prompts exposed via MCP `prompts/list` + `prompts/get`.
@@ -7595,6 +7871,8 @@ function createMcpServer() {
7595
7871
  registerGetLogs(server);
7596
7872
  registerGetDiagnostics(server);
7597
7873
  registerGetPipelineChanges(server);
7874
+ registerGetPipelineVelocity(server);
7875
+ registerGetPipelineFunnel(server);
7598
7876
  registerCustomObjectTools(server);
7599
7877
  registerPrompts(server);
7600
7878
  registerResources(server);