@goxtechnologies/connectwise-psa-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 (147) hide show
  1. package/data/connectwise_api.db +0 -0
  2. package/data/manage.json +298179 -0
  3. package/dist/index.d.ts +10 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +116 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/operations/analytics-extended.d.ts +6 -0
  8. package/dist/operations/analytics-extended.d.ts.map +1 -0
  9. package/dist/operations/analytics-extended.js +825 -0
  10. package/dist/operations/analytics-extended.js.map +1 -0
  11. package/dist/operations/analytics-msp-assets.d.ts +3 -0
  12. package/dist/operations/analytics-msp-assets.d.ts.map +1 -0
  13. package/dist/operations/analytics-msp-assets.js +180 -0
  14. package/dist/operations/analytics-msp-assets.js.map +1 -0
  15. package/dist/operations/analytics-msp-clients.d.ts +3 -0
  16. package/dist/operations/analytics-msp-clients.d.ts.map +1 -0
  17. package/dist/operations/analytics-msp-clients.js +198 -0
  18. package/dist/operations/analytics-msp-clients.js.map +1 -0
  19. package/dist/operations/analytics-msp-comms.d.ts +3 -0
  20. package/dist/operations/analytics-msp-comms.d.ts.map +1 -0
  21. package/dist/operations/analytics-msp-comms.js +127 -0
  22. package/dist/operations/analytics-msp-comms.js.map +1 -0
  23. package/dist/operations/analytics-msp-contracts.d.ts +3 -0
  24. package/dist/operations/analytics-msp-contracts.d.ts.map +1 -0
  25. package/dist/operations/analytics-msp-contracts.js +91 -0
  26. package/dist/operations/analytics-msp-contracts.js.map +1 -0
  27. package/dist/operations/analytics-msp-financial.d.ts +3 -0
  28. package/dist/operations/analytics-msp-financial.d.ts.map +1 -0
  29. package/dist/operations/analytics-msp-financial.js +300 -0
  30. package/dist/operations/analytics-msp-financial.js.map +1 -0
  31. package/dist/operations/analytics-msp-procurement.d.ts +3 -0
  32. package/dist/operations/analytics-msp-procurement.d.ts.map +1 -0
  33. package/dist/operations/analytics-msp-procurement.js +78 -0
  34. package/dist/operations/analytics-msp-procurement.js.map +1 -0
  35. package/dist/operations/analytics-msp-projects.d.ts +3 -0
  36. package/dist/operations/analytics-msp-projects.d.ts.map +1 -0
  37. package/dist/operations/analytics-msp-projects.js +190 -0
  38. package/dist/operations/analytics-msp-projects.js.map +1 -0
  39. package/dist/operations/analytics-msp-sales.d.ts +3 -0
  40. package/dist/operations/analytics-msp-sales.d.ts.map +1 -0
  41. package/dist/operations/analytics-msp-sales.js +99 -0
  42. package/dist/operations/analytics-msp-sales.js.map +1 -0
  43. package/dist/operations/analytics-msp-schedule.d.ts +3 -0
  44. package/dist/operations/analytics-msp-schedule.d.ts.map +1 -0
  45. package/dist/operations/analytics-msp-schedule.js +339 -0
  46. package/dist/operations/analytics-msp-schedule.js.map +1 -0
  47. package/dist/operations/analytics-msp-team.d.ts +3 -0
  48. package/dist/operations/analytics-msp-team.d.ts.map +1 -0
  49. package/dist/operations/analytics-msp-team.js +195 -0
  50. package/dist/operations/analytics-msp-team.js.map +1 -0
  51. package/dist/operations/analytics-msp-tickets.d.ts +3 -0
  52. package/dist/operations/analytics-msp-tickets.d.ts.map +1 -0
  53. package/dist/operations/analytics-msp-tickets.js +578 -0
  54. package/dist/operations/analytics-msp-tickets.js.map +1 -0
  55. package/dist/operations/analytics-msp-time.d.ts +3 -0
  56. package/dist/operations/analytics-msp-time.d.ts.map +1 -0
  57. package/dist/operations/analytics-msp-time.js +485 -0
  58. package/dist/operations/analytics-msp-time.js.map +1 -0
  59. package/dist/operations/analytics-msp-utils.d.ts +49 -0
  60. package/dist/operations/analytics-msp-utils.d.ts.map +1 -0
  61. package/dist/operations/analytics-msp-utils.js +157 -0
  62. package/dist/operations/analytics-msp-utils.js.map +1 -0
  63. package/dist/operations/analytics.d.ts +9 -0
  64. package/dist/operations/analytics.d.ts.map +1 -0
  65. package/dist/operations/analytics.js +742 -0
  66. package/dist/operations/analytics.js.map +1 -0
  67. package/dist/operations/executor.d.ts +10 -0
  68. package/dist/operations/executor.d.ts.map +1 -0
  69. package/dist/operations/executor.js +243 -0
  70. package/dist/operations/executor.js.map +1 -0
  71. package/dist/operations/registry.d.ts +16 -0
  72. package/dist/operations/registry.d.ts.map +1 -0
  73. package/dist/operations/registry.js +847 -0
  74. package/dist/operations/registry.js.map +1 -0
  75. package/dist/services/api-database.d.ts +38 -0
  76. package/dist/services/api-database.d.ts.map +1 -0
  77. package/dist/services/api-database.js +191 -0
  78. package/dist/services/api-database.js.map +1 -0
  79. package/dist/services/cache.d.ts +12 -0
  80. package/dist/services/cache.d.ts.map +1 -0
  81. package/dist/services/cache.js +32 -0
  82. package/dist/services/cache.js.map +1 -0
  83. package/dist/services/connectwise-api.d.ts +43 -0
  84. package/dist/services/connectwise-api.d.ts.map +1 -0
  85. package/dist/services/connectwise-api.js +198 -0
  86. package/dist/services/connectwise-api.js.map +1 -0
  87. package/dist/services/db-builder.d.ts +11 -0
  88. package/dist/services/db-builder.d.ts.map +1 -0
  89. package/dist/services/db-builder.js +237 -0
  90. package/dist/services/db-builder.js.map +1 -0
  91. package/dist/services/fast-memory.d.ts +39 -0
  92. package/dist/services/fast-memory.d.ts.map +1 -0
  93. package/dist/services/fast-memory.js +147 -0
  94. package/dist/services/fast-memory.js.map +1 -0
  95. package/dist/services/load-env.d.ts +15 -0
  96. package/dist/services/load-env.d.ts.map +1 -0
  97. package/dist/services/load-env.js +59 -0
  98. package/dist/services/load-env.js.map +1 -0
  99. package/dist/tools/batch.d.ts +9 -0
  100. package/dist/tools/batch.d.ts.map +1 -0
  101. package/dist/tools/batch.js +159 -0
  102. package/dist/tools/batch.js.map +1 -0
  103. package/dist/tools/composite.d.ts +9 -0
  104. package/dist/tools/composite.d.ts.map +1 -0
  105. package/dist/tools/composite.js +353 -0
  106. package/dist/tools/composite.js.map +1 -0
  107. package/dist/tools/discovery.d.ts +9 -0
  108. package/dist/tools/discovery.d.ts.map +1 -0
  109. package/dist/tools/discovery.js +245 -0
  110. package/dist/tools/discovery.js.map +1 -0
  111. package/dist/tools/execution.d.ts +9 -0
  112. package/dist/tools/execution.d.ts.map +1 -0
  113. package/dist/tools/execution.js +130 -0
  114. package/dist/tools/execution.js.map +1 -0
  115. package/dist/tools/memory.d.ts +9 -0
  116. package/dist/tools/memory.d.ts.map +1 -0
  117. package/dist/tools/memory.js +152 -0
  118. package/dist/tools/memory.js.map +1 -0
  119. package/dist/tools/operations.d.ts +9 -0
  120. package/dist/tools/operations.d.ts.map +1 -0
  121. package/dist/tools/operations.js +214 -0
  122. package/dist/tools/operations.js.map +1 -0
  123. package/dist/tools/pagination.d.ts +9 -0
  124. package/dist/tools/pagination.d.ts.map +1 -0
  125. package/dist/tools/pagination.js +133 -0
  126. package/dist/tools/pagination.js.map +1 -0
  127. package/dist/tools/validation.d.ts +9 -0
  128. package/dist/tools/validation.d.ts.map +1 -0
  129. package/dist/tools/validation.js +705 -0
  130. package/dist/tools/validation.js.map +1 -0
  131. package/dist/types/index.d.ts +145 -0
  132. package/dist/types/index.d.ts.map +1 -0
  133. package/dist/types/index.js +3 -0
  134. package/dist/types/index.js.map +1 -0
  135. package/dist/types/operations.d.ts +30 -0
  136. package/dist/types/operations.d.ts.map +1 -0
  137. package/dist/types/operations.js +3 -0
  138. package/dist/types/operations.js.map +1 -0
  139. package/dist/utils/conditions.d.ts +20 -0
  140. package/dist/utils/conditions.d.ts.map +1 -0
  141. package/dist/utils/conditions.js +78 -0
  142. package/dist/utils/conditions.js.map +1 -0
  143. package/dist/utils/formatters.d.ts +35 -0
  144. package/dist/utils/formatters.d.ts.map +1 -0
  145. package/dist/utils/formatters.js +337 -0
  146. package/dist/utils/formatters.js.map +1 -0
  147. package/package.json +46 -0
@@ -0,0 +1,847 @@
1
+ // ConnectWise PSA MCP Server — Named Operations Registry
2
+ // 457 operations mapped from the Stackjack ConnectWise Manage connector catalog.
3
+ // Builder helpers keep each declaration to one line.
4
+ const idParam = { id: { description: 'Record ID', required: true, type: 'number' } };
5
+ function list(name, path, cat, desc, opts) {
6
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path, method: 'GET', paginate: true, params: {}, confirmRequired: false, ...opts };
7
+ }
8
+ function get(name, path, cat, desc, opts) {
9
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path: `${path}/{{id}}`, method: 'GET', paginate: false, params: { ...idParam }, confirmRequired: false, ...opts };
10
+ }
11
+ function search(name, path, cat, desc, opts) {
12
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path, method: 'GET', paginate: true, params: { conditions: { description: 'CW condition string', required: true, type: 'string' } }, confirmRequired: false, ...opts };
13
+ }
14
+ function count(name, path, cat, desc, opts) {
15
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path: `${path}/count`, method: 'GET', paginate: false, params: {}, confirmRequired: false, ...opts };
16
+ }
17
+ function discover(name, path, cat, desc, opts) {
18
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path, method: 'GET', paginate: false, params: {}, confirmRequired: false, ...opts };
19
+ }
20
+ function filtered(name, path, cat, desc, conditions, opts) {
21
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path, method: 'GET', paginate: true, params: {}, confirmRequired: false, defaultConditions: conditions, ...opts };
22
+ }
23
+ function listSub(name, parentPath, sub, parentParam, cat, desc, opts) {
24
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path: `${parentPath}/{{${parentParam}}}/${sub}`, method: 'GET', paginate: true, params: { [parentParam]: { description: `Parent record ID`, required: true, type: 'number' } }, confirmRequired: false, ...opts };
25
+ }
26
+ function getSub(name, parentPath, sub, parentParam, cat, desc, opts) {
27
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path: `${parentPath}/{{${parentParam}}}/${sub}/{{id}}`, method: 'GET', paginate: false, params: { [parentParam]: { description: 'Parent record ID', required: true, type: 'number' }, ...idParam }, confirmRequired: false, ...opts };
28
+ }
29
+ function create(name, path, cat, desc, opts) {
30
+ return { name, category: cat, tier: 'pro', description: desc, type: 'simple', path, method: 'POST', paginate: false, params: { body: { description: 'Request body (JSON object)', required: true, type: 'string' } }, confirmRequired: true, ...opts };
31
+ }
32
+ function createSub(name, parentPath, sub, parentParam, cat, desc, opts) {
33
+ return { name, category: cat, tier: 'pro', description: desc, type: 'simple', path: `${parentPath}/{{${parentParam}}}/${sub}`, method: 'POST', paginate: false, params: { [parentParam]: { description: 'Parent record ID', required: true, type: 'number' }, body: { description: 'Request body (JSON object)', required: true, type: 'string' } }, confirmRequired: true, ...opts };
34
+ }
35
+ function update(name, path, cat, desc, opts) {
36
+ return { name, category: cat, tier: 'pro', description: desc, type: 'simple', path: `${path}/{{id}}`, method: 'PATCH', paginate: false, params: { ...idParam, body: { description: 'Fields to update (JSON object)', required: true, type: 'string' } }, confirmRequired: true, ...opts };
37
+ }
38
+ function updateSub(name, parentPath, sub, parentParam, cat, desc, opts) {
39
+ return { name, category: cat, tier: 'pro', description: desc, type: 'simple', path: `${parentPath}/{{${parentParam}}}/${sub}/{{id}}`, method: 'PATCH', paginate: false, params: { [parentParam]: { description: 'Parent record ID', required: true, type: 'number' }, ...idParam, body: { description: 'Fields to update (JSON object)', required: true, type: 'string' } }, confirmRequired: true, ...opts };
40
+ }
41
+ function remove(name, path, cat, desc, opts) {
42
+ return { name, category: cat, tier: 'pro', description: desc, type: 'simple', path: `${path}/{{id}}`, method: 'DELETE', paginate: false, params: { ...idParam }, confirmRequired: true, ...opts };
43
+ }
44
+ function removeSub(name, parentPath, sub, parentParam, cat, desc, opts) {
45
+ return { name, category: cat, tier: 'pro', description: desc, type: 'simple', path: `${parentPath}/{{${parentParam}}}/${sub}/{{id}}`, method: 'DELETE', paginate: false, params: { [parentParam]: { description: 'Parent record ID', required: true, type: 'number' }, ...idParam }, confirmRequired: true, ...opts };
46
+ }
47
+ function composite(name, cat, desc, steps, tier = 'pro', opts) {
48
+ return { name, category: cat, tier, description: desc, type: 'composite', path: '', method: 'GET', paginate: false, params: {}, confirmRequired: false, steps, ...opts };
49
+ }
50
+ function analytics(name, cat, desc, handler, tier = 'free', opts) {
51
+ return { name, category: cat, tier, description: desc, type: 'analytics', path: '', method: 'GET', paginate: false, params: {}, confirmRequired: false, handler, ...opts };
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Shorthand for member-filtered and company-filtered convenience operations
55
+ // ---------------------------------------------------------------------------
56
+ function memberFiltered(name, path, cat, desc, conditionField, opts) {
57
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path, method: 'GET', paginate: true, params: { identifier: { description: 'Member identifier (login name)', required: true, type: 'string' } }, confirmRequired: false, defaultConditions: `${conditionField}='{{identifier}}'`, ...opts };
58
+ }
59
+ function companyFiltered(name, path, cat, desc, conditionField = 'company/id', opts) {
60
+ return { name, category: cat, tier: 'free', description: desc, type: 'simple', path, method: 'GET', paginate: true, params: { company_id: { description: 'Company ID', required: true, type: 'number' } }, confirmRequired: false, defaultConditions: `${conditionField}={{company_id}}`, ...opts };
61
+ }
62
+ // ---------------------------------------------------------------------------
63
+ // Board-scoped helper (statuses, types, subtypes need board_id)
64
+ // ---------------------------------------------------------------------------
65
+ function boardSub(name, sub, cat, desc) {
66
+ return listSub(name, '/service/boards', sub, 'board_id', cat, desc);
67
+ }
68
+ // ===========================================================================
69
+ // FREE TIER — 307 OPERATIONS
70
+ // ===========================================================================
71
+ const T = '/service/tickets';
72
+ const B = '/service/boards';
73
+ const CO = '/company/companies';
74
+ const CT = '/company/contacts';
75
+ const CF = '/company/configurations';
76
+ const AG = '/finance/agreements';
77
+ const INV = '/finance/invoices';
78
+ const TE = '/time/entries';
79
+ const TS = '/time/sheets';
80
+ const PR = '/project/projects';
81
+ const PT = '/project/tickets';
82
+ const OP = '/sales/opportunities';
83
+ const ACT = '/sales/activities';
84
+ const ORD = '/sales/orders';
85
+ const SCH = '/schedule/entries';
86
+ const CAL = '/schedule/calendars';
87
+ const CAT = '/procurement/catalog';
88
+ const PO = '/procurement/purchaseorders';
89
+ const EXE = '/expense/entries';
90
+ const EXR = '/expense/reports';
91
+ const MKT = '/marketing/campaigns';
92
+ const MKG = '/marketing/groups';
93
+ const MEM = '/system/members';
94
+ const DEP = '/system/departments';
95
+ const DOC = '/system/documents';
96
+ const WF = '/system/workflows';
97
+ const LOC = '/system/locations';
98
+ const freeOps = [
99
+ // =========================================================================
100
+ // SERVICE TICKETS
101
+ // =========================================================================
102
+ list('list_tickets', T, 'Service Tickets', 'List all service tickets', { defaultFields: 'id,summary,status/name,board/name,priority/name,company/name,dateEntered' }),
103
+ get('get_ticket', T, 'Service Tickets', 'Get a specific ticket by ID'),
104
+ search('search_tickets', T, 'Service Tickets', 'Search tickets with CW conditions'),
105
+ count('count_tickets', T, 'Service Tickets', 'Count tickets matching conditions'),
106
+ filtered('list_open_tickets', T, 'Service Tickets', 'List all open tickets', 'closedFlag=false', { defaultFields: 'id,summary,status/name,board/name,priority/name,company/name,dateEntered' }),
107
+ filtered('list_high_priority_tickets', T, 'Service Tickets', 'List high/critical priority tickets', "priority/id in (1,2,3) AND closedFlag=false", { defaultFields: 'id,summary,status/name,board/name,priority/name,company/name,dateEntered' }),
108
+ filtered('list_unassigned_tickets', T, 'Service Tickets', 'List tickets with no assigned resources', "resources=null AND closedFlag=false", { defaultFields: 'id,summary,status/name,board/name,priority/name,company/name,dateEntered' }),
109
+ filtered('list_recently_updated_tickets', T, 'Service Tickets', 'List tickets updated in the last 7 days', "lastUpdated>=[{{daysAgo:7}}]", { defaultFields: 'id,summary,status/name,board/name,priority/name,company/name,lastUpdated' }),
110
+ memberFiltered('list_member_tickets', T, 'Service Tickets', 'List tickets assigned to a specific member', "resources like '%{{identifier}}%' AND closedFlag=false"),
111
+ companyFiltered('list_company_tickets', T, 'Service Tickets', 'List tickets for a specific company'),
112
+ // =========================================================================
113
+ // TICKET DETAILS
114
+ // =========================================================================
115
+ listSub('list_ticket_notes', T, 'notes', 'ticket_id', 'Ticket Details', 'List all notes for a ticket'),
116
+ getSub('get_ticket_note', T, 'notes', 'ticket_id', 'Ticket Details', 'Get a specific ticket note'),
117
+ listSub('list_ticket_tasks', T, 'tasks', 'ticket_id', 'Ticket Details', 'List all tasks on a ticket'),
118
+ getSub('get_ticket_task', T, 'tasks', 'ticket_id', 'Ticket Details', 'Get a specific ticket task'),
119
+ listSub('list_ticket_configs', T, 'configurations', 'ticket_id', 'Ticket Details', 'List configurations linked to a ticket'),
120
+ listSub('list_ticket_documents', T, 'documents', 'ticket_id', 'Ticket Details', 'List documents attached to a ticket'),
121
+ listSub('list_ticket_products', T, 'products', 'ticket_id', 'Ticket Details', 'List products on a ticket'),
122
+ listSub('list_ticket_scheduleentries', T, 'scheduleentries', 'ticket_id', 'Ticket Details', 'List schedule entries for a ticket'),
123
+ listSub('list_ticket_timeentries', T, 'timeentries', 'ticket_id', 'Ticket Details', 'List time entries on a ticket'),
124
+ // =========================================================================
125
+ // SERVICE BOARDS
126
+ // =========================================================================
127
+ discover('discover_boards', B, 'Service Boards', 'List all service boards'),
128
+ get('get_board', B, 'Service Boards', 'Get a specific service board'),
129
+ boardSub('discover_statuses', 'statuses', 'Service Boards', 'List statuses for a board'),
130
+ boardSub('discover_types', 'types', 'Service Boards', 'List types for a board'),
131
+ boardSub('discover_subtypes', 'subtypes', 'Service Boards', 'List subtypes for a board'),
132
+ boardSub('discover_items', 'items', 'Service Boards', 'List items for a board'),
133
+ boardSub('discover_board_teams', 'teams', 'Service Boards', 'List teams assigned to a board'),
134
+ // =========================================================================
135
+ // SERVICE CONFIG
136
+ // =========================================================================
137
+ discover('discover_priorities', '/service/priorities', 'Service Config', 'List all service priorities'),
138
+ get('get_priority', '/service/priorities', 'Service Config', 'Get a specific priority'),
139
+ discover('discover_slas', '/service/SLAs', 'Service Config', 'List all SLA definitions'),
140
+ get('get_sla', '/service/SLAs', 'Service Config', 'Get a specific SLA'),
141
+ discover('discover_sources', '/service/sources', 'Service Config', 'List all ticket sources'),
142
+ discover('discover_impacts', '/service/impacts', 'Service Config', 'List all impact levels'),
143
+ discover('discover_severities', '/service/severities', 'Service Config', 'List all severity levels'),
144
+ discover('discover_codes', '/service/codes', 'Service Config', 'List all service codes'),
145
+ discover('discover_service_locations', '/service/locations', 'Service Config', 'List all service locations'),
146
+ discover('discover_surveys', '/service/surveys', 'Service Config', 'List all service surveys'),
147
+ listSub('discover_sla_priorities', '/service/SLAs', 'priorities', 'sla_id', 'Service Config', 'List priority settings for an SLA'),
148
+ // =========================================================================
149
+ // KNOWLEDGE BASE
150
+ // =========================================================================
151
+ list('list_kb_articles', '/service/knowledgeBaseArticles', 'Knowledge Base', 'List all knowledge base articles'),
152
+ get('get_kb_article', '/service/knowledgeBaseArticles', 'Knowledge Base', 'Get a specific KB article'),
153
+ search('search_kb_articles', '/service/knowledgeBaseArticles', 'Knowledge Base', 'Search KB articles with conditions'),
154
+ count('count_kb_articles', '/service/knowledgeBaseArticles', 'Knowledge Base', 'Count KB articles'),
155
+ filtered('list_recent_kb_articles', '/service/knowledgeBaseArticles', 'Knowledge Base', 'List recently created KB articles', 'dateCreated>=[{{daysAgo:30}}]'),
156
+ discover('discover_kb_categories', '/service/knowledgeBaseCategories', 'Knowledge Base', 'List KB categories'),
157
+ listSub('discover_kb_subcategories', '/service/knowledgeBaseCategories', 'subcategories', 'category_id', 'Knowledge Base', 'List subcategories for a KB category'),
158
+ // =========================================================================
159
+ // SERVICE TEMPLATES
160
+ // =========================================================================
161
+ list('list_service_templates', '/service/templates', 'Service Templates', 'List all service templates'),
162
+ get('get_service_template', '/service/templates', 'Service Templates', 'Get a specific service template'),
163
+ list('list_survey_results', '/service/surveys/results', 'Service Templates', 'List all survey results'),
164
+ // =========================================================================
165
+ // COMPANIES
166
+ // =========================================================================
167
+ list('list_companies', CO, 'Companies', 'List all companies', { defaultFields: 'id,name,identifier,status/name,types/name,phoneNumber,website' }),
168
+ get('get_company', CO, 'Companies', 'Get a specific company'),
169
+ search('search_companies', CO, 'Companies', 'Search companies with CW conditions'),
170
+ count('count_companies', CO, 'Companies', 'Count companies'),
171
+ get('get_company_usages', `${CO}/usages`, 'Companies', 'Get usage counts for a company (tickets, contacts, etc.)', { path: `${CO}/{{id}}/usages` }),
172
+ filtered('list_active_companies', CO, 'Companies', 'List active companies', "status/name='Active'"),
173
+ filtered('list_customer_companies', CO, 'Companies', 'List customer-type companies', "types/name='Customer'"),
174
+ filtered('list_prospect_companies', CO, 'Companies', 'List prospect-type companies', "types/name='Prospect'"),
175
+ // =========================================================================
176
+ // COMPANY SITES
177
+ // =========================================================================
178
+ listSub('list_company_sites', CO, 'sites', 'company_id', 'Company Sites', 'List sites for a company'),
179
+ getSub('get_company_site', CO, 'sites', 'company_id', 'Company Sites', 'Get a specific company site'),
180
+ // =========================================================================
181
+ // COMPANY NOTES
182
+ // =========================================================================
183
+ listSub('list_company_notes', CO, 'notes', 'company_id', 'Company Notes', 'List notes for a company'),
184
+ // =========================================================================
185
+ // CONTACTS
186
+ // =========================================================================
187
+ list('list_contacts', CT, 'Contacts', 'List all contacts', { defaultFields: 'id,firstName,lastName,company/name,title,communicationItems' }),
188
+ get('get_contact', CT, 'Contacts', 'Get a specific contact'),
189
+ search('search_contacts', CT, 'Contacts', 'Search contacts with CW conditions'),
190
+ count('count_contacts', CT, 'Contacts', 'Count contacts'),
191
+ listSub('list_contact_communications', CT, 'communications', 'contact_id', 'Contacts', 'List communication items for a contact'),
192
+ listSub('list_contact_notes', CT, 'notes', 'contact_id', 'Contacts', 'List notes for a contact'),
193
+ listSub('list_company_contacts', CO, 'contacts', 'company_id', 'Contacts', 'List contacts for a specific company'),
194
+ // =========================================================================
195
+ // CONFIGURATIONS
196
+ // =========================================================================
197
+ list('list_configurations', CF, 'Configurations', 'List all configurations'),
198
+ get('get_configuration', CF, 'Configurations', 'Get a specific configuration'),
199
+ search('search_configurations', CF, 'Configurations', 'Search configurations with CW conditions'),
200
+ count('count_configurations', CF, 'Configurations', 'Count configurations'),
201
+ companyFiltered('list_company_configurations', CF, 'Configurations', 'List configurations for a specific company'),
202
+ filtered('list_active_configurations', CF, 'Configurations', 'List active configurations', "activeFlag=true"),
203
+ // =========================================================================
204
+ // COMPANY DISCOVERY (reference data)
205
+ // =========================================================================
206
+ discover('discover_company_statuses', '/company/companies/statuses', 'Company Discovery', 'List all company statuses'),
207
+ discover('discover_company_types', '/company/companies/types', 'Company Discovery', 'List all company types'),
208
+ discover('discover_contact_types', '/company/contacts/types', 'Company Discovery', 'List all contact types'),
209
+ discover('discover_contact_departments', '/company/contacts/departments', 'Company Discovery', 'List all contact departments'),
210
+ discover('discover_configuration_types', '/company/configurations/types', 'Company Discovery', 'List all configuration types'),
211
+ discover('discover_configuration_statuses', '/company/configurations/statuses', 'Company Discovery', 'List all configuration statuses'),
212
+ discover('discover_company_groups', '/company/companies/groups', 'Company Discovery', 'List all company groups'),
213
+ discover('discover_company_note_types', '/company/noteTypes', 'Company Discovery', 'List all company note types'),
214
+ discover('discover_contact_relationships', '/company/contacts/relationships', 'Company Discovery', 'List all contact relationship types'),
215
+ discover('discover_contact_groups', '/company/contacts/groups', 'Company Discovery', 'List all contact groups'),
216
+ discover('discover_portal_configurations', '/company/portalConfigurations', 'Company Discovery', 'List all portal configurations'),
217
+ listSub('discover_configuration_type_questions', '/company/configurations/types', 'questions', 'type_id', 'Company Discovery', 'List questions for a configuration type'),
218
+ listSub('list_company_teams', CO, 'teams', 'company_id', 'Company Discovery', 'List teams assigned to a company'),
219
+ // =========================================================================
220
+ // AGREEMENTS
221
+ // =========================================================================
222
+ list('list_agreements', AG, 'Agreements', 'List all agreements', { defaultFields: 'id,name,company/name,type/name,startDate,endDate,cancelledFlag,billAmount' }),
223
+ get('get_agreement', AG, 'Agreements', 'Get a specific agreement'),
224
+ search('search_agreements', AG, 'Agreements', 'Search agreements with CW conditions'),
225
+ count('count_agreements', AG, 'Agreements', 'Count agreements'),
226
+ get('get_agreement_recaps', `${AG}/recaps`, 'Agreements', 'Get agreement billing recaps', { path: `${AG}/{{id}}/recaps`, paginate: true }),
227
+ filtered('list_active_agreements', AG, 'Agreements', 'List active (non-cancelled) agreements', 'cancelledFlag=false'),
228
+ companyFiltered('list_company_agreements', AG, 'Agreements', 'List agreements for a specific company'),
229
+ filtered('list_expiring_agreements', AG, 'Agreements', 'List agreements expiring in the next 60 days', "endDate>=[{{today}}] AND endDate<=[{{daysAhead:60}}] AND cancelledFlag=false"),
230
+ // =========================================================================
231
+ // AGREEMENT DETAILS
232
+ // =========================================================================
233
+ listSub('list_agreement_additions', AG, 'additions', 'agreement_id', 'Agreement Details', 'List additions for an agreement'),
234
+ listSub('list_agreement_adjustments', AG, 'adjustments', 'agreement_id', 'Agreement Details', 'List adjustments for an agreement'),
235
+ listSub('list_agreement_sites', AG, 'sites', 'agreement_id', 'Agreement Details', 'List sites covered by an agreement'),
236
+ listSub('list_agreement_workroles', AG, 'workRoles', 'agreement_id', 'Agreement Details', 'List work roles for an agreement'),
237
+ listSub('list_agreement_worktypes', AG, 'workTypes', 'agreement_id', 'Agreement Details', 'List work types for an agreement'),
238
+ listSub('list_agreement_board_defaults', AG, 'boardDefaults', 'agreement_id', 'Agreement Details', 'List board defaults for an agreement'),
239
+ // =========================================================================
240
+ // INVOICES
241
+ // =========================================================================
242
+ list('list_invoices', INV, 'Invoices', 'List all invoices', { defaultFields: 'id,invoiceNumber,company/name,total,date,status' }),
243
+ get('get_invoice', INV, 'Invoices', 'Get a specific invoice'),
244
+ get('get_invoice_pdf', INV, 'Invoices', 'Get invoice PDF', { path: `${INV}/{{id}}/pdf` }),
245
+ search('search_invoices', INV, 'Invoices', 'Search invoices with CW conditions'),
246
+ count('count_invoices', INV, 'Invoices', 'Count invoices'),
247
+ listSub('list_invoice_payments', INV, 'payments', 'invoice_id', 'Invoices', 'List payments for an invoice'),
248
+ filtered('list_recent_invoices', INV, 'Invoices', 'List invoices from the last 30 days', 'date>=[{{daysAgo:30}}]'),
249
+ companyFiltered('list_company_invoices', INV, 'Invoices', 'List invoices for a specific company'),
250
+ // =========================================================================
251
+ // FINANCE DISCOVERY
252
+ // =========================================================================
253
+ discover('discover_agreement_types', '/finance/agreements/types', 'Finance Discovery', 'List all agreement types'),
254
+ discover('discover_billing_cycles', '/finance/billingCycles', 'Finance Discovery', 'List all billing cycles'),
255
+ discover('discover_billing_terms', '/finance/billingTerms', 'Finance Discovery', 'List all billing terms'),
256
+ discover('discover_tax_codes', '/finance/taxCodes', 'Finance Discovery', 'List all tax codes'),
257
+ discover('discover_gl_accounts', '/finance/glAccounts', 'Finance Discovery', 'List all GL accounts'),
258
+ discover('discover_currencies', '/finance/currencies', 'Finance Discovery', 'List all currencies'),
259
+ discover('discover_invoice_templates', '/finance/invoiceTemplates', 'Finance Discovery', 'List all invoice templates'),
260
+ // =========================================================================
261
+ // ACCOUNTING
262
+ // =========================================================================
263
+ list('list_accounting_batches', '/finance/accounting/batches', 'Accounting', 'List accounting batches'),
264
+ get('get_accounting_batch', '/finance/accounting/batches', 'Accounting', 'Get a specific accounting batch'),
265
+ list('list_unposted_invoices', '/finance/accounting/unpostedinvoices', 'Accounting', 'List unposted invoices'),
266
+ list('list_unposted_expenses', '/finance/accounting/unpostedexpenses', 'Accounting', 'List unposted expenses'),
267
+ list('list_unposted_payments', '/finance/accounting/unpostedpayments', 'Accounting', 'List unposted payments'),
268
+ list('list_gl_entries', '/finance/glAccounts/entries', 'Accounting', 'List GL entries'),
269
+ discover('discover_billing_setups', '/finance/billingSetups', 'Accounting', 'List billing setup configurations'),
270
+ discover('discover_billing_statuses', '/finance/billingStatuses', 'Accounting', 'List billing statuses'),
271
+ discover('discover_delivery_methods', '/finance/deliveryMethods', 'Accounting', 'List delivery methods'),
272
+ count('count_unposted_invoices', '/finance/accounting/unpostedinvoices', 'Accounting', 'Count unposted invoices'),
273
+ count('count_unposted_expenses', '/finance/accounting/unpostedexpenses', 'Accounting', 'Count unposted expenses'),
274
+ count('count_gl_entries', '/finance/glAccounts/entries', 'Accounting', 'Count GL entries'),
275
+ // =========================================================================
276
+ // TIME ENTRIES
277
+ // =========================================================================
278
+ list('list_time_entries', TE, 'Time Entries', 'List all time entries', { defaultFields: 'id,member/identifier,dateEntered,actualHours,notes,company/name,chargeToType,chargeToId' }),
279
+ get('get_time_entry', TE, 'Time Entries', 'Get a specific time entry'),
280
+ search('search_time_entries', TE, 'Time Entries', 'Search time entries with CW conditions'),
281
+ count('count_time_entries', TE, 'Time Entries', 'Count time entries'),
282
+ filtered('list_today_time_entries', TE, 'Time Entries', 'List time entries for today', "dateEntered>=[{{today}}]"),
283
+ memberFiltered('list_member_time_entries', TE, 'Time Entries', 'List time entries for a specific member', 'member/identifier'),
284
+ companyFiltered('list_company_time_entries', TE, 'Time Entries', 'List time entries for a specific company'),
285
+ filtered('list_unbilled_time_entries', TE, 'Time Entries', 'List unbilled time entries', "billableOption='Billable' AND invoiceFlag=false"),
286
+ // =========================================================================
287
+ // TIMESHEETS
288
+ // =========================================================================
289
+ list('list_timesheets', TS, 'Timesheets', 'List all timesheets'),
290
+ get('get_timesheet', TS, 'Timesheets', 'Get a specific timesheet'),
291
+ search('search_timesheets', TS, 'Timesheets', 'Search timesheets with CW conditions'),
292
+ count('count_timesheets', TS, 'Timesheets', 'Count timesheets'),
293
+ filtered('list_pending_timesheets', TS, 'Timesheets', 'List timesheets pending approval', "status='Open'"),
294
+ // =========================================================================
295
+ // STOPWATCHES
296
+ // =========================================================================
297
+ list('list_ticket_stopwatches', '/time/ticketstopwatches', 'Stopwatches', 'List all ticket stopwatches'),
298
+ get('get_ticket_stopwatch', '/time/ticketstopwatches', 'Stopwatches', 'Get a specific ticket stopwatch'),
299
+ list('list_activity_stopwatches', '/time/activitystopwatches', 'Stopwatches', 'List all activity stopwatches'),
300
+ search('search_ticket_stopwatches', '/time/ticketstopwatches', 'Stopwatches', 'Search ticket stopwatches with conditions'),
301
+ filtered('list_running_stopwatches', '/time/ticketstopwatches', 'Stopwatches', 'List currently running stopwatches', "status='Running'"),
302
+ memberFiltered('list_member_stopwatches', '/time/ticketstopwatches', 'Stopwatches', 'List stopwatches for a member', 'member/identifier'),
303
+ // =========================================================================
304
+ // TIME DISCOVERY
305
+ // =========================================================================
306
+ discover('discover_work_roles', '/time/workRoles', 'Time Discovery', 'List all work roles'),
307
+ discover('discover_work_types', '/time/workTypes', 'Time Discovery', 'List all work types'),
308
+ discover('discover_charge_codes', '/time/chargeCodes', 'Time Discovery', 'List all charge codes'),
309
+ discover('discover_time_accruals', '/time/accruals', 'Time Discovery', 'List all time accrual types'),
310
+ listSub('list_time_accrual_details', '/time/accruals', 'details', 'accrual_id', 'Time Discovery', 'List details for a time accrual'),
311
+ discover('discover_work_role_locations', '/time/workRoles/locations', 'Time Discovery', 'List work role location overrides'),
312
+ discover('discover_charge_code_expense_types', '/time/chargeCodes/expenseTypes', 'Time Discovery', 'List charge code expense types'),
313
+ // =========================================================================
314
+ // PROJECTS
315
+ // =========================================================================
316
+ list('list_projects', PR, 'Projects', 'List all projects', { defaultFields: 'id,name,company/name,status/name,manager/identifier,estimatedStart,estimatedEnd,budgetHours,actualHours' }),
317
+ get('get_project', PR, 'Projects', 'Get a specific project'),
318
+ search('search_projects', PR, 'Projects', 'Search projects with CW conditions'),
319
+ count('count_projects', PR, 'Projects', 'Count projects'),
320
+ listSub('list_project_phases', PR, 'phases', 'project_id', 'Projects', 'List phases of a project'),
321
+ filtered('list_active_projects', PR, 'Projects', 'List active projects', "status/name='Open'"),
322
+ companyFiltered('list_company_projects', PR, 'Projects', 'List projects for a specific company'),
323
+ filtered('list_overdue_projects', PR, 'Projects', 'List overdue projects', "status/name='Open' AND estimatedEnd<[{{today}}]"),
324
+ listSub('list_project_billing_rates', PR, 'billingRates', 'project_id', 'Projects', 'List billing rates for a project'),
325
+ // =========================================================================
326
+ // PROJECT TICKETS
327
+ // =========================================================================
328
+ list('list_project_tickets', PT, 'Project Tickets', 'List all project tickets'),
329
+ get('get_project_ticket', PT, 'Project Tickets', 'Get a specific project ticket'),
330
+ search('search_project_tickets', PT, 'Project Tickets', 'Search project tickets with conditions'),
331
+ count('count_project_tickets', PT, 'Project Tickets', 'Count project tickets'),
332
+ listSub('list_project_ticket_notes', PT, 'notes', 'ticket_id', 'Project Tickets', 'List notes for a project ticket'),
333
+ // =========================================================================
334
+ // PROJECT NOTES & TEAM
335
+ // =========================================================================
336
+ listSub('list_project_notes', PR, 'notes', 'project_id', 'Project Notes', 'List notes for a project'),
337
+ listSub('list_project_team_members', PR, 'teamMembers', 'project_id', 'Project Notes', 'List team members on a project'),
338
+ listSub('list_project_contacts', PR, 'contacts', 'project_id', 'Project Notes', 'List contacts associated with a project'),
339
+ // =========================================================================
340
+ // PROJECT DISCOVERY
341
+ // =========================================================================
342
+ discover('discover_project_boards', '/project/boards', 'Project Discovery', 'List all project boards'),
343
+ discover('discover_project_statuses', '/project/statuses', 'Project Discovery', 'List all project statuses'),
344
+ discover('discover_project_types', '/project/types', 'Project Discovery', 'List all project types'),
345
+ list('list_project_templates', '/project/projectTemplates', 'Project Discovery', 'List project templates'),
346
+ get('get_project_template', '/project/projectTemplates', 'Project Discovery', 'Get a specific project template'),
347
+ discover('discover_phase_statuses', '/project/phaseStatuses', 'Project Discovery', 'List all phase statuses'),
348
+ discover('discover_project_security_roles', '/project/securityRoles', 'Project Discovery', 'List project security roles'),
349
+ discover('discover_project_billing_rates', '/project/billingRates', 'Project Discovery', 'List project billing rate definitions'),
350
+ // =========================================================================
351
+ // OPPORTUNITIES
352
+ // =========================================================================
353
+ list('list_opportunities', OP, 'Opportunities', 'List all opportunities', { defaultFields: 'id,name,company/name,status/name,stage/name,expectedCloseDate,totalSalesValue' }),
354
+ get('get_opportunity', OP, 'Opportunities', 'Get a specific opportunity'),
355
+ search('search_opportunities', OP, 'Opportunities', 'Search opportunities with CW conditions'),
356
+ count('count_opportunities', OP, 'Opportunities', 'Count opportunities'),
357
+ companyFiltered('list_company_opportunities', OP, 'Opportunities', 'List opportunities for a company'),
358
+ filtered('list_won_opportunities', OP, 'Opportunities', 'List won opportunities', "status/name='Won'"),
359
+ // =========================================================================
360
+ // OPPORTUNITY DETAILS
361
+ // =========================================================================
362
+ listSub('list_opportunity_contacts', OP, 'contacts', 'opportunity_id', 'Opportunity Details', 'List contacts on an opportunity'),
363
+ listSub('list_opportunity_notes', OP, 'notes', 'opportunity_id', 'Opportunity Details', 'List notes for an opportunity'),
364
+ listSub('list_opportunity_forecasts', OP, 'forecast', 'opportunity_id', 'Opportunity Details', 'List forecasts for an opportunity'),
365
+ listSub('list_opportunity_team', OP, 'team', 'opportunity_id', 'Opportunity Details', 'List team members on an opportunity'),
366
+ // =========================================================================
367
+ // SALES ACTIVITIES
368
+ // =========================================================================
369
+ list('list_activities', ACT, 'Sales Activities', 'List all sales activities'),
370
+ get('get_activity', ACT, 'Sales Activities', 'Get a specific activity'),
371
+ search('search_activities', ACT, 'Sales Activities', 'Search activities with CW conditions'),
372
+ count('count_activities', ACT, 'Sales Activities', 'Count activities'),
373
+ memberFiltered('list_activity_stopwatches_by_member', '/time/activitystopwatches', 'Sales Activities', 'List activity stopwatches for a member', 'member/identifier'),
374
+ // =========================================================================
375
+ // SALES ORDERS
376
+ // =========================================================================
377
+ list('list_orders', ORD, 'Sales Orders', 'List all sales orders'),
378
+ get('get_order', ORD, 'Sales Orders', 'Get a specific sales order'),
379
+ search('search_orders', ORD, 'Sales Orders', 'Search orders with CW conditions'),
380
+ count('count_orders', ORD, 'Sales Orders', 'Count orders'),
381
+ listSub('list_order_line_items', ORD, 'lineItems', 'order_id', 'Sales Orders', 'List line items for an order'),
382
+ // =========================================================================
383
+ // SALES DISCOVERY
384
+ // =========================================================================
385
+ discover('discover_opportunity_types', '/sales/opportunities/types', 'Sales Discovery', 'List all opportunity types'),
386
+ discover('discover_opportunity_statuses', '/sales/opportunities/statuses', 'Sales Discovery', 'List all opportunity statuses'),
387
+ discover('discover_opportunity_stages', '/sales/stages', 'Sales Discovery', 'List all opportunity stages'),
388
+ discover('discover_opportunity_ratings', '/sales/opportunities/ratings', 'Sales Discovery', 'List all opportunity ratings'),
389
+ discover('discover_activity_types', '/sales/activities/types', 'Sales Discovery', 'List all activity types'),
390
+ discover('discover_activity_statuses', '/sales/activities/statuses', 'Sales Discovery', 'List all activity statuses'),
391
+ discover('discover_order_statuses', '/sales/orders/statuses', 'Sales Discovery', 'List all order statuses'),
392
+ discover('discover_sales_probabilities', '/sales/probabilities', 'Sales Discovery', 'List all sales probabilities'),
393
+ discover('discover_sales_roles', '/sales/roles', 'Sales Discovery', 'List all sales roles'),
394
+ // =========================================================================
395
+ // SCHEDULE
396
+ // =========================================================================
397
+ list('list_schedule_entries', SCH, 'Schedule', 'List all schedule entries'),
398
+ get('get_schedule_entry', SCH, 'Schedule', 'Get a specific schedule entry'),
399
+ list('list_calendars', CAL, 'Schedule', 'List all calendars'),
400
+ memberFiltered('list_member_schedule', SCH, 'Schedule', 'List schedule entries for a member', 'member/identifier'),
401
+ memberFiltered('list_member_today_schedule', SCH, 'Schedule', "List today's schedule for a member", 'member/identifier', { defaultConditions: "member/identifier='{{identifier}}' AND dateStart>=[{{today}}] AND dateEnd<=[{{tomorrow}}]" }),
402
+ filtered('list_upcoming_schedule', SCH, 'Schedule', 'List upcoming schedule entries', "dateStart>=[{{today}}]"),
403
+ // =========================================================================
404
+ // SCHEDULE REFERENCE
405
+ // =========================================================================
406
+ get('get_calendar', CAL, 'Schedule Reference', 'Get a specific calendar'),
407
+ list('list_holiday_lists', '/schedule/holidayLists', 'Schedule Reference', 'List all holiday lists'),
408
+ listSub('list_holidays', '/schedule/holidayLists', 'holidays', 'list_id', 'Schedule Reference', 'List holidays in a holiday list'),
409
+ discover('discover_schedule_types', '/schedule/types', 'Schedule Reference', 'List all schedule entry types'),
410
+ discover('discover_schedule_statuses', '/schedule/statuses', 'Schedule Reference', 'List all schedule statuses'),
411
+ count('list_schedule_entry_count', SCH, 'Schedule Reference', 'Count schedule entries'),
412
+ search('search_schedule_entries', SCH, 'Schedule Reference', 'Search schedule entries with conditions'),
413
+ // =========================================================================
414
+ // PROCUREMENT
415
+ // =========================================================================
416
+ list('list_catalog_items', CAT, 'Procurement', 'List all catalog items'),
417
+ get('get_catalog_item', CAT, 'Procurement', 'Get a specific catalog item'),
418
+ search('search_catalog_items', CAT, 'Procurement', 'Search catalog items with conditions'),
419
+ count('count_catalog_items', CAT, 'Procurement', 'Count catalog items'),
420
+ list('list_purchase_orders', PO, 'Procurement', 'List all purchase orders'),
421
+ get('get_purchase_order', PO, 'Procurement', 'Get a specific purchase order'),
422
+ search('search_purchase_orders', PO, 'Procurement', 'Search purchase orders with conditions'),
423
+ count('count_purchase_orders', PO, 'Procurement', 'Count purchase orders'),
424
+ listSub('list_po_line_items', PO, 'lineItems', 'po_id', 'Procurement', 'List line items for a purchase order'),
425
+ filtered('list_open_purchase_orders', PO, 'Procurement', 'List open purchase orders', "status/name='Open'"),
426
+ { ...filtered('list_vendor_purchase_orders', PO, 'Procurement', 'List purchase orders for a vendor', "vendorCompany/id={{vendor_id}}"), params: { vendor_id: { description: 'Vendor company ID', required: true, type: 'number' } } },
427
+ discover('discover_product_types', '/procurement/types', 'Procurement', 'List all product types'),
428
+ discover('discover_manufacturers', '/procurement/manufacturers', 'Procurement', 'List all manufacturers'),
429
+ discover('discover_warehouses', '/procurement/warehouses', 'Procurement', 'List all warehouses'),
430
+ discover('discover_procurement_categories', '/procurement/categories', 'Procurement', 'List all procurement categories'),
431
+ listSub('discover_procurement_subcategories', '/procurement/categories', 'subcategories', 'category_id', 'Procurement', 'List subcategories for a procurement category'),
432
+ discover('discover_shipment_methods', '/procurement/shipmentMethods', 'Procurement', 'List all shipment methods'),
433
+ discover('discover_unit_of_measures', '/procurement/unitOfMeasures', 'Procurement', 'List all units of measure'),
434
+ discover('discover_po_statuses', '/procurement/purchaseorders/statuses', 'Procurement', 'List all PO statuses'),
435
+ // =========================================================================
436
+ // EXPENSES
437
+ // =========================================================================
438
+ list('list_expense_entries', EXE, 'Expenses', 'List all expense entries'),
439
+ get('get_expense_entry', EXE, 'Expenses', 'Get a specific expense entry'),
440
+ search('search_expense_entries', EXE, 'Expenses', 'Search expense entries with conditions'),
441
+ count('count_expense_entries', EXE, 'Expenses', 'Count expense entries'),
442
+ list('list_expense_reports', EXR, 'Expenses', 'List all expense reports'),
443
+ get('get_expense_report', EXR, 'Expenses', 'Get a specific expense report'),
444
+ search('search_expense_reports', EXR, 'Expenses', 'Search expense reports with conditions'),
445
+ count('count_expense_reports', EXR, 'Expenses', 'Count expense reports'),
446
+ memberFiltered('list_member_expenses', EXE, 'Expenses', 'List expenses for a specific member', 'member/identifier'),
447
+ filtered('list_pending_expense_reports', EXR, 'Expenses', 'List pending expense reports', "status/name='Open'"),
448
+ discover('discover_expense_types', '/expense/types', 'Expenses', 'List all expense types'),
449
+ // =========================================================================
450
+ // MARKETING
451
+ // =========================================================================
452
+ list('list_campaigns', MKT, 'Marketing', 'List all marketing campaigns'),
453
+ get('get_campaign', MKT, 'Marketing', 'Get a specific campaign'),
454
+ search('search_campaigns', MKT, 'Marketing', 'Search campaigns with conditions'),
455
+ count('count_campaigns', MKT, 'Marketing', 'Count campaigns'),
456
+ filtered('list_active_campaigns', MKT, 'Marketing', 'List active campaigns', "inactiveFlag=false"),
457
+ listSub('list_campaign_activities', MKT, 'activities', 'campaign_id', 'Marketing', 'List activities for a campaign'),
458
+ list('list_marketing_groups', MKG, 'Marketing', 'List all marketing groups'),
459
+ get('get_marketing_group', MKG, 'Marketing', 'Get a specific marketing group'),
460
+ listSub('list_marketing_group_companies', MKG, 'companies', 'group_id', 'Marketing', 'List companies in a marketing group'),
461
+ listSub('list_marketing_group_contacts', MKG, 'contacts', 'group_id', 'Marketing', 'List contacts in a marketing group'),
462
+ discover('discover_campaign_types', '/marketing/campaigns/types', 'Marketing', 'List all campaign types'),
463
+ discover('discover_campaign_subtypes', '/marketing/campaigns/subTypes', 'Marketing', 'List all campaign subtypes'),
464
+ discover('discover_campaign_statuses', '/marketing/campaigns/statuses', 'Marketing', 'List all campaign statuses'),
465
+ // =========================================================================
466
+ // MEMBERS
467
+ // =========================================================================
468
+ list('list_members', MEM, 'Members', 'List all PSA members', { defaultFields: 'id,identifier,firstName,lastName,title,officeEmail,defaultDepartment/name,inactiveFlag' }),
469
+ get('get_member', MEM, 'Members', 'Get a specific member'),
470
+ search('search_members', MEM, 'Members', 'Search members with CW conditions'),
471
+ discover('get_system_info', '/system/info', 'Members', 'Get ConnectWise system info (version, cloud instance)'),
472
+ list('list_departments', DEP, 'Members', 'List all departments'),
473
+ filtered('list_active_members', MEM, 'Members', 'List active members', 'inactiveFlag=false'),
474
+ { ...filtered('list_department_members', MEM, 'Members', 'List members in a department', "defaultDepartment/id={{department_id}} AND inactiveFlag=false"), params: { department_id: { description: 'Department ID', required: true, type: 'number' } } },
475
+ listSub('list_member_skills', MEM, 'skills', 'member_id', 'Members', 'List skills for a member'),
476
+ listSub('list_member_certifications', MEM, 'certifications', 'member_id', 'Members', 'List certifications for a member'),
477
+ listSub('list_member_delegations', MEM, 'delegations', 'member_id', 'Members', 'List delegations for a member'),
478
+ list('list_skills', '/system/skills', 'Members', 'List all available skills'),
479
+ list('list_certifications', '/system/certifications', 'Members', 'List all available certifications'),
480
+ // =========================================================================
481
+ // SYSTEM
482
+ // =========================================================================
483
+ list('list_locations', LOC, 'System', 'List all system locations'),
484
+ list('list_documents', DOC, 'System', 'List all system documents'),
485
+ get('get_document', DOC, 'System', 'Get a specific document'),
486
+ search('search_documents', DOC, 'System', 'Search documents with CW conditions'),
487
+ // =========================================================================
488
+ // WORKFLOWS
489
+ // =========================================================================
490
+ list('list_workflows', WF, 'Workflows', 'List all workflows'),
491
+ get('get_workflow', WF, 'Workflows', 'Get a specific workflow'),
492
+ listSub('list_workflow_events', WF, 'events', 'workflow_id', 'Workflows', 'List events for a workflow'),
493
+ listSub('list_workflow_triggers', WF, 'triggers', 'workflow_id', 'Workflows', 'List triggers for a workflow'),
494
+ search('search_workflows', WF, 'Workflows', 'Search workflows with CW conditions'),
495
+ // =========================================================================
496
+ // SYSTEM UTILITIES
497
+ // =========================================================================
498
+ list('list_user_defined_fields', '/system/userDefinedFields', 'System Utilities', 'List all user-defined fields'),
499
+ list('list_callbacks', '/system/callbacks', 'System Utilities', 'List all API callbacks'),
500
+ list('list_audit_trail', '/system/audittrail', 'System Utilities', 'List audit trail entries'),
501
+ list('list_security_roles', '/system/securityRoles', 'System Utilities', 'List all security roles'),
502
+ list('list_custom_reports', '/system/reports', 'System Utilities', 'List all custom reports'),
503
+ list('list_standard_notes', '/system/standardNotes', 'System Utilities', 'List all standard notes'),
504
+ search('search_user_defined_fields', '/system/userDefinedFields', 'System Utilities', 'Search UDFs with conditions'),
505
+ search('search_audit_trail', '/system/audittrail', 'System Utilities', 'Search audit trail with conditions'),
506
+ filtered('list_recent_audit_entries', '/system/audittrail', 'System Utilities', 'List recent audit trail entries (7 days)', "enteredDate>=[{{daysAgo:7}}]"),
507
+ filtered('list_active_callbacks', '/system/callbacks', 'System Utilities', 'List active callbacks', "inactiveFlag=false"),
508
+ count('count_callbacks', '/system/callbacks', 'System Utilities', 'Count callbacks'),
509
+ // =========================================================================
510
+ // ANALYTICS (computed from API data — handler-based)
511
+ // =========================================================================
512
+ analytics('stale_ticket_report', 'Analytics', 'Find tickets with no updates in the last N days', 'stale_ticket_report', 'free', { params: { days: { description: 'Number of days without update (default: 14)', type: 'number' } } }),
513
+ analytics('ticket_volume_trends', 'Analytics', 'Show ticket creation trends over the last N days', 'ticket_volume_trends', 'free', { params: { days: { description: 'Number of days to analyze (default: 30)', type: 'number' } } }),
514
+ analytics('open_ticket_age_distribution', 'Analytics', 'Distribution of open tickets by age brackets', 'open_ticket_age_distribution', 'free'),
515
+ analytics('team_workload_balance', 'Analytics', 'Compare open ticket counts across team members', 'team_workload_balance', 'free'),
516
+ analytics('ticket_resolution_analytics', 'Analytics', 'Average resolution times by board and priority', 'ticket_resolution_analytics', 'free', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
517
+ analytics('board_performance_comparison', 'Analytics', 'Compare metrics across service boards', 'board_performance_comparison', 'free'),
518
+ analytics('ticket_source_analysis', 'Analytics', 'Breakdown of tickets by source', 'ticket_source_analysis', 'free', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
519
+ analytics('first_response_time_analytics', 'Analytics', 'Measure first response times across the team', 'first_response_time_analytics', 'free', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
520
+ analytics('open_ticket_summary', 'Analytics', 'Summary of open tickets by board, priority, and company', 'open_ticket_summary', 'free'),
521
+ analytics('unassigned_ticket_report', 'Analytics', 'All unassigned tickets with age and priority', 'unassigned_ticket_report', 'free'),
522
+ analytics('ticket_aging_report', 'Analytics', 'Detailed aging report for open tickets', 'ticket_aging_report', 'free'),
523
+ analytics('priority_distribution', 'Analytics', 'Distribution of tickets by priority level', 'priority_distribution', 'free'),
524
+ ];
525
+ // ===========================================================================
526
+ // PRO TIER — 150 OPERATIONS
527
+ // ===========================================================================
528
+ const proOps = [
529
+ // =========================================================================
530
+ // TICKET COMMANDS
531
+ // =========================================================================
532
+ create('create_ticket', T, 'Ticket Commands', 'Create a new service ticket'),
533
+ update('update_ticket', T, 'Ticket Commands', 'Update an existing ticket'),
534
+ remove('delete_ticket', T, 'Ticket Commands', 'Delete a ticket'),
535
+ createSub('create_ticket_note', T, 'notes', 'ticket_id', 'Ticket Commands', 'Add a note to a ticket'),
536
+ updateSub('update_ticket_note', T, 'notes', 'ticket_id', 'Ticket Commands', 'Update a ticket note'),
537
+ removeSub('delete_ticket_note', T, 'notes', 'ticket_id', 'Ticket Commands', 'Delete a ticket note'),
538
+ createSub('create_ticket_task', T, 'tasks', 'ticket_id', 'Ticket Commands', 'Add a task to a ticket'),
539
+ updateSub('update_ticket_task', T, 'tasks', 'ticket_id', 'Ticket Commands', 'Update a ticket task'),
540
+ removeSub('delete_ticket_task', T, 'tasks', 'ticket_id', 'Ticket Commands', 'Delete a ticket task'),
541
+ createSub('add_ticket_config', T, 'configurations', 'ticket_id', 'Ticket Commands', 'Link a configuration to a ticket'),
542
+ removeSub('remove_ticket_config', T, 'configurations', 'ticket_id', 'Ticket Commands', 'Unlink a configuration from a ticket'),
543
+ create('create_ticket_stopwatch', '/time/ticketstopwatches', 'Ticket Commands', 'Create a stopwatch for a ticket'),
544
+ update('update_ticket_stopwatch', '/time/ticketstopwatches', 'Ticket Commands', 'Update a ticket stopwatch'),
545
+ // =========================================================================
546
+ // COMPANY COMMANDS
547
+ // =========================================================================
548
+ create('create_company', CO, 'Company Commands', 'Create a new company'),
549
+ update('update_company', CO, 'Company Commands', 'Update a company'),
550
+ remove('delete_company', CO, 'Company Commands', 'Delete a company'),
551
+ { ...create('merge_companies', `${CO}/merge`, 'Company Commands', 'Merge two companies'), description: 'Merge source company into target company' },
552
+ createSub('create_company_site', CO, 'sites', 'company_id', 'Company Commands', 'Add a site to a company'),
553
+ updateSub('update_company_site', CO, 'sites', 'company_id', 'Company Commands', 'Update a company site'),
554
+ createSub('create_company_note', CO, 'notes', 'company_id', 'Company Commands', 'Add a note to a company'),
555
+ updateSub('update_company_note', CO, 'notes', 'company_id', 'Company Commands', 'Update a company note'),
556
+ // =========================================================================
557
+ // CONTACT COMMANDS
558
+ // =========================================================================
559
+ create('create_contact', CT, 'Contact Commands', 'Create a new contact'),
560
+ update('update_contact', CT, 'Contact Commands', 'Update a contact'),
561
+ remove('delete_contact', CT, 'Contact Commands', 'Delete a contact'),
562
+ createSub('create_contact_communication', CT, 'communications', 'contact_id', 'Contact Commands', 'Add a communication item to a contact'),
563
+ createSub('create_contact_note', CT, 'notes', 'contact_id', 'Contact Commands', 'Add a note to a contact'),
564
+ // =========================================================================
565
+ // CONFIGURATION COMMANDS
566
+ // =========================================================================
567
+ create('create_configuration', CF, 'Configuration Commands', 'Create a new configuration'),
568
+ update('update_configuration', CF, 'Configuration Commands', 'Update a configuration'),
569
+ remove('delete_configuration', CF, 'Configuration Commands', 'Delete a configuration'),
570
+ // =========================================================================
571
+ // AGREEMENT COMMANDS
572
+ // =========================================================================
573
+ create('create_agreement', AG, 'Agreement Commands', 'Create a new agreement'),
574
+ update('update_agreement', AG, 'Agreement Commands', 'Update an agreement'),
575
+ remove('delete_agreement', AG, 'Agreement Commands', 'Delete an agreement'),
576
+ { ...create('copy_agreement', AG, 'Agreement Commands', 'Copy an existing agreement'), path: `${AG}/{{id}}/copy`, method: 'POST', params: { ...idParam, body: { description: 'Copy options (JSON)', required: false, type: 'string' } } },
577
+ createSub('create_agreement_addition', AG, 'additions', 'agreement_id', 'Agreement Commands', 'Add an addition to an agreement'),
578
+ updateSub('update_agreement_addition', AG, 'additions', 'agreement_id', 'Agreement Commands', 'Update an agreement addition'),
579
+ createSub('create_agreement_adjustment', AG, 'adjustments', 'agreement_id', 'Agreement Commands', 'Add an adjustment to an agreement'),
580
+ createSub('create_agreement_site', AG, 'sites', 'agreement_id', 'Agreement Commands', 'Add a site to an agreement'),
581
+ createSub('create_agreement_workrole', AG, 'workRoles', 'agreement_id', 'Agreement Commands', 'Add a work role to an agreement'),
582
+ createSub('create_agreement_worktype', AG, 'workTypes', 'agreement_id', 'Agreement Commands', 'Add a work type to an agreement'),
583
+ // =========================================================================
584
+ // INVOICE COMMANDS
585
+ // =========================================================================
586
+ create('create_invoice', INV, 'Invoice Commands', 'Create a new invoice'),
587
+ update('update_invoice', INV, 'Invoice Commands', 'Update an invoice'),
588
+ createSub('create_invoice_payment', INV, 'payments', 'invoice_id', 'Invoice Commands', 'Record a payment on an invoice'),
589
+ // =========================================================================
590
+ // ACCOUNTING COMMANDS
591
+ // =========================================================================
592
+ create('create_accounting_batch', '/finance/accounting/batches', 'Accounting Commands', 'Create a new accounting batch'),
593
+ // =========================================================================
594
+ // TIME COMMANDS
595
+ // =========================================================================
596
+ create('create_time_entry', TE, 'Time Commands', 'Create a new time entry'),
597
+ update('update_time_entry', TE, 'Time Commands', 'Update a time entry'),
598
+ remove('delete_time_entry', TE, 'Time Commands', 'Delete a time entry'),
599
+ // =========================================================================
600
+ // PROJECT COMMANDS
601
+ // =========================================================================
602
+ create('create_project', PR, 'Project Commands', 'Create a new project'),
603
+ update('update_project', PR, 'Project Commands', 'Update a project'),
604
+ remove('delete_project', PR, 'Project Commands', 'Delete a project'),
605
+ createSub('create_project_phase', PR, 'phases', 'project_id', 'Project Commands', 'Add a phase to a project'),
606
+ updateSub('update_project_phase', PR, 'phases', 'project_id', 'Project Commands', 'Update a project phase'),
607
+ createSub('create_project_note', PR, 'notes', 'project_id', 'Project Commands', 'Add a note to a project'),
608
+ createSub('create_project_team_member', PR, 'teamMembers', 'project_id', 'Project Commands', 'Add a team member to a project'),
609
+ create('create_project_ticket', PT, 'Project Commands', 'Create a project ticket'),
610
+ update('update_project_ticket', PT, 'Project Commands', 'Update a project ticket'),
611
+ createSub('create_project_ticket_note', PT, 'notes', 'ticket_id', 'Project Commands', 'Add a note to a project ticket'),
612
+ // =========================================================================
613
+ // OPPORTUNITY COMMANDS
614
+ // =========================================================================
615
+ create('create_opportunity', OP, 'Opportunity Commands', 'Create a new opportunity'),
616
+ update('update_opportunity', OP, 'Opportunity Commands', 'Update an opportunity'),
617
+ remove('delete_opportunity', OP, 'Opportunity Commands', 'Delete an opportunity'),
618
+ { ...create('convert_opportunity_to_agreement', OP, 'Opportunity Commands', 'Convert opportunity to agreement'), path: `${OP}/{{id}}/convertToAgreement`, params: { ...idParam }, confirmRequired: true },
619
+ { ...create('convert_opportunity_to_project', OP, 'Opportunity Commands', 'Convert opportunity to project'), path: `${OP}/{{id}}/convertToProject`, params: { ...idParam }, confirmRequired: true },
620
+ { ...create('convert_opportunity_to_sales_order', OP, 'Opportunity Commands', 'Convert opportunity to sales order'), path: `${OP}/{{id}}/convertToSalesOrder`, params: { ...idParam }, confirmRequired: true },
621
+ { ...create('convert_opportunity_to_ticket', OP, 'Opportunity Commands', 'Convert opportunity to service ticket'), path: `${OP}/{{id}}/convertToServiceTicket`, params: { ...idParam }, confirmRequired: true },
622
+ createSub('create_opportunity_contact', OP, 'contacts', 'opportunity_id', 'Opportunity Commands', 'Add a contact to an opportunity'),
623
+ createSub('create_opportunity_note', OP, 'notes', 'opportunity_id', 'Opportunity Commands', 'Add a note to an opportunity'),
624
+ createSub('create_opportunity_team_member', OP, 'team', 'opportunity_id', 'Opportunity Commands', 'Add a team member to an opportunity'),
625
+ // =========================================================================
626
+ // SALES COMMANDS
627
+ // =========================================================================
628
+ create('create_activity', ACT, 'Sales Commands', 'Create a new sales activity'),
629
+ update('update_activity', ACT, 'Sales Commands', 'Update a sales activity'),
630
+ remove('delete_activity', ACT, 'Sales Commands', 'Delete a sales activity'),
631
+ create('create_order', ORD, 'Sales Commands', 'Create a new sales order'),
632
+ update('update_order', ORD, 'Sales Commands', 'Update a sales order'),
633
+ createSub('create_order_line_item', ORD, 'lineItems', 'order_id', 'Sales Commands', 'Add a line item to a sales order'),
634
+ // =========================================================================
635
+ // SCHEDULE COMMANDS
636
+ // =========================================================================
637
+ create('create_schedule_entry', SCH, 'Schedule Commands', 'Create a new schedule entry'),
638
+ update('update_schedule_entry', SCH, 'Schedule Commands', 'Update a schedule entry'),
639
+ remove('delete_schedule_entry', SCH, 'Schedule Commands', 'Delete a schedule entry'),
640
+ // =========================================================================
641
+ // PROCUREMENT COMMANDS
642
+ // =========================================================================
643
+ create('create_catalog_item', CAT, 'Procurement Commands', 'Create a new catalog item'),
644
+ update('update_catalog_item', CAT, 'Procurement Commands', 'Update a catalog item'),
645
+ remove('delete_catalog_item', CAT, 'Procurement Commands', 'Delete a catalog item'),
646
+ create('create_purchase_order', PO, 'Procurement Commands', 'Create a new purchase order'),
647
+ update('update_purchase_order', PO, 'Procurement Commands', 'Update a purchase order'),
648
+ remove('delete_purchase_order', PO, 'Procurement Commands', 'Delete a purchase order'),
649
+ createSub('create_po_line_item', PO, 'lineItems', 'po_id', 'Procurement Commands', 'Add a line item to a purchase order'),
650
+ // =========================================================================
651
+ // EXPENSE COMMANDS
652
+ // =========================================================================
653
+ create('create_expense_entry', EXE, 'Expense Commands', 'Create a new expense entry'),
654
+ update('update_expense_entry', EXE, 'Expense Commands', 'Update an expense entry'),
655
+ remove('delete_expense_entry', EXE, 'Expense Commands', 'Delete an expense entry'),
656
+ // =========================================================================
657
+ // MARKETING COMMANDS
658
+ // =========================================================================
659
+ create('create_campaign', MKT, 'Marketing Commands', 'Create a new campaign'),
660
+ update('update_campaign', MKT, 'Marketing Commands', 'Update a campaign'),
661
+ remove('delete_campaign', MKT, 'Marketing Commands', 'Delete a campaign'),
662
+ // =========================================================================
663
+ // KB COMMANDS
664
+ // =========================================================================
665
+ create('create_kb_article', '/service/knowledgeBaseArticles', 'KB Commands', 'Create a new KB article'),
666
+ update('update_kb_article', '/service/knowledgeBaseArticles', 'KB Commands', 'Update a KB article'),
667
+ // =========================================================================
668
+ // SYSTEM COMMANDS
669
+ // =========================================================================
670
+ create('create_callback', '/system/callbacks', 'System Commands', 'Create a new API callback'),
671
+ remove('delete_callback', '/system/callbacks', 'System Commands', 'Delete an API callback'),
672
+ // =========================================================================
673
+ // ADVANCED ANALYTICS (Pro)
674
+ // =========================================================================
675
+ analytics('sla_compliance_dashboard', 'Advanced Analytics', 'SLA compliance rates by board and priority', 'sla_compliance_dashboard', 'pro'),
676
+ analytics('sla_breach_alerts', 'Advanced Analytics', 'Tickets at risk of or already breaching SLA', 'sla_breach_alerts', 'pro'),
677
+ analytics('member_utilization_report', 'Advanced Analytics', 'Billable vs non-billable hours by member', 'member_utilization_report', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
678
+ analytics('timesheet_summary', 'Advanced Analytics', 'Timesheet hours summary by member and status', 'timesheet_summary', 'pro'),
679
+ analytics('dispatch_optimizer', 'Advanced Analytics', 'Suggest optimal ticket assignments based on workload and skills', 'dispatch_optimizer', 'pro'),
680
+ analytics('unbilled_time_report', 'Advanced Analytics', 'All unbilled time entries with totals by company', 'unbilled_time_report', 'pro'),
681
+ analytics('agreement_utilization_tracker', 'Advanced Analytics', 'Track hours used vs budgeted per agreement', 'agreement_utilization_tracker', 'pro'),
682
+ analytics('csat_report', 'Advanced Analytics', 'Customer satisfaction scores from surveys', 'csat_report', 'pro', { params: { days: { description: 'Days to look back (default: 90)', type: 'number' } } }),
683
+ analytics('repeat_caller_analysis', 'Advanced Analytics', 'Contacts who create the most tickets', 'repeat_caller_analysis', 'pro', { params: { days: { description: 'Days to look back (default: 90)', type: 'number' } } }),
684
+ analytics('ticket_category_trends', 'Advanced Analytics', 'Ticket volume trends by type and subtype', 'ticket_category_trends', 'pro'),
685
+ analytics('time_entry_anomaly_detection', 'Advanced Analytics', 'Detect unusual time entry patterns', 'time_entry_anomaly_detection', 'pro'),
686
+ analytics('billing_leakage_detection', 'Advanced Analytics', 'Find billable work that was not invoiced', 'billing_leakage_detection', 'pro'),
687
+ analytics('project_burn_rate', 'Advanced Analytics', 'Project hours consumed vs budget over time', 'project_burn_rate', 'pro', { params: { project_id: { description: 'Project ID', required: true, type: 'number' } } }),
688
+ analytics('agreement_renewal_forecast', 'Advanced Analytics', 'Agreements approaching renewal with revenue impact', 'agreement_renewal_forecast', 'pro'),
689
+ analytics('resource_capacity_planning', 'Advanced Analytics', 'Available capacity per member based on schedule vs time logged', 'resource_capacity_planning', 'pro'),
690
+ analytics('mean_time_metrics', 'Advanced Analytics', 'MTTR, MTTA, and MTTF metrics by board', 'mean_time_metrics', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
691
+ analytics('cross_board_ticket_flow', 'Advanced Analytics', 'Track tickets that moved between boards', 'cross_board_ticket_flow', 'pro'),
692
+ analytics('top_time_consumers', 'Advanced Analytics', 'Tickets and companies consuming the most hours', 'top_time_consumers', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
693
+ analytics('agreement_coverage_gaps', 'Advanced Analytics', 'Companies with expired or missing agreements', 'agreement_coverage_gaps', 'pro'),
694
+ analytics('weekend_work_report', 'Advanced Analytics', 'Time entries logged on weekends', 'weekend_work_report', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
695
+ analytics('overdue_ticket_report', 'Advanced Analytics', 'Tickets past their due date', 'overdue_ticket_report', 'pro'),
696
+ // =========================================================================
697
+ // INTELLIGENCE (Pro)
698
+ // =========================================================================
699
+ analytics('ticket_triage_assist', 'Intelligence', 'Suggest priority, board, and assignee for a ticket', 'ticket_triage_assist', 'pro', { params: { ticket_id: { description: 'Ticket ID to triage', required: true, type: 'number' } } }),
700
+ analytics('company_360_view', 'Intelligence', 'Complete company overview: contacts, tickets, agreements, configs, time', 'company_360_view', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
701
+ analytics('company_risk_assessment', 'Intelligence', 'Assess company health based on ticket volume, SLA, and billing', 'company_risk_assessment', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
702
+ analytics('daily_operations_briefing', 'Intelligence', 'Daily summary: new tickets, SLA status, team workload', 'daily_operations_briefing', 'pro'),
703
+ analytics('revenue_leakage_scan', 'Intelligence', 'Detect unbilled hours, expired agreements, and missed charges', 'revenue_leakage_scan', 'pro'),
704
+ analytics('recurring_issue_detector', 'Intelligence', 'Find tickets with similar summaries for the same company', 'recurring_issue_detector', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
705
+ analytics('company_onboarding_checklist', 'Intelligence', 'Check onboarding completion: contacts, configs, agreements, site', 'company_onboarding_checklist', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
706
+ analytics('knowledge_article_recommendation', 'Intelligence', 'Suggest KB articles based on ticket content', 'knowledge_article_recommendation', 'pro', { params: { ticket_id: { description: 'Ticket ID', required: true, type: 'number' } } }),
707
+ analytics('similar_ticket_finder', 'Intelligence', 'Find tickets with similar summaries', 'similar_ticket_finder', 'pro', { params: { ticket_id: { description: 'Ticket ID', required: true, type: 'number' } } }),
708
+ analytics('escalation_risk_scorer', 'Intelligence', 'Score tickets by escalation risk based on age, priority, and activity', 'escalation_risk_scorer', 'pro'),
709
+ analytics('schedule_gap_analysis', 'Intelligence', 'Find scheduling gaps and conflicts for team members', 'schedule_gap_analysis', 'pro', { params: { days: { description: 'Days ahead to check (default: 14)', type: 'number' } } }),
710
+ analytics('after_hours_impact_report', 'Intelligence', 'Analyze after-hours ticket creation and response patterns', 'after_hours_impact_report', 'pro'),
711
+ analytics('proactive_maintenance_alerts', 'Intelligence', 'Configurations due for maintenance based on ticket history', 'proactive_maintenance_alerts', 'pro'),
712
+ analytics('resource_skill_matcher', 'Intelligence', 'Match tickets to members based on skills and certifications', 'resource_skill_matcher', 'pro', { params: { ticket_id: { description: 'Ticket ID to match', required: true, type: 'number' } } }),
713
+ analytics('configuration_lifecycle_tracker', 'Intelligence', 'Track configuration age, ticket history, and replacement forecasts', 'configuration_lifecycle_tracker', 'pro', { params: { company_id: { description: 'Company ID', type: 'number' } } }),
714
+ analytics('time_prediction_model', 'Intelligence', 'Predict time needed for a ticket based on historical similar tickets', 'time_prediction_model', 'pro', { params: { ticket_id: { description: 'Ticket ID', required: true, type: 'number' } } }),
715
+ // =========================================================================
716
+ // PREMIUM ANALYTICS (Pro)
717
+ // =========================================================================
718
+ analytics('agreement_profitability_analysis', 'Premium Analytics', 'Revenue vs cost analysis per agreement', 'agreement_profitability_analysis', 'pro'),
719
+ analytics('client_health_score', 'Premium Analytics', 'Composite health score based on tickets, SLA, billing, and engagement', 'client_health_score', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
720
+ analytics('effective_hourly_rate', 'Premium Analytics', 'Actual revenue per hour by member and agreement', 'effective_hourly_rate', 'pro', { params: { days: { description: 'Days to look back (default: 90)', type: 'number' } } }),
721
+ analytics('qbr_data_pack', 'Premium Analytics', 'Quarterly business review data package for a company', 'qbr_data_pack', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' }, quarter: { description: 'Quarter (1-4)', type: 'number' }, year: { description: 'Year (e.g. 2026)', type: 'number' } } }),
722
+ analytics('pipeline_revenue_forecast', 'Premium Analytics', 'Forecast revenue from open opportunities by probability', 'pipeline_revenue_forecast', 'pro'),
723
+ analytics('project_portfolio_health', 'Premium Analytics', 'Health overview of all active projects: budget, timeline, burn rate', 'project_portfolio_health', 'pro'),
724
+ analytics('revenue_per_employee', 'Premium Analytics', 'Revenue generated per team member', 'revenue_per_employee', 'pro', { params: { days: { description: 'Days to look back (default: 90)', type: 'number' } } }),
725
+ analytics('service_margin_analysis', 'Premium Analytics', 'Service delivery costs vs revenue by agreement type', 'service_margin_analysis', 'pro'),
726
+ analytics('customer_lifetime_value', 'Premium Analytics', 'Estimated lifetime value based on agreement and invoice history', 'customer_lifetime_value', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
727
+ analytics('company_offboarding_audit', 'Premium Analytics', 'Check for open items when offboarding a company', 'company_offboarding_audit', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
728
+ analytics('procurement_spend_analysis', 'Premium Analytics', 'Analyze procurement spending by vendor and category', 'procurement_spend_analysis', 'pro', { params: { days: { description: 'Days to look back (default: 365)', type: 'number' } } }),
729
+ analytics('monthly_recurring_revenue', 'Premium Analytics', 'Calculate MRR from active agreements', 'monthly_recurring_revenue', 'pro'),
730
+ analytics('client_retention_analysis', 'Premium Analytics', 'Analyze client retention and churn indicators', 'client_retention_analysis', 'pro'),
731
+ analytics('profitability_leaderboard', 'Premium Analytics', 'Rank companies by profitability', 'profitability_leaderboard', 'pro'),
732
+ // =========================================================================
733
+ // CROSS-DOMAIN (Pro)
734
+ // =========================================================================
735
+ analytics('entity_relationship_map', 'Cross-Domain', 'Map relationships between a company and all related entities', 'entity_relationship_map', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
736
+ analytics('bulk_status_summary', 'Cross-Domain', 'Status counts across tickets, projects, and opportunities', 'bulk_status_summary', 'pro'),
737
+ // technician_dashboard removed — duplicate of existing cw_member_dashboard composite tool
738
+ analytics('ticket_handoff_analysis', 'Cross-Domain', 'Analyze ticket reassignment patterns between members', 'ticket_handoff_analysis', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
739
+ analytics('company_financial_summary', 'Cross-Domain', 'Complete financial view: agreements, invoices, time, and expenses for a company', 'company_financial_summary', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
740
+ analytics('new_client_onboarding_status', 'Cross-Domain', 'Track onboarding progress for recently added companies', 'new_client_onboarding_status', 'pro', { params: { days: { description: 'Days to look back for new companies (default: 30)', type: 'number' } } }),
741
+ analytics('weekly_operations_report', 'Cross-Domain', 'Weekly summary: tickets, time, revenue, and team performance', 'weekly_operations_report', 'pro'),
742
+ analytics('board_health_dashboard', 'Cross-Domain', 'Health metrics for a specific board: volume, aging, SLA, response times', 'board_health_dashboard', 'pro', { params: { board_id: { description: 'Board ID', required: true, type: 'number' } } }),
743
+ // ==========================================================================
744
+ // MSP ANALYTICS — 54 server-side aggregation operations (no manual math)
745
+ // ==========================================================================
746
+ // --- Time & Labor (8) ---
747
+ analytics('member_daily_totals', 'MSP Analytics', 'Daily hour totals per member with billable/non-billable/charge-type breakdown', 'member_daily_totals', 'pro', { params: { identifier: { description: 'Member identifier', required: true, type: 'string' }, start_date: { description: 'Start date (YYYY-MM-DD)', required: true, type: 'string' }, end_date: { description: 'End date (YYYY-MM-DD)', required: true, type: 'string' } } }),
748
+ analytics('member_utilization', 'MSP Analytics', 'Member utilization: billable hours / available capacity', 'member_utilization', 'pro', { params: { identifier: { description: 'Member identifier', type: 'string' }, days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
749
+ analytics('team_utilization_comparison', 'MSP Analytics', 'All members ranked by utilization rate', 'team_utilization_comparison', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
750
+ analytics('timesheet_completeness', 'MSP Analytics', 'Expected vs logged hours per member per weekday', 'timesheet_completeness', 'pro', { params: { start_date: { description: 'Start date (YYYY-MM-DD)', required: true, type: 'string' }, end_date: { description: 'End date (YYYY-MM-DD)', required: true, type: 'string' } } }),
751
+ analytics('overtime_detection', 'MSP Analytics', 'Flag days >8h or weeks >40h for any member', 'overtime_detection', 'pro', { params: { days: { description: 'Days to look back (default: 14)', type: 'number' } } }),
752
+ analytics('unbilled_wip_report', 'MSP Analytics', 'Unbilled billable hours by company with revenue estimate', 'unbilled_wip_report', 'pro'),
753
+ analytics('charge_code_summary', 'MSP Analytics', 'Internal time breakdown by charge code', 'charge_code_summary', 'pro', { params: { identifier: { description: 'Member identifier (optional)', type: 'string' }, days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
754
+ analytics('time_entry_gap_analysis', 'MSP Analytics', 'Find gaps >30min in a member timesheet for a specific day', 'time_entry_gap_analysis', 'pro', { params: { identifier: { description: 'Member identifier', required: true, type: 'string' }, date: { description: 'Date (YYYY-MM-DD)', required: true, type: 'string' } } }),
755
+ // --- Ticket & Service (8) ---
756
+ analytics('ticket_volume_by_company', 'MSP Analytics', 'Tickets opened/closed per company in period', 'ticket_volume_by_company', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
757
+ analytics('ticket_aging_summary', 'MSP Analytics', 'Open tickets grouped by age bucket with counts', 'ticket_aging_summary', 'pro', { params: { board: { description: 'Board name filter (optional)', type: 'string' } } }),
758
+ analytics('sla_compliance_report', 'MSP Analytics', 'SLA compliance % by priority tier', 'sla_compliance_report', 'pro', { params: { board: { description: 'Board name (optional)', type: 'string' }, days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
759
+ analytics('backlog_trend', 'MSP Analytics', 'Daily open ticket count trend over time', 'backlog_trend', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' }, board: { description: 'Board name (optional)', type: 'string' } } }),
760
+ analytics('escalation_frequency', 'MSP Analytics', 'Tickets with frequent updates suggesting escalation', 'escalation_frequency', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
761
+ analytics('ticket_touch_count', 'MSP Analytics', 'How many times each open ticket was worked on', 'ticket_touch_count', 'pro', { params: { board: { description: 'Board name (optional)', type: 'string' } } }),
762
+ analytics('first_response_metrics', 'MSP Analytics', 'Time to first response by priority (avg/median/P90)', 'first_response_metrics', 'pro', { params: { board: { description: 'Board name (optional)', type: 'string' }, days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
763
+ analytics('ticket_resolution_metrics', 'MSP Analytics', 'Resolution time by priority (avg/median/P90)', 'ticket_resolution_metrics', 'pro', { params: { board: { description: 'Board name (optional)', type: 'string' }, days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
764
+ // --- Financial & Profitability (7) ---
765
+ analytics('revenue_per_company', 'MSP Analytics', 'Billable revenue per company with effective rate', 'revenue_per_company', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
766
+ analytics('agreement_utilization_summary', 'MSP Analytics', 'Hours consumed vs covered across all agreements', 'agreement_utilization_summary', 'pro'),
767
+ analytics('agreement_burn_rate', 'MSP Analytics', 'Monthly burn rate and projected exhaustion per agreement', 'agreement_burn_rate', 'pro', { params: { agreement_id: { description: 'Agreement ID (optional, all if omitted)', type: 'number' } } }),
768
+ analytics('effective_hourly_rate_msp', 'MSP Analytics', 'Actual revenue / hours by company or member', 'effective_hourly_rate_msp', 'pro', { params: { group_by: { description: 'Group by: company or member (default: company)', type: 'string' }, days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
769
+ analytics('profitability_by_company', 'MSP Analytics', 'Revenue minus cost per company with margin %', 'profitability_by_company', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
770
+ analytics('profitability_by_member', 'MSP Analytics', 'Revenue minus cost per member with margin', 'profitability_by_member', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
771
+ analytics('invoice_readiness', 'MSP Analytics', 'Time entries ready vs pending invoice by company', 'invoice_readiness', 'pro'),
772
+ // --- Schedule & Capacity (5) ---
773
+ analytics('schedule_vs_actual', 'MSP Analytics', 'Scheduled hours vs logged hours per day for a member', 'schedule_vs_actual', 'pro', { params: { identifier: { description: 'Member identifier', required: true, type: 'string' }, start_date: { description: 'Start date (YYYY-MM-DD)', required: true, type: 'string' }, end_date: { description: 'End date (YYYY-MM-DD)', required: true, type: 'string' } } }),
774
+ analytics('team_capacity_forecast', 'MSP Analytics', 'Available capacity per member for next N days', 'team_capacity_forecast', 'pro', { params: { days_ahead: { description: 'Days ahead to forecast (default: 7)', type: 'number' } } }),
775
+ analytics('overbooked_detection', 'MSP Analytics', 'Members with >8h scheduled in a day', 'overbooked_detection', 'pro', { params: { days_ahead: { description: 'Days ahead to check (default: 14)', type: 'number' } } }),
776
+ analytics('unscheduled_time_report', 'MSP Analytics', 'Logged time with no matching schedule entry', 'unscheduled_time_report', 'pro', { params: { identifier: { description: 'Member identifier', required: true, type: 'string' }, start_date: { description: 'Start date (YYYY-MM-DD)', required: true, type: 'string' }, end_date: { description: 'End date (YYYY-MM-DD)', required: true, type: 'string' } } }),
777
+ analytics('schedule_coverage_gaps', 'MSP Analytics', 'Time slots with no team member scheduled', 'schedule_coverage_gaps', 'pro', { params: { start_date: { description: 'Start date (YYYY-MM-DD)', required: true, type: 'string' }, end_date: { description: 'End date (YYYY-MM-DD)', required: true, type: 'string' } } }),
778
+ // --- Client Health (4) ---
779
+ analytics('client_health_scorecard', 'MSP Analytics', 'Composite health score for a company (tickets, SLA, agreement, engagement)', 'client_health_scorecard', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' } } }),
780
+ analytics('client_ticket_trend', 'MSP Analytics', 'Monthly ticket volume trend for a company', 'client_ticket_trend', 'pro', { params: { company_id: { description: 'Company ID', required: true, type: 'number' }, days: { description: 'Days to look back (default: 90)', type: 'number' } } }),
781
+ analytics('top_consumers', 'MSP Analytics', 'Companies ranked by hours + tickets consumed', 'top_consumers', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
782
+ analytics('at_risk_clients', 'MSP Analytics', 'Companies with rising tickets, SLA misses, or agreement overuse', 'at_risk_clients', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
783
+ // --- Team Performance (4) ---
784
+ analytics('member_performance_summary', 'MSP Analytics', 'Performance card: tickets, hours, utilization, resolution time', 'member_performance_summary', 'pro', { params: { identifier: { description: 'Member identifier', required: true, type: 'string' }, days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
785
+ analytics('team_leaderboard', 'MSP Analytics', 'Ranked members by chosen metric', 'team_leaderboard', 'pro', { params: { metric: { description: 'Metric: tickets, hours, or utilization', required: true, type: 'string' }, days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
786
+ analytics('member_ticket_distribution', 'MSP Analytics', 'Ticket distribution fairness across team', 'member_ticket_distribution', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
787
+ analytics('dispatch_efficiency', 'MSP Analytics', 'Dispatch/triage time vs productive time per member', 'dispatch_efficiency', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
788
+ // --- Configurations & Assets (4) ---
789
+ analytics('asset_count_by_company', 'MSP Analytics', 'Device/configuration count per company with type breakdown', 'asset_count_by_company', 'pro'),
790
+ analytics('warranty_expiration_forecast', 'MSP Analytics', 'Configurations with warranty expiring within N days', 'warranty_expiration_forecast', 'pro', { params: { days_ahead: { description: 'Days ahead to check (default: 90)', type: 'number' } } }),
791
+ analytics('asset_aging_report', 'MSP Analytics', 'Average asset age per company, flagging old hardware', 'asset_aging_report', 'pro'),
792
+ analytics('license_compliance_check', 'MSP Analytics', 'Software license tracked vs installed counts', 'license_compliance_check', 'pro'),
793
+ // --- Project Metrics (4) ---
794
+ analytics('project_budget_burn', 'MSP Analytics', 'Actual vs budget hours per project with burn rate', 'project_budget_burn', 'pro', { params: { project_id: { description: 'Project ID (optional, all if omitted)', type: 'number' } } }),
795
+ analytics('project_phase_completion', 'MSP Analytics', 'Phase completion % based on closed vs total tickets', 'project_phase_completion', 'pro', { params: { project_id: { description: 'Project ID', required: true, type: 'number' } } }),
796
+ analytics('project_health_dashboard', 'MSP Analytics', 'Health overview of all active projects', 'project_health_dashboard', 'pro'),
797
+ analytics('project_resource_allocation', 'MSP Analytics', 'Hours per member across active projects', 'project_resource_allocation', 'pro'),
798
+ // --- Sales Pipeline (3) ---
799
+ analytics('pipeline_value_summary', 'MSP Analytics', 'Pipeline by stage with weighted forecast value', 'pipeline_value_summary', 'pro'),
800
+ analytics('opportunity_win_rate', 'MSP Analytics', 'Won vs lost deals with win rate and avg deal size', 'opportunity_win_rate', 'pro', { params: { days: { description: 'Days to look back (default: 90)', type: 'number' } } }),
801
+ analytics('opportunity_aging', 'MSP Analytics', 'Open opportunities by age bucket with total value', 'opportunity_aging', 'pro'),
802
+ // --- Procurement & Expenses (3) ---
803
+ analytics('procurement_spend_summary', 'MSP Analytics', 'PO totals by vendor with status', 'procurement_spend_summary', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
804
+ analytics('expense_pending_approvals', 'MSP Analytics', 'Total pending expense approvals by member', 'expense_pending_approvals', 'pro'),
805
+ analytics('expense_by_category', 'MSP Analytics', 'Expense spend breakdown by category', 'expense_by_category', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
806
+ // --- Contracts & Renewals (2) ---
807
+ analytics('expiring_agreements_forecast', 'MSP Analytics', 'Agreements expiring within N days with revenue at risk', 'expiring_agreements_forecast', 'pro', { params: { days_ahead: { description: 'Days ahead to check (default: 90)', type: 'number' } } }),
808
+ analytics('renewal_pipeline_value', 'MSP Analytics', 'Upcoming agreement renewals by month with cumulative value', 'renewal_pipeline_value', 'pro'),
809
+ // --- Communication (2) ---
810
+ analytics('first_contact_response_time', 'MSP Analytics', 'Average time from ticket creation to first human response', 'first_contact_response_time', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
811
+ analytics('communication_frequency_by_client', 'MSP Analytics', 'Notes and updates per client to measure engagement', 'communication_frequency_by_client', 'pro', { params: { days: { description: 'Days to look back (default: 30)', type: 'number' } } }),
812
+ ];
813
+ // ===========================================================================
814
+ // REGISTRY — combined and indexed
815
+ // ===========================================================================
816
+ const allOperations = [...freeOps, ...proOps];
817
+ /** Lookup by operation name. O(1). */
818
+ export const operationMap = new Map(allOperations.map(op => [op.name, op]));
819
+ /** All operations as a flat array (read-only). */
820
+ export const operations = Object.freeze(allOperations);
821
+ /** Distinct category names in declaration order. */
822
+ export const categories = Object.freeze([...new Set(allOperations.map(op => op.category))]);
823
+ /** Get operation by name, or undefined if not found. */
824
+ export function getOperation(name) {
825
+ return operationMap.get(name);
826
+ }
827
+ /** List operations, optionally filtering by category, tier, or search term. */
828
+ export function listOperations(opts) {
829
+ let result = allOperations;
830
+ if (opts?.category) {
831
+ const cat = opts.category.toLowerCase();
832
+ // Exact match first; fall back to partial if no exact match found
833
+ const exact = result.filter(op => op.category.toLowerCase() === cat);
834
+ result = exact.length > 0
835
+ ? exact
836
+ : result.filter(op => op.category.toLowerCase().includes(cat));
837
+ }
838
+ if (opts?.tier) {
839
+ result = result.filter(op => op.tier === opts.tier);
840
+ }
841
+ if (opts?.search) {
842
+ const q = opts.search.toLowerCase();
843
+ result = result.filter(op => op.name.includes(q) || op.description.toLowerCase().includes(q) || op.category.toLowerCase().includes(q));
844
+ }
845
+ return result;
846
+ }
847
+ //# sourceMappingURL=registry.js.map