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