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