@codegame.dev/careerflow-mcp 1.1.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +398 -0
  3. package/dist/client.js +110 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/config.js +37 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/helpers.js +192 -0
  8. package/dist/helpers.js.map +1 -0
  9. package/dist/index.js +194 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/setup.js +296 -0
  12. package/dist/setup.js.map +1 -0
  13. package/dist/tools/admin.js +232 -0
  14. package/dist/tools/admin.js.map +1 -0
  15. package/dist/tools/benefit-funds.js +243 -0
  16. package/dist/tools/benefit-funds.js.map +1 -0
  17. package/dist/tools/chat-extra.js +172 -0
  18. package/dist/tools/chat-extra.js.map +1 -0
  19. package/dist/tools/chat.js +125 -0
  20. package/dist/tools/chat.js.map +1 -0
  21. package/dist/tools/cms.js +145 -0
  22. package/dist/tools/cms.js.map +1 -0
  23. package/dist/tools/crm-admin.js +269 -0
  24. package/dist/tools/crm-admin.js.map +1 -0
  25. package/dist/tools/crm.js +632 -0
  26. package/dist/tools/crm.js.map +1 -0
  27. package/dist/tools/dev-tickets.js +94 -0
  28. package/dist/tools/dev-tickets.js.map +1 -0
  29. package/dist/tools/forms.js +105 -0
  30. package/dist/tools/forms.js.map +1 -0
  31. package/dist/tools/hr.js +517 -0
  32. package/dist/tools/hr.js.map +1 -0
  33. package/dist/tools/kanban-extra.js +403 -0
  34. package/dist/tools/kanban-extra.js.map +1 -0
  35. package/dist/tools/misc.js +159 -0
  36. package/dist/tools/misc.js.map +1 -0
  37. package/dist/tools/projects.js +138 -0
  38. package/dist/tools/projects.js.map +1 -0
  39. package/dist/tools/tasks.js +342 -0
  40. package/dist/tools/tasks.js.map +1 -0
  41. package/dist/tools/time.js +129 -0
  42. package/dist/tools/time.js.map +1 -0
  43. package/dist/tools/workplace.js +110 -0
  44. package/dist/tools/workplace.js.map +1 -0
  45. package/dist/tools/workspace.js +199 -0
  46. package/dist/tools/workspace.js.map +1 -0
  47. package/package.json +61 -0
