@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/cli.js +35 -5
- package/dist/cli.js.map +1 -1
- package/dist/{index-BBAlKZg6.d.ts → index-DoYT-azq.d.ts} +8 -8
- package/dist/index-DoYT-azq.d.ts.map +1 -0
- package/dist/{index-DcDaz_cu.d.cts → index-Ewy4f1XW.d.cts} +14 -14
- package/dist/{index-DcDaz_cu.d.cts.map → index-Ewy4f1XW.d.cts.map} +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/{knowledge-base-Cc0niBFf.js → knowledge-base-Byo0zwM5.js} +2 -1
- package/dist/knowledge-base-Byo0zwM5.js.map +1 -0
- package/dist/mcp-CdTJWTJf.d.cts.map +1 -1
- package/dist/mcp-CdTJWTJf.d.ts.map +1 -1
- package/dist/mcp.cjs +303 -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 +303 -137
- package/dist/mcp.js.map +1 -1
- package/dist/{server-BbInMUgp.js → server-C0XkJQBo.js} +167 -132
- package/dist/server-C0XkJQBo.js.map +1 -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-BBAlKZg6.d.ts.map +0 -1
- package/dist/knowledge-base-Cc0niBFf.js.map +0 -1
- package/dist/server-BbInMUgp.js.map +0 -1
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$
|
|
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$
|
|
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$
|
|
1584
|
-
async function handleSearchCustomerKnowledge(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
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$
|
|
2153
|
-
async function handleLogInteraction(input, dataDir = DATA_DIR$
|
|
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$
|
|
2263
|
-
async function handleUpdateDeal(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
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$
|
|
2465
|
-
async function handleUpdateCustomerFacts(input, dataDir = DATA_DIR$
|
|
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$
|
|
2644
|
-
async function handleGetDealHealth(input, dataDir = DATA_DIR$
|
|
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$
|
|
2694
|
-
async function handleGetPipelineForecast(input, dataDir = DATA_DIR$
|
|
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$
|
|
2756
|
-
async function handleSummarizeMeeting(input, dataDir = DATA_DIR$
|
|
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$
|
|
2880
|
-
async function handleGetPipelineStages(_input, dataDir = DATA_DIR$
|
|
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$
|
|
2909
|
-
async function handleGetMarketIntelligence(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3688
|
-
async function handleRunDealAgent(input, dataDir = DATA_DIR$
|
|
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$
|
|
3756
|
-
async function handleApproveAgentAction(input, dataDir = DATA_DIR$
|
|
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$
|
|
4017
|
-
async function handleSimulateRevenue(input, dataDir = DATA_DIR$
|
|
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$
|
|
4076
|
-
async function handleGetPlaybook(input, dataDir = DATA_DIR$
|
|
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$
|
|
4162
|
+
}, DATA_DIR$38));
|
|
4162
4163
|
}
|
|
4163
4164
|
//#endregion
|
|
4164
4165
|
//#region src/mcp/tools/create-playbook.ts
|
|
4165
|
-
const DATA_DIR$
|
|
4166
|
-
async function handleCreatePlaybook(input, dataDir = DATA_DIR$
|
|
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$
|
|
4240
|
+
}, DATA_DIR$37));
|
|
4240
4241
|
}
|
|
4241
4242
|
//#endregion
|
|
4242
4243
|
//#region src/mcp/tools/list-playbooks.ts
|
|
4243
|
-
const DATA_DIR$
|
|
4244
|
-
async function handleListPlaybooks(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
4288
|
-
async function handleDistillPlaybook(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
4566
|
-
async function handlePursueGoal(input, dataDir = DATA_DIR$
|
|
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$
|
|
4630
|
+
}, DATA_DIR$34));
|
|
4630
4631
|
}
|
|
4631
4632
|
//#endregion
|
|
4632
4633
|
//#region src/mcp/tools/get-goal-status.ts
|
|
4633
|
-
const DATA_DIR$
|
|
4634
|
-
async function handleGetGoalStatus(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
4790
|
+
}, DATA_DIR$32));
|
|
4790
4791
|
}
|
|
4791
4792
|
//#endregion
|
|
4792
4793
|
//#region src/mcp/tools/get-push-status.ts
|
|
4793
|
-
const DATA_DIR$
|
|
4794
|
-
async function handleGetPushStatus(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
4933
|
-
async function handleGetOrgIntelligence(input, dataDir = DATA_DIR$
|
|
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$
|
|
5067
|
-
async function handleOpenDealRoom(input, dataDir = DATA_DIR$
|
|
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$
|
|
5151
|
-
async function handleGetProactiveBriefing(input, dataDir = DATA_DIR$
|
|
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$
|
|
5252
|
-
async function handleListEmailTemplates(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
5294
|
-
async function handleGetEmailTemplate(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
5319
|
-
async function handleDraftEmail(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
5472
|
-
async function handleEnrollInSequence(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
5522
|
-
async function handleListSequenceEnrollments(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
5551
|
-
async function handleUnenrollFromSequence(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
5574
|
-
async function handleListSequences(_input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
5724
|
-
async function handleGenerateQuote(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
5777
|
-
async function handleGetQuoteStatus(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
6032
|
-
async function handleCreateTicket(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
6081
|
-
async function handleUpdateTicket(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
6126
|
-
async function handleListTickets(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
6169
|
-
async function handleCloseTicket(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
6351
|
-
async function handleSendNpsSurvey(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
6393
|
-
async function handleGetSurveyResults(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
6521
|
-
async function handleSearchKnowledgeBase(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
6556
|
-
async function handleCreateKbArticle(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
6610
|
-
async function handleBackupNow(input, dataDir = DATA_DIR$
|
|
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$
|
|
6648
|
-
async function handleListBackups(input, dataDir = DATA_DIR$
|
|
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$
|
|
6683
|
-
async function handleTriggerSync(input, dataDir = DATA_DIR$
|
|
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$
|
|
6778
|
-
async function handleGetAuditLog(input, dataDir = DATA_DIR$
|
|
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$
|
|
6821
|
-
async function handleGetLogs(input, dataDir = DATA_DIR$
|
|
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$
|
|
6984
|
-
async function handleGetDiagnostics(input, dataDir = DATA_DIR$
|
|
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$
|
|
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$
|
|
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);
|