salesflare-mcp-server 1.0.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/API.md +691 -0
- package/CHANGELOG.md +49 -0
- package/CLAUDE.md +117 -0
- package/CONTRIBUTING.md +399 -0
- package/FIX_PLAN.md +70 -0
- package/INSPECTOR.md +191 -0
- package/LICENSE +21 -0
- package/PUBLISH.md +73 -0
- package/README.md +383 -0
- package/dist/auth/api-key-auth.d.ts +75 -0
- package/dist/auth/api-key-auth.d.ts.map +1 -0
- package/dist/auth/api-key-auth.js +103 -0
- package/dist/auth/oauth-auth.d.ts +81 -0
- package/dist/auth/oauth-auth.d.ts.map +1 -0
- package/dist/auth/oauth-auth.js +123 -0
- package/dist/auth/token-manager.d.ts +105 -0
- package/dist/auth/token-manager.d.ts.map +1 -0
- package/dist/auth/token-manager.js +87 -0
- package/dist/client/salesflare-client.d.ts +219 -0
- package/dist/client/salesflare-client.d.ts.map +1 -0
- package/dist/client/salesflare-client.js +484 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/server.d.ts +39 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +140 -0
- package/dist/tools/companies.d.ts +45 -0
- package/dist/tools/companies.d.ts.map +1 -0
- package/dist/tools/companies.js +392 -0
- package/dist/tools/contacts.d.ts +45 -0
- package/dist/tools/contacts.d.ts.map +1 -0
- package/dist/tools/contacts.js +290 -0
- package/dist/tools/deals.d.ts +46 -0
- package/dist/tools/deals.d.ts.map +1 -0
- package/dist/tools/deals.js +442 -0
- package/dist/tools/pipeline.d.ts +43 -0
- package/dist/tools/pipeline.d.ts.map +1 -0
- package/dist/tools/pipeline.js +328 -0
- package/dist/tools/tasks.d.ts +44 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +406 -0
- package/dist/transport/http-transport.d.ts +36 -0
- package/dist/transport/http-transport.d.ts.map +1 -0
- package/dist/transport/http-transport.js +173 -0
- package/dist/transport/stdio-transport.d.ts +37 -0
- package/dist/transport/stdio-transport.d.ts.map +1 -0
- package/dist/transport/stdio-transport.js +129 -0
- package/dist/types/company.d.ts +223 -0
- package/dist/types/company.d.ts.map +1 -0
- package/dist/types/company.js +8 -0
- package/dist/types/contact.d.ts +166 -0
- package/dist/types/contact.d.ts.map +1 -0
- package/dist/types/contact.js +8 -0
- package/dist/types/deal.d.ts +203 -0
- package/dist/types/deal.d.ts.map +1 -0
- package/dist/types/deal.js +8 -0
- package/dist/types/pipeline.d.ts +116 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +8 -0
- package/dist/types/task.d.ts +154 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +8 -0
- package/dist/utils/errors.d.ts +128 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +205 -0
- package/dist/utils/validation.d.ts +354 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +716 -0
- package/package.json +49 -0
- package/test-tasks-debug.js +21 -0
- package/test-tasks-params.js +52 -0
- package/test-tools.js +171 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline tools for Salesflare MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Implements pipeline operations:
|
|
5
|
+
* - salesflare_pipeline_list_stages: List pipeline stages
|
|
6
|
+
* - salesflare_pipeline_get_overview: Get pipeline statistics
|
|
7
|
+
*
|
|
8
|
+
* @module tools/pipeline
|
|
9
|
+
*/
|
|
10
|
+
import { SalesflareError, ErrorCode } from '../utils/errors.js';
|
|
11
|
+
/**
|
|
12
|
+
* Pipeline tool definitions with JSON schemas
|
|
13
|
+
*/
|
|
14
|
+
const pipelineTools = [
|
|
15
|
+
{
|
|
16
|
+
name: 'salesflare_pipeline_list_stages',
|
|
17
|
+
description: 'List pipeline stages from Salesflare CRM. ' +
|
|
18
|
+
'Supports filtering by pipeline_id to get stages for a specific pipeline. ' +
|
|
19
|
+
'Returns all stages across all pipelines by default, sorted by pipeline and position.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
pipeline_id: { type: 'string', description: 'Filter by pipeline ID (UUID, optional)' },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'salesflare_pipeline_get_overview',
|
|
29
|
+
description: 'Get pipeline overview and statistics from Salesflare CRM. ' +
|
|
30
|
+
'Shows deals per stage, total values, and win/loss counts. ' +
|
|
31
|
+
'Supports filtering by pipeline_id for single pipeline view. ' +
|
|
32
|
+
'Values are displayed in human-readable format (e.g., "$10,000.00").',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
pipeline_id: { type: 'string', description: 'Filter by pipeline ID (UUID, optional)' },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Exported pipeline tools array for unified registration
|
|
43
|
+
*/
|
|
44
|
+
export { pipelineTools };
|
|
45
|
+
/**
|
|
46
|
+
* Handle pipeline tool calls
|
|
47
|
+
*
|
|
48
|
+
* @param client - Salesflare API client
|
|
49
|
+
* @param name - Tool name
|
|
50
|
+
* @param args - Tool arguments
|
|
51
|
+
* @returns Tool response
|
|
52
|
+
*/
|
|
53
|
+
export async function handlePipelineTool(client, name, args) {
|
|
54
|
+
switch (name) {
|
|
55
|
+
case 'salesflare_pipeline_list_stages':
|
|
56
|
+
return await handleListStages(client, args);
|
|
57
|
+
case 'salesflare_pipeline_get_overview':
|
|
58
|
+
return await handleGetOverview(client, args);
|
|
59
|
+
default:
|
|
60
|
+
throw new SalesflareError({
|
|
61
|
+
code: ErrorCode.INVALID_INPUT,
|
|
62
|
+
message: `Unknown pipeline tool: ${name}`,
|
|
63
|
+
retryable: false,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Register pipeline-related tools with the MCP server
|
|
69
|
+
*
|
|
70
|
+
* @deprecated Use handlePipelineTool instead for unified registration
|
|
71
|
+
* @param server - MCP Server instance
|
|
72
|
+
* @param client - Salesflare API client
|
|
73
|
+
*/
|
|
74
|
+
export function registerPipelineTools(server, client) {
|
|
75
|
+
// This function is deprecated - use unified tool registration in server.ts
|
|
76
|
+
// Kept for backward compatibility
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Convert cents to dollars
|
|
80
|
+
*/
|
|
81
|
+
function centsToDollars(cents) {
|
|
82
|
+
return cents / 100;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Format currency value for human-readable display
|
|
86
|
+
* Example: 1000000 cents → '$10,000.00' (per D-24)
|
|
87
|
+
*/
|
|
88
|
+
function formatCurrency(valueInCents, currency = 'USD') {
|
|
89
|
+
const dollars = centsToDollars(valueInCents);
|
|
90
|
+
// Format with appropriate locale based on currency
|
|
91
|
+
let locale = 'en-US';
|
|
92
|
+
switch (currency.toUpperCase()) {
|
|
93
|
+
case 'EUR':
|
|
94
|
+
locale = 'de-DE';
|
|
95
|
+
break;
|
|
96
|
+
case 'GBP':
|
|
97
|
+
locale = 'en-GB';
|
|
98
|
+
break;
|
|
99
|
+
case 'JPY':
|
|
100
|
+
locale = 'ja-JP';
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
locale = 'en-US';
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
return new Intl.NumberFormat(locale, {
|
|
107
|
+
style: 'currency',
|
|
108
|
+
currency: currency.toUpperCase(),
|
|
109
|
+
}).format(dollars);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Fallback for unsupported currency codes
|
|
113
|
+
return `$${dollars.toFixed(2)}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Handle salesflare_pipeline_list_stages tool call
|
|
118
|
+
* Supports filtering by pipeline_id per D-21
|
|
119
|
+
*/
|
|
120
|
+
async function handleListStages(client, params) {
|
|
121
|
+
let stages = [];
|
|
122
|
+
let total = 0;
|
|
123
|
+
if (params.pipeline_id) {
|
|
124
|
+
// Get stages for specific pipeline
|
|
125
|
+
const response = await client.get(`/pipelines/${params.pipeline_id}/stages`);
|
|
126
|
+
const items = response.items || [];
|
|
127
|
+
stages = items.map((item) => ({
|
|
128
|
+
id: item.id,
|
|
129
|
+
name: item.name,
|
|
130
|
+
pipeline_id: params.pipeline_id,
|
|
131
|
+
position: item.position,
|
|
132
|
+
created_at: item.created_at,
|
|
133
|
+
}));
|
|
134
|
+
total = response.total ?? items.length;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Get all pipelines first, then their stages
|
|
138
|
+
const pipelinesResponse = await client.get('/pipelines');
|
|
139
|
+
// Handle different response formats
|
|
140
|
+
const pipelines = pipelinesResponse.items || [];
|
|
141
|
+
// Fetch stages for each pipeline
|
|
142
|
+
const stagesPromises = pipelines.map(async (pipeline) => {
|
|
143
|
+
const pipelineId = pipeline.id;
|
|
144
|
+
try {
|
|
145
|
+
const stagesResponse = await client.get(`/pipelines/${pipelineId}/stages`);
|
|
146
|
+
const stagesItems = stagesResponse.items || [];
|
|
147
|
+
return stagesItems.map((item) => ({
|
|
148
|
+
id: item.id,
|
|
149
|
+
name: item.name,
|
|
150
|
+
pipeline_id: pipelineId,
|
|
151
|
+
position: item.position,
|
|
152
|
+
created_at: item.created_at,
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// If can't fetch stages for a pipeline, return empty array
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
const stagesArrays = await Promise.all(stagesPromises);
|
|
161
|
+
stages = stagesArrays.flat();
|
|
162
|
+
// Sort by pipeline_id, then position (per D-22)
|
|
163
|
+
stages.sort((a, b) => {
|
|
164
|
+
if (a.pipeline_id !== b.pipeline_id) {
|
|
165
|
+
return a.pipeline_id.localeCompare(b.pipeline_id);
|
|
166
|
+
}
|
|
167
|
+
return a.position - b.position;
|
|
168
|
+
});
|
|
169
|
+
total = stages.length;
|
|
170
|
+
}
|
|
171
|
+
const listResponse = {
|
|
172
|
+
stages,
|
|
173
|
+
pipeline_id: params.pipeline_id,
|
|
174
|
+
total,
|
|
175
|
+
};
|
|
176
|
+
// Generate human-readable summary
|
|
177
|
+
let summaryText;
|
|
178
|
+
if (stages.length === 0) {
|
|
179
|
+
summaryText = params.pipeline_id
|
|
180
|
+
? 'No stages found for the specified pipeline.'
|
|
181
|
+
: 'No pipeline stages found.';
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
summaryText = `Found ${total} pipeline stage(s)`;
|
|
185
|
+
if (params.pipeline_id) {
|
|
186
|
+
summaryText += ` for pipeline ${params.pipeline_id}`;
|
|
187
|
+
}
|
|
188
|
+
summaryText += '.';
|
|
189
|
+
// List stages
|
|
190
|
+
const stageList = stages.slice(0, 10).map((stage) => {
|
|
191
|
+
return ` ${stage.position}. ${stage.name}`;
|
|
192
|
+
});
|
|
193
|
+
if (stageList.length > 0) {
|
|
194
|
+
summaryText += '\n\nStages:\n' + stageList.join('\n');
|
|
195
|
+
}
|
|
196
|
+
if (stages.length > 10) {
|
|
197
|
+
summaryText += `\n ... and ${stages.length - 10} more`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{ type: 'text', text: summaryText },
|
|
203
|
+
{ type: 'text', text: JSON.stringify(listResponse, null, 2) },
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Handle salesflare_pipeline_get_overview tool call
|
|
209
|
+
* Returns pipeline statistics per D-18, D-23, D-24
|
|
210
|
+
*/
|
|
211
|
+
async function handleGetOverview(client, params) {
|
|
212
|
+
const overviews = [];
|
|
213
|
+
// Get pipelines to process
|
|
214
|
+
let pipelines = [];
|
|
215
|
+
if (params.pipeline_id) {
|
|
216
|
+
// Get specific pipeline
|
|
217
|
+
try {
|
|
218
|
+
const pipelineResponse = await client.get(`/pipelines/${params.pipeline_id}`);
|
|
219
|
+
pipelines = [{
|
|
220
|
+
id: pipelineResponse.id,
|
|
221
|
+
name: pipelineResponse.name,
|
|
222
|
+
currency: pipelineResponse.currency || 'USD',
|
|
223
|
+
}];
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (error instanceof SalesflareError && error.code === ErrorCode.NOT_FOUND) {
|
|
227
|
+
throw new SalesflareError({
|
|
228
|
+
code: ErrorCode.NOT_FOUND,
|
|
229
|
+
message: `Pipeline not found: ${params.pipeline_id}`,
|
|
230
|
+
retryable: false,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Get all pipelines
|
|
238
|
+
const pipelinesResponse = await client.get('/pipelines');
|
|
239
|
+
const pipelinesItems = pipelinesResponse.items || [];
|
|
240
|
+
pipelines = pipelinesItems.map((p) => ({
|
|
241
|
+
id: p.id,
|
|
242
|
+
name: p.name,
|
|
243
|
+
currency: p.currency || 'USD',
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
// Gather statistics for each pipeline
|
|
247
|
+
for (const pipeline of pipelines) {
|
|
248
|
+
try {
|
|
249
|
+
// Get stages for this pipeline
|
|
250
|
+
const stagesResponse = await client.get(`/pipelines/${pipeline.id}/stages`);
|
|
251
|
+
// Get deals for this pipeline
|
|
252
|
+
const dealsResponse = await client.get('/opportunities', {
|
|
253
|
+
params: { pipeline_id: pipeline.id, limit: 1000 },
|
|
254
|
+
});
|
|
255
|
+
const stagesItems = stagesResponse.items || [];
|
|
256
|
+
const dealsItems = dealsResponse.items || [];
|
|
257
|
+
// Calculate statistics per stage
|
|
258
|
+
const stageStatistics = stagesItems.map((stage) => {
|
|
259
|
+
const stageDeals = dealsItems.filter((d) => d.stage_id === stage.id);
|
|
260
|
+
const stageValueCents = stageDeals.reduce((sum, d) => sum + (d.value || 0), 0);
|
|
261
|
+
return {
|
|
262
|
+
stage_id: stage.id,
|
|
263
|
+
stage_name: stage.name,
|
|
264
|
+
position: stage.position,
|
|
265
|
+
deal_count: stageDeals.length,
|
|
266
|
+
total_value_cents: stageValueCents,
|
|
267
|
+
total_value_display: formatCurrency(stageValueCents, pipeline.currency),
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
// Calculate overall totals
|
|
271
|
+
const totalValueCents = dealsItems.reduce((sum, d) => sum + (d.value || 0), 0);
|
|
272
|
+
const openDeals = dealsItems.filter((d) => d.status === 'open').length;
|
|
273
|
+
const wonDeals = dealsItems.filter((d) => d.status === 'won').length;
|
|
274
|
+
const lostDeals = dealsItems.filter((d) => d.status === 'lost').length;
|
|
275
|
+
overviews.push({
|
|
276
|
+
pipeline_id: pipeline.id,
|
|
277
|
+
pipeline_name: pipeline.name,
|
|
278
|
+
total_deals: dealsItems.length,
|
|
279
|
+
total_value_cents: totalValueCents,
|
|
280
|
+
total_value_display: formatCurrency(totalValueCents, pipeline.currency),
|
|
281
|
+
stages: stageStatistics,
|
|
282
|
+
open_deals: openDeals,
|
|
283
|
+
won_deals: wonDeals,
|
|
284
|
+
lost_deals: lostDeals,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// Skip pipelines we can't fetch data for
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const response = {
|
|
293
|
+
pipelines: overviews,
|
|
294
|
+
total_pipelines: overviews.length,
|
|
295
|
+
};
|
|
296
|
+
// Generate human-readable summary
|
|
297
|
+
let summaryText;
|
|
298
|
+
if (overviews.length === 0) {
|
|
299
|
+
summaryText = params.pipeline_id
|
|
300
|
+
? 'No data found for the specified pipeline.'
|
|
301
|
+
: 'No pipeline overview data available.';
|
|
302
|
+
}
|
|
303
|
+
else if (overviews.length === 1) {
|
|
304
|
+
const overview = overviews[0];
|
|
305
|
+
summaryText = `Pipeline Overview: ${overview.pipeline_name}\n` +
|
|
306
|
+
`Total: ${overview.total_deals} deals worth ${overview.total_value_display}\n` +
|
|
307
|
+
`Status: ${overview.open_deals} open, ${overview.won_deals} won, ${overview.lost_deals} lost`;
|
|
308
|
+
if (overview.stages.length > 0) {
|
|
309
|
+
summaryText += '\n\nStages:';
|
|
310
|
+
overview.stages.forEach((stage) => {
|
|
311
|
+
summaryText += `\n ${stage.stage_name}: ${stage.deal_count} deals (${stage.total_value_display})`;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
summaryText = `Pipeline Overview for ${overviews.length} pipelines:\n`;
|
|
317
|
+
overviews.forEach((overview) => {
|
|
318
|
+
summaryText += `\n${overview.pipeline_name}: ${overview.total_deals} deals, ${overview.total_value_display} ` +
|
|
319
|
+
`(${overview.open_deals} open, ${overview.won_deals} won, ${overview.lost_deals} lost)`;
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
content: [
|
|
324
|
+
{ type: 'text', text: summaryText },
|
|
325
|
+
{ type: 'text', text: JSON.stringify(response, null, 2) },
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task tools for Salesflare MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Implements CRUD operations for tasks:
|
|
5
|
+
* - salesflare_tasks_list: List tasks with filtering and pagination
|
|
6
|
+
* - salesflare_tasks_create: Create new tasks
|
|
7
|
+
* - salesflare_tasks_update: Update existing tasks (mark complete/incomplete)
|
|
8
|
+
*
|
|
9
|
+
* @module tools/tasks
|
|
10
|
+
*/
|
|
11
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { SalesflareClient } from '../client/salesflare-client.js';
|
|
14
|
+
/**
|
|
15
|
+
* Task tool definitions with JSON schemas
|
|
16
|
+
*/
|
|
17
|
+
declare const taskTools: Tool[];
|
|
18
|
+
/**
|
|
19
|
+
* Exported task tools array for unified registration
|
|
20
|
+
*/
|
|
21
|
+
export { taskTools };
|
|
22
|
+
/**
|
|
23
|
+
* Handle task tool calls
|
|
24
|
+
*
|
|
25
|
+
* @param client - Salesflare API client
|
|
26
|
+
* @param name - Tool name
|
|
27
|
+
* @param args - Tool arguments
|
|
28
|
+
* @returns Tool response
|
|
29
|
+
*/
|
|
30
|
+
export declare function handleTasksTool(client: SalesflareClient, name: string, args: unknown): Promise<{
|
|
31
|
+
content: Array<{
|
|
32
|
+
type: string;
|
|
33
|
+
text: string;
|
|
34
|
+
}>;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Register task-related tools with the MCP server
|
|
38
|
+
*
|
|
39
|
+
* @deprecated Use handleTasksTool instead for unified registration
|
|
40
|
+
* @param server - MCP Server instance
|
|
41
|
+
* @param client - Salesflare API client
|
|
42
|
+
*/
|
|
43
|
+
export declare function registerTasksTools(server: Server, client: SalesflareClient): void;
|
|
44
|
+
//# sourceMappingURL=tasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/tools/tasks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAGL,IAAI,EACL,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AASlE;;GAEG;AACH,QAAA,MAAM,SAAS,EAAE,IAAI,EA2DpB,CAAC;AAEF;;GAEG;AACH,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CAkB7D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAGjF"}
|