@@ -0,0 +1,632 @@
1
+ import { z } from "zod";
2
+ import { boolParam, companyIdSchema, extractList, resolveCompanyId, safeHandler, textResult } from "../helpers.js";
3
+ /**
4
+ * CRM tools: contacts (leads and customers), deals, and activities.
5
+ *
6
+ * Contacts carry a dual identity: a `contact_status` of `lead` moves through a
7
+ * pipeline/stage, while `customer` is a settled state. The API derives status
8
+ * from the pipeline/stage rather than accepting it directly on edit, so these
9
+ * tools never expose `contact_status` as a writable field.
10
+ */
11
+ const contactTypeSchema = z.enum(["individual", "corporation"]);
12
+ const relatedToTypeSchema = z.enum(["Contact", "Deal"]);
13
+ function summariseContact(c) {
14
+ return {
15
+ id: c.id,
16
+ contact_type: c.contact_type,
17
+ contact_status: c.contact_status,
18
+ name: c.contact_type === "corporation"
19
+ ? c.corporation_name
20
+ : [c.firstname, c.lastname].filter(Boolean).join(" ") || undefined,
21
+ email: c.email || undefined,
22
+ phone: c.phone || undefined,
23
+ mobile: c.mobile || undefined,
24
+ parent_id: c.parent_id ?? undefined,
25
+ child_count: c.child_count ?? undefined,
26
+ customer_code: c.customer_code || undefined,
27
+ lead_source: c.lead_source_name?.en ?? undefined,
28
+ lead_pipeline: c.lead_pipeline_name?.en ?? undefined,
29
+ lead_stage: c.lead_stage || undefined,
30
+ lead_score: c.lead_score ?? undefined,
31
+ rating: c.rating ?? undefined,
32
+ account_owner: [c.account_owner_firstname, c.account_owner_lastname].filter(Boolean).join(" ") || undefined,
33
+ is_active: c.is_active === undefined ? undefined : Boolean(c.is_active),
34
+ tags: Array.isArray(c.tags) ? c.tags.map((t) => t.name?.en ?? t.name_fa) : undefined,
35
+ created: c.created,
36
+ updated: c.updated
37
+ };
38
+ }
39
+ function summariseDeal(d) {
40
+ return {
41
+ id: d.id,
42
+ deal_name: d.deal_name,
43
+ contact_id: d.contact_id,
44
+ contact_name: d.contact_corporation_name ||
45
+ [d.contact_firstname, d.contact_lastname].filter(Boolean).join(" ") ||
46
+ undefined,
47
+ pipeline_id: d.pipeline_id,
48
+ pipeline: d.pipeline_name?.en ?? undefined,
49
+ stage_id: d.stage_id,
50
+ stage: d.stage_name?.en ?? undefined,
51
+ stage_is_won: d.stage_is_won === undefined ? undefined : Boolean(d.stage_is_won),
52
+ stage_is_lost: d.stage_is_lost === undefined ? undefined : Boolean(d.stage_is_lost),
53
+ amount: d.amount ?? undefined,
54
+ currency_id: d.currency_id ?? undefined,
55
+ probability: d.probability ?? undefined,
56
+ expected_close_date: d.expected_close_date || undefined,
57
+ actual_close_date: d.actual_close_date || undefined,
58
+ assigned_to: [d.assigned_to_firstname, d.assigned_to_lastname].filter(Boolean).join(" ") || undefined,
59
+ next_step: d.next_step || undefined,
60
+ lost_reason: d.lost_reason || undefined,
61
+ created: d.created,
62
+ updated: d.updated
63
+ };
64
+ }
65
+ function summariseActivity(a) {
66
+ return {
67
+ id: a.id,
68
+ activity_type: a.activity_type,
69
+ subject: a.subject,
70
+ related_to_type: a.related_to_type,
71
+ related_to_id: a.related_to_id,
72
+ contact_id: a.contact_id ?? undefined,
73
+ status: a.status,
74
+ priority: a.priority,
75
+ due_date: a.due_date || undefined,
76
+ completed_at: a.completed_at || undefined,
77
+ assigned_to: [a.assigned_to_firstname, a.assigned_to_lastname].filter(Boolean).join(" ") || undefined,
78
+ outcome: a.outcome || undefined,
79
+ created: a.created
80
+ };
81
+ }
82
+ export function registerCrmTools(server, client) {
83
+ // ---------------------------------------------------------------- contacts
84
+ server.registerTool("crm_search_contacts", {
85
+ title: "Search CRM contacts",
86
+ description: "Search leads and customers. A 'lead' moves through lead_pipeline_id/lead_stage_id; a " +
87
+ "'customer' is settled. Use crm_list_pipelines to resolve pipeline/stage ids.",
88
+ inputSchema: {
89
+ company_id: companyIdSchema,
90
+ search: z.string().optional(),
91
+ contact_type: contactTypeSchema.optional(),
92
+ contact_status: z.enum(["lead", "customer", "lost"]).optional(),
93
+ lead_pipeline_id: z.number().int().optional(),
94
+ lead_stage_id: z.number().int().optional(),
95
+ tag_id: z.number().int().optional(),
96
+ parent_id: z.number().int().optional().describe("Sub-contacts of one corporation."),
97
+ is_active: z.boolean().optional().default(true),
98
+ limit: z.number().int().min(1).max(50).optional().default(20),
99
+ page: z.number().int().min(1).optional().default(1),
100
+ order_by: z.string().optional().default("created"),
101
+ direction: z.enum(["asc", "desc"]).optional().default("desc")
102
+ }
103
+ }, safeHandler(async (args) => {
104
+ const companyId = resolveCompanyId(client, args.company_id);
105
+ const response = await client.get("crm/contacts/search", {
106
+ company_id: companyId,
107
+ search: args.search,
108
+ contact_type: args.contact_type,
109
+ contact_status: args.contact_status,
110
+ lead_pipeline_id: args.lead_pipeline_id,
111
+ lead_stage_id: args.lead_stage_id,
112
+ tag_id: args.tag_id,
113
+ parent_id: args.parent_id,
114
+ is_active: boolParam(args.is_active),
115
+ limit: args.limit,
116
+ page: args.page,
117
+ order_by: args.order_by,
118
+ direction: args.direction
119
+ });
120
+ const { items, total } = extractList(response.data, "contacts");
121
+ return textResult({ count: total ?? items.length, contacts: items.map(summariseContact) });
122
+ }));
123
+ server.registerTool("crm_get_contact", {
124
+ title: "Get a CRM contact",
125
+ description: "Fetch one contact (lead or customer) in full, including tags and custom fields.",
126
+ inputSchema: { company_id: companyIdSchema, contact_id: z.number().int() }
127
+ }, safeHandler(async (args) => {
128
+ const companyId = resolveCompanyId(client, args.company_id);
129
+ const response = await client.get(`crm/contacts/${args.contact_id}`, {
130
+ company_id: companyId
131
+ });
132
+ return textResult(response.data);
133
+ }));
134
+ server.registerTool("crm_create_contact", {
135
+ title: "Create a CRM contact",
136
+ description: "Create a lead or a customer. Individuals use firstname/lastname; corporations use " +
137
+ "corporation_name. Set parent_id to attach as a sub-contact of an existing corporation " +
138
+ "(inherits its status/pipeline/stage). Leads should include lead_pipeline_id and " +
139
+ "lead_stage_id to place them on a board.",
140
+ inputSchema: {
141
+ company_id: companyIdSchema,
142
+ contact_type: contactTypeSchema.optional(),
143
+ parent_id: z.number().int().optional().describe("Attaches as a sub-contact; forces individual type."),
144
+ firstname: z.string().optional(),
145
+ lastname: z.string().optional(),
146
+ corporation_name: z.string().optional(),
147
+ email: z.string().optional(),
148
+ phone: z.string().optional(),
149
+ mobile: z.string().optional(),
150
+ position: z.string().optional(),
151
+ department: z.string().optional(),
152
+ website: z.string().optional(),
153
+ industry: z.string().optional(),
154
+ employee_count: z.number().int().optional(),
155
+ lead_source_id: z.number().int().optional(),
156
+ rating: z.number().int().optional(),
157
+ credit_limit: z.number().optional(),
158
+ payment_terms_days: z.number().int().optional(),
159
+ currency: z.string().length(3).optional(),
160
+ account_owner_user_id: z.number().int().optional(),
161
+ lead_pipeline_id: z.number().int().optional().describe("Must be a pipeline of kind 'lead'."),
162
+ lead_stage_id: z.number().int().optional(),
163
+ address: z.string().optional(),
164
+ city: z.string().optional(),
165
+ state: z.string().optional(),
166
+ postal_code: z.string().optional(),
167
+ country: z.string().optional(),
168
+ is_active: z.boolean().optional().default(true),
169
+ notes: z.string().optional()
170
+ }
171
+ }, safeHandler(async (args) => {
172
+ const companyId = resolveCompanyId(client, args.company_id);
173
+ const response = await client.post("crm/contacts/create", {
174
+ company_id: companyId,
175
+ contact_type: args.contact_type,
176
+ parent_id: args.parent_id,
177
+ firstname: args.firstname,
178
+ lastname: args.lastname,
179
+ corporation_name: args.corporation_name,
180
+ email: args.email,
181
+ phone: args.phone,
182
+ mobile: args.mobile,
183
+ position: args.position,
184
+ department: args.department,
185
+ website: args.website,
186
+ industry: args.industry,
187
+ employee_count: args.employee_count,
188
+ lead_source_id: args.lead_source_id,
189
+ rating: args.rating,
190
+ credit_limit: args.credit_limit,
191
+ payment_terms_days: args.payment_terms_days,
192
+ currency: args.currency,
193
+ account_owner_user_id: args.account_owner_user_id,
194
+ lead_pipeline_id: args.lead_pipeline_id,
195
+ lead_stage_id: args.lead_stage_id,
196
+ address: args.address,
197
+ city: args.city,
198
+ state: args.state,
199
+ postal_code: args.postal_code,
200
+ country: args.country,
201
+ is_active: boolParam(args.is_active),
202
+ notes: args.notes
203
+ });
204
+ return textResult({ created: true, contact: summariseContact(response.data ?? {}) });
205
+ }));
206
+ server.registerTool("crm_update_contact", {
207
+ title: "Update a CRM contact",
208
+ description: "Change fields on a contact. Only the fields passed are altered. To move a lead through " +
209
+ "its pipeline, pass lead_pipeline_id/lead_stage_id — contact_status is derived from the " +
210
+ "stage automatically, not set directly.",
211
+ inputSchema: {
212
+ company_id: companyIdSchema,
213
+ contact_id: z.number().int(),
214
+ firstname: z.string().optional(),
215
+ lastname: z.string().optional(),
216
+ corporation_name: z.string().optional(),
217
+ email: z.string().optional(),
218
+ phone: z.string().optional(),
219
+ mobile: z.string().optional(),
220
+ position: z.string().optional(),
221
+ rating: z.number().int().optional(),
222
+ account_owner_user_id: z.number().int().optional(),
223
+ lead_pipeline_id: z.number().int().optional(),
224
+ lead_stage_id: z.number().int().optional(),
225
+ lead_stage_note: z.string().optional().describe("Note recorded against the stage change."),
226
+ lost_reason: z.string().optional(),
227
+ is_active: z.boolean().optional(),
228
+ notes: z.string().optional()
229
+ }
230
+ }, safeHandler(async (args) => {
231
+ const companyId = resolveCompanyId(client, args.company_id);
232
+ const response = await client.post("crm/contacts/edit", {
233
+ company_id: companyId,
234
+ id: args.contact_id,
235
+ firstname: args.firstname,
236
+ lastname: args.lastname,
237
+ corporation_name: args.corporation_name,
238
+ email: args.email,
239
+ phone: args.phone,
240
+ mobile: args.mobile,
241
+ position: args.position,
242
+ rating: args.rating,
243
+ account_owner_user_id: args.account_owner_user_id,
244
+ lead_pipeline_id: args.lead_pipeline_id,
245
+ lead_stage_id: args.lead_stage_id,
246
+ lead_stage_note: args.lead_stage_note,
247
+ lost_reason: args.lost_reason,
248
+ is_active: args.is_active === undefined ? undefined : boolParam(args.is_active),
249
+ notes: args.notes
250
+ });
251
+ return textResult({ updated: true, contact: summariseContact(response.data ?? {}) });
252
+ }));
253
+ server.registerTool("crm_delete_contact", {
254
+ title: "Delete a CRM contact",
255
+ description: "Soft-delete a contact. Its sub-contacts are hidden along with it.",
256
+ inputSchema: { company_id: companyIdSchema, contact_id: z.number().int() }
257
+ }, safeHandler(async (args) => {
258
+ const companyId = resolveCompanyId(client, args.company_id);
259
+ await client.post(`crm/contacts/delete/${args.contact_id}`, { company_id: companyId });
260
+ return textResult({ deleted: true, contact_id: args.contact_id });
261
+ }));
262
+ server.registerTool("crm_contact_timeline", {
263
+ title: "Get a contact's activity timeline",
264
+ description: "Chronological feed of activities on a contact and on its deals.",
265
+ inputSchema: {
266
+ company_id: companyIdSchema,
267
+ contact_id: z.number().int(),
268
+ limit: z.number().int().min(1).max(100).optional().default(20),
269
+ page: z.number().int().min(1).optional().default(1)
270
+ }
271
+ }, safeHandler(async (args) => {
272
+ const companyId = resolveCompanyId(client, args.company_id);
273
+ const response = await client.get(`crm/contacts/${args.contact_id}/timeline`, { company_id: companyId, limit: args.limit, page: args.page });
274
+ const { items, total } = extractList(response.data, "activities");
275
+ return textResult({ count: total ?? items.length, activities: items.map(summariseActivity) });
276
+ }));
277
+ // ------------------------------------------------------------------ deals
278
+ server.registerTool("crm_search_deals", {
279
+ title: "Search CRM deals",
280
+ description: "Search deals, optionally by pipeline, stage, contact, or lifecycle status.",
281
+ inputSchema: {
282
+ company_id: companyIdSchema,
283
+ search: z.string().optional(),
284
+ contact_id: z.number().int().optional(),
285
+ pipeline_id: z.number().int().optional(),
286
+ stage_id: z.number().int().optional(),
287
+ assigned_to_user_id: z.number().int().optional(),
288
+ status: z.enum(["open", "won", "lost", "closed", "all"]).optional().default("all"),
289
+ limit: z.number().int().min(1).max(50).optional().default(20),
290
+ page: z.number().int().min(1).optional().default(1),
291
+ order_by: z.string().optional().default("created"),
292
+ direction: z.enum(["asc", "desc"]).optional().default("desc")
293
+ }
294
+ }, safeHandler(async (args) => {
295
+ const companyId = resolveCompanyId(client, args.company_id);
296
+ const response = await client.get("crm/deals/search", {
297
+ company_id: companyId,
298
+ search: args.search,
299
+ contact_id: args.contact_id,
300
+ pipeline_id: args.pipeline_id,
301
+ stage_id: args.stage_id,
302
+ assigned_to_user_id: args.assigned_to_user_id,
303
+ status: args.status,
304
+ limit: args.limit,
305
+ page: args.page,
306
+ order_by: args.order_by,
307
+ direction: args.direction
308
+ });
309
+ const { items, total } = extractList(response.data, "deals");
310
+ return textResult({ count: total ?? items.length, deals: items.map(summariseDeal) });
311
+ }));
312
+ server.registerTool("crm_get_deal", {
313
+ title: "Get a CRM deal",
314
+ description: "Fetch one deal in full.",
315
+ inputSchema: { company_id: companyIdSchema, deal_id: z.number().int() }
316
+ }, safeHandler(async (args) => {
317
+ const companyId = resolveCompanyId(client, args.company_id);
318
+ const response = await client.get(`crm/deals/${args.deal_id}`, {
319
+ company_id: companyId
320
+ });
321
+ return textResult(response.data);
322
+ }));
323
+ server.registerTool("crm_create_deal", {
324
+ title: "Create a CRM deal",
325
+ description: "Create a deal on a pipeline/stage. Use crm_list_pipelines to resolve pipeline_id and " +
326
+ "stage_id, and crm_search_contacts to resolve contact_id.",
327
+ inputSchema: {
328
+ company_id: companyIdSchema,
329
+ deal_name: z.string().min(1).max(200),
330
+ contact_id: z.number().int(),
331
+ pipeline_id: z.number().int().describe("Must be a pipeline of kind 'deal'."),
332
+ stage_id: z.number().int(),
333
+ assigned_to_user_id: z.number().int(),
334
+ primary_contact_person_id: z.number().int().optional(),
335
+ amount: z.number().optional().default(0),
336
+ currency_id: z.number().int().optional(),
337
+ expected_close_date: z.string().optional(),
338
+ probability: z.number().int().optional(),
339
+ source_id: z.number().int().optional(),
340
+ description: z.string().optional(),
341
+ next_step: z.string().optional(),
342
+ competitors: z.array(z.string()).optional()
343
+ }
344
+ }, safeHandler(async (args) => {
345
+ const companyId = resolveCompanyId(client, args.company_id);
346
+ const response = await client.post("crm/deals/create", {
347
+ company_id: companyId,
348
+ deal_name: args.deal_name,
349
+ contact_id: args.contact_id,
350
+ pipeline_id: args.pipeline_id,
351
+ stage_id: args.stage_id,
352
+ assigned_to_user_id: args.assigned_to_user_id,
353
+ primary_contact_person_id: args.primary_contact_person_id,
354
+ amount: args.amount,
355
+ currency_id: args.currency_id,
356
+ expected_close_date: args.expected_close_date,
357
+ probability: args.probability,
358
+ source_id: args.source_id,
359
+ description: args.description,
360
+ next_step: args.next_step,
361
+ competitors: args.competitors ? JSON.stringify(args.competitors) : undefined
362
+ });
363
+ return textResult({ created: true, deal: summariseDeal(response.data ?? {}) });
364
+ }));
365
+ server.registerTool("crm_update_deal", {
366
+ title: "Update a CRM deal",
367
+ description: "Change fields on a deal. Only the fields passed are altered — the deal is read first and " +
368
+ "unmentioned fields keep their current values (the underlying API replaces the whole row, " +
369
+ "so this tool merges for you). Moving into a won/lost stage auto-fills actual_close_date " +
370
+ "unless you pass one.",
371
+ inputSchema: {
372
+ company_id: companyIdSchema,
373
+ deal_id: z.number().int(),
374
+ deal_name: z.string().min(1).max(200).optional(),
375
+ contact_id: z.number().int().optional(),
376
+ pipeline_id: z.number().int().optional(),
377
+ stage_id: z.number().int().optional(),
378
+ assigned_to_user_id: z.number().int().optional(),
379
+ amount: z.number().optional(),
380
+ currency_id: z.number().int().optional(),
381
+ expected_close_date: z.string().optional(),
382
+ actual_close_date: z.string().optional(),
383
+ probability: z.number().int().optional(),
384
+ source_id: z.number().int().optional(),
385
+ description: z.string().optional(),
386
+ next_step: z.string().optional(),
387
+ lost_reason: z.string().optional(),
388
+ stage_note: z.string().optional(),
389
+ competitors: z.array(z.string()).optional().describe("Omit to leave existing competitors unchanged.")
390
+ }
391
+ }, safeHandler(async (args) => {
392
+ const companyId = resolveCompanyId(client, args.company_id);
393
+ const current = await client.get(`crm/deals/${args.deal_id}`, {
394
+ company_id: companyId
395
+ });
396
+ const deal = current.data ?? {};
397
+ const response = await client.post("crm/deals/edit", {
398
+ company_id: companyId,
399
+ id: args.deal_id,
400
+ deal_name: args.deal_name ?? deal.deal_name,
401
+ contact_id: args.contact_id ?? deal.contact_id,
402
+ pipeline_id: args.pipeline_id ?? deal.pipeline_id,
403
+ stage_id: args.stage_id ?? deal.stage_id,
404
+ assigned_to_user_id: args.assigned_to_user_id ?? deal.assigned_to_user_id,
405
+ amount: args.amount ?? deal.amount,
406
+ currency_id: args.currency_id ?? deal.currency_id,
407
+ expected_close_date: args.expected_close_date ?? deal.expected_close_date,
408
+ actual_close_date: args.actual_close_date ?? deal.actual_close_date,
409
+ probability: args.probability ?? deal.probability,
410
+ source_id: args.source_id ?? deal.source_id,
411
+ description: args.description ?? deal.description,
412
+ next_step: args.next_step ?? deal.next_step,
413
+ lost_reason: args.lost_reason ?? deal.lost_reason,
414
+ stage_note: args.stage_note,
415
+ competitors: args.competitors ? JSON.stringify(args.competitors) : undefined
416
+ });
417
+ return textResult({ updated: true, deal: summariseDeal(response.data ?? {}) });
418
+ }));
419
+ server.registerTool("crm_delete_deal", {
420
+ title: "Delete a CRM deal",
421
+ description: "Soft-delete a deal.",
422
+ inputSchema: { company_id: companyIdSchema, deal_id: z.number().int() }
423
+ }, safeHandler(async (args) => {
424
+ const companyId = resolveCompanyId(client, args.company_id);
425
+ await client.post(`crm/deals/delete/${args.deal_id}`, { company_id: companyId });
426
+ return textResult({ deleted: true, deal_id: args.deal_id });
427
+ }));
428
+ server.registerTool("crm_deal_stage_summary", {
429
+ title: "Deal pipeline stage summary",
430
+ description: "Per-stage deal count and total amount, for a pipeline board's collapsed view.",
431
+ inputSchema: { company_id: companyIdSchema, pipeline_id: z.number().int().optional() }
432
+ }, safeHandler(async (args) => {
433
+ const companyId = resolveCompanyId(client, args.company_id);
434
+ const response = await client.get("crm/deals/stage-summary", {
435
+ company_id: companyId,
436
+ pipeline_id: args.pipeline_id
437
+ });
438
+ return textResult(response.data);
439
+ }));
440
+ server.registerTool("crm_add_deal_next_step", {
441
+ title: "Add a deal's next step",
442
+ description: "Record what happens next on a deal. Adds a history entry and updates the deal's current next step.",
443
+ inputSchema: { company_id: companyIdSchema, deal_id: z.number().int(), note: z.string().min(1).max(255) }
444
+ }, safeHandler(async (args) => {
445
+ const companyId = resolveCompanyId(client, args.company_id);
446
+ const response = await client.post("crm/deals/next-step/create", {
447
+ company_id: companyId,
448
+ deal_id: args.deal_id,
449
+ note: args.note
450
+ });
451
+ return textResult({ deal_id: args.deal_id, next_steps: response.data });
452
+ }));
453
+ server.registerTool("crm_list_deal_line_items", {
454
+ title: "List a deal's line items",
455
+ description: "List the priced line items on a deal.",
456
+ inputSchema: { company_id: companyIdSchema, deal_id: z.number().int() }
457
+ }, safeHandler(async (args) => {
458
+ const companyId = resolveCompanyId(client, args.company_id);
459
+ const response = await client.get(`crm/deals/${args.deal_id}/line-items`, {
460
+ company_id: companyId
461
+ });
462
+ return textResult(response.data);
463
+ }));
464
+ server.registerTool("crm_add_deal_line_item", {
465
+ title: "Add a deal line item",
466
+ description: "Add a priced line item to a deal. The line total and the deal's overall amount are " +
467
+ "recomputed automatically.",
468
+ inputSchema: {
469
+ company_id: companyIdSchema,
470
+ deal_id: z.number().int(),
471
+ name: z.string().min(1).max(200),
472
+ description: z.string().optional(),
473
+ quantity: z.number().optional().default(1),
474
+ unit_price: z.number().optional().default(0),
475
+ discount_percent: z.number().optional().default(0)
476
+ }
477
+ }, safeHandler(async (args) => {
478
+ const companyId = resolveCompanyId(client, args.company_id);
479
+ const response = await client.post("crm/deals/line-item/create", {
480
+ company_id: companyId,
481
+ deal_id: args.deal_id,
482
+ name: args.name,
483
+ description: args.description,
484
+ quantity: args.quantity,
485
+ unit_price: args.unit_price,
486
+ discount_percent: args.discount_percent
487
+ });
488
+ return textResult({ created: true, line_item: response.data });
489
+ }));
490
+ server.registerTool("crm_delete_deal_line_item", {
491
+ title: "Delete a deal line item",
492
+ description: "Remove a line item. The deal's amount is recomputed afterward.",
493
+ inputSchema: { company_id: companyIdSchema, line_item_id: z.number().int() }
494
+ }, safeHandler(async (args) => {
495
+ const companyId = resolveCompanyId(client, args.company_id);
496
+ await client.post(`crm/deals/line-item/delete/${args.line_item_id}`, { company_id: companyId });
497
+ return textResult({ deleted: true, line_item_id: args.line_item_id });
498
+ }));
499
+ // ------------------------------------------------------------- activities
500
+ server.registerTool("crm_search_activities", {
501
+ title: "Search CRM activities",
502
+ description: "Search calls, emails, meetings, tasks and notes logged against contacts or deals.",
503
+ inputSchema: {
504
+ company_id: companyIdSchema,
505
+ search: z.string().optional(),
506
+ activity_type: z.enum(["call", "email", "meeting", "task", "note"]).optional(),
507
+ related_to_type: relatedToTypeSchema.optional(),
508
+ related_to_id: z.number().int().optional(),
509
+ contact_id: z.number().int().optional(),
510
+ status: z.enum(["pending", "completed", "cancelled"]).optional(),
511
+ assigned_to_user_id: z.number().int().optional(),
512
+ priority: z.enum(["low", "medium", "high"]).optional(),
513
+ overdue: z.boolean().optional().describe("Only pending activities past their due date."),
514
+ limit: z.number().int().min(1).max(50).optional().default(20),
515
+ page: z.number().int().min(1).optional().default(1)
516
+ }
517
+ }, safeHandler(async (args) => {
518
+ const companyId = resolveCompanyId(client, args.company_id);
519
+ const response = await client.get("crm/activities/search", {
520
+ company_id: companyId,
521
+ search: args.search,
522
+ activity_type: args.activity_type,
523
+ related_to_type: args.related_to_type,
524
+ related_to_id: args.related_to_id,
525
+ contact_id: args.contact_id,
526
+ status: args.status,
527
+ assigned_to_user_id: args.assigned_to_user_id,
528
+ priority: args.priority,
529
+ overdue: boolParam(args.overdue),
530
+ limit: args.limit,
531
+ page: args.page
532
+ });
533
+ const { items, total } = extractList(response.data, "activities");
534
+ return textResult({ count: total ?? items.length, activities: items.map(summariseActivity) });
535
+ }));
536
+ server.registerTool("crm_create_activity", {
537
+ title: "Create a CRM activity",
538
+ description: "Log a call, email, meeting, task, or note against a contact or a deal.",
539
+ inputSchema: {
540
+ company_id: companyIdSchema,
541
+ activity_type: z.enum(["call", "email", "meeting", "task", "note"]),
542
+ subject: z.string().min(1).max(200),
543
+ related_to_type: relatedToTypeSchema,
544
+ related_to_id: z.number().int(),
545
+ assigned_to_user_id: z.number().int().optional(),
546
+ due_date: z.string().optional(),
547
+ reminder_at: z.string().optional(),
548
+ status: z.enum(["pending", "completed", "cancelled"]).optional().default("pending"),
549
+ priority: z.enum(["low", "medium", "high"]).optional().default("medium"),
550
+ direction: z.enum(["inbound", "outbound"]).optional(),
551
+ duration_minutes: z.number().int().optional(),
552
+ outcome: z.string().optional()
553
+ }
554
+ }, safeHandler(async (args) => {
555
+ const companyId = resolveCompanyId(client, args.company_id);
556
+ const response = await client.post("crm/activities/create", {
557
+ company_id: companyId,
558
+ activity_type: args.activity_type,
559
+ subject: args.subject,
560
+ related_to_type: args.related_to_type,
561
+ related_to_id: args.related_to_id,
562
+ assigned_to_user_id: args.assigned_to_user_id,
563
+ due_date: args.due_date,
564
+ reminder_at: args.reminder_at,
565
+ status: args.status,
566
+ priority: args.priority,
567
+ direction: args.direction,
568
+ duration_minutes: args.duration_minutes,
569
+ outcome: args.outcome
570
+ });
571
+ return textResult({ created: true, activity: summariseActivity(response.data ?? {}) });
572
+ }));
573
+ server.registerTool("crm_complete_activity", {
574
+ title: "Complete a CRM activity",
575
+ description: "Mark an activity done, optionally recording its outcome and duration.",
576
+ inputSchema: {
577
+ company_id: companyIdSchema,
578
+ activity_id: z.number().int(),
579
+ outcome: z.string().optional(),
580
+ duration_minutes: z.number().int().optional()
581
+ }
582
+ }, safeHandler(async (args) => {
583
+ const companyId = resolveCompanyId(client, args.company_id);
584
+ const response = await client.post("crm/activities/complete", {
585
+ company_id: companyId,
586
+ id: args.activity_id,
587
+ outcome: args.outcome,
588
+ duration_minutes: args.duration_minutes
589
+ });
590
+ return textResult({ completed: true, activity: summariseActivity(response.data ?? {}) });
591
+ }));
592
+ server.registerTool("crm_delete_activity", {
593
+ title: "Delete a CRM activity",
594
+ description: "Soft-delete an activity.",
595
+ inputSchema: { company_id: companyIdSchema, activity_id: z.number().int() }
596
+ }, safeHandler(async (args) => {
597
+ const companyId = resolveCompanyId(client, args.company_id);
598
+ await client.post(`crm/activities/delete/${args.activity_id}`, { company_id: companyId });
599
+ return textResult({ deleted: true, activity_id: args.activity_id });
600
+ }));
601
+ // ------------------------------------------------------------- dashboard
602
+ server.registerTool("crm_dashboard_summary", {
603
+ title: "CRM dashboard summary",
604
+ description: "Win/loss rates, forecast, lead sources and activity stats over a date range.",
605
+ inputSchema: {
606
+ company_id: companyIdSchema,
607
+ from: z.string().optional().describe("YYYY-MM-DD, defaults to 30 days ago."),
608
+ to: z.string().optional().describe("YYYY-MM-DD, defaults to today.")
609
+ }
610
+ }, safeHandler(async (args) => {
611
+ const companyId = resolveCompanyId(client, args.company_id);
612
+ const response = await client.get("crm/dashboard/summary", {
613
+ company_id: companyId,
614
+ from: args.from,
615
+ to: args.to
616
+ });
617
+ return textResult(response.data);
618
+ }));
619
+ server.registerTool("crm_dashboard_pipeline", {
620
+ title: "CRM pipeline dashboard",
621
+ description: "Per-stage deal breakdown and forecast for one pipeline.",
622
+ inputSchema: { company_id: companyIdSchema, pipeline_id: z.number().int() }
623
+ }, safeHandler(async (args) => {
624
+ const companyId = resolveCompanyId(client, args.company_id);
625
+ const response = await client.get("crm/dashboard/pipeline", {
626
+ company_id: companyId,
627
+ pipeline_id: args.pipeline_id
628
+ });
629
+ return textResult(response.data);
630
+ }));
631
+ }
632
+ //# sourceMappingURL=crm.js.map