@datasynx/agentic-crm 1.7.0 → 1.8.0

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