@vibescope/mcp-server 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -84
- package/README.md +194 -194
- package/dist/api-client/tasks.d.ts +1 -0
- package/dist/cli-init.js +21 -21
- package/dist/cli.js +26 -26
- package/dist/handlers/tasks.js +7 -1
- package/dist/handlers/tool-docs.js +1216 -1216
- package/dist/index.js +73 -73
- package/dist/templates/agent-guidelines.d.ts +1 -1
- package/dist/templates/agent-guidelines.js +205 -205
- package/dist/templates/help-content.js +1621 -1621
- package/dist/tools/bodies-of-work.js +6 -6
- package/dist/tools/cloud-agents.js +22 -22
- package/dist/tools/milestones.js +2 -2
- package/dist/tools/requests.js +1 -1
- package/dist/tools/session.js +11 -11
- package/dist/tools/sprints.js +9 -9
- package/dist/tools/tasks.js +43 -35
- package/dist/tools/worktrees.js +14 -14
- package/dist/utils.js +11 -11
- package/docs/TOOLS.md +2687 -2685
- package/package.json +53 -53
- package/scripts/generate-docs.ts +212 -212
- package/scripts/version-bump.ts +203 -203
- package/src/api-client/blockers.ts +86 -86
- package/src/api-client/bodies-of-work.ts +194 -194
- package/src/api-client/chat.ts +50 -50
- package/src/api-client/connectors.ts +152 -152
- package/src/api-client/cost.ts +185 -185
- package/src/api-client/decisions.ts +87 -87
- package/src/api-client/deployment.ts +313 -313
- package/src/api-client/discovery.ts +81 -81
- package/src/api-client/fallback.ts +52 -52
- package/src/api-client/file-checkouts.ts +115 -115
- package/src/api-client/findings.ts +100 -100
- package/src/api-client/git-issues.ts +88 -88
- package/src/api-client/ideas.ts +112 -112
- package/src/api-client/index.ts +592 -592
- package/src/api-client/milestones.ts +83 -83
- package/src/api-client/organizations.ts +185 -185
- package/src/api-client/progress.ts +94 -94
- package/src/api-client/project.ts +181 -181
- package/src/api-client/requests.ts +54 -54
- package/src/api-client/session.ts +220 -220
- package/src/api-client/sprints.ts +227 -227
- package/src/api-client/subtasks.ts +57 -57
- package/src/api-client/tasks.ts +451 -450
- package/src/api-client/types.ts +32 -32
- package/src/api-client/validation.ts +60 -60
- package/src/api-client/worktrees.ts +53 -53
- package/src/api-client.test.ts +847 -847
- package/src/api-client.ts +2728 -2728
- package/src/cli-init.ts +558 -558
- package/src/cli.test.ts +284 -284
- package/src/cli.ts +204 -204
- package/src/handlers/__test-setup__.ts +240 -240
- package/src/handlers/__test-utils__.ts +89 -89
- package/src/handlers/blockers.test.ts +468 -468
- package/src/handlers/blockers.ts +172 -172
- package/src/handlers/bodies-of-work.test.ts +704 -704
- package/src/handlers/bodies-of-work.ts +526 -526
- package/src/handlers/chat.test.ts +185 -185
- package/src/handlers/chat.ts +101 -101
- package/src/handlers/cloud-agents.test.ts +438 -438
- package/src/handlers/cloud-agents.ts +156 -156
- package/src/handlers/connectors.test.ts +834 -834
- package/src/handlers/connectors.ts +229 -229
- package/src/handlers/cost.test.ts +462 -462
- package/src/handlers/cost.ts +285 -285
- package/src/handlers/decisions.test.ts +382 -382
- package/src/handlers/decisions.ts +153 -153
- package/src/handlers/deployment.test.ts +551 -551
- package/src/handlers/deployment.ts +570 -570
- package/src/handlers/discovery.test.ts +206 -206
- package/src/handlers/discovery.ts +433 -433
- package/src/handlers/fallback.test.ts +537 -537
- package/src/handlers/fallback.ts +194 -194
- package/src/handlers/file-checkouts.test.ts +750 -750
- package/src/handlers/file-checkouts.ts +185 -185
- package/src/handlers/findings.test.ts +633 -633
- package/src/handlers/findings.ts +239 -239
- package/src/handlers/git-issues.test.ts +631 -631
- package/src/handlers/git-issues.ts +136 -136
- package/src/handlers/ideas.test.ts +644 -644
- package/src/handlers/ideas.ts +207 -207
- package/src/handlers/index.ts +93 -93
- package/src/handlers/milestones.test.ts +475 -475
- package/src/handlers/milestones.ts +180 -180
- package/src/handlers/organizations.test.ts +826 -826
- package/src/handlers/organizations.ts +315 -315
- package/src/handlers/progress.test.ts +269 -269
- package/src/handlers/progress.ts +77 -77
- package/src/handlers/project.test.ts +546 -546
- package/src/handlers/project.ts +245 -245
- package/src/handlers/requests.test.ts +303 -303
- package/src/handlers/requests.ts +99 -99
- package/src/handlers/roles.test.ts +305 -305
- package/src/handlers/roles.ts +219 -219
- package/src/handlers/session.test.ts +998 -998
- package/src/handlers/session.ts +1105 -1105
- package/src/handlers/sprints.test.ts +732 -732
- package/src/handlers/sprints.ts +537 -537
- package/src/handlers/tasks.test.ts +931 -931
- package/src/handlers/tasks.ts +1144 -1137
- package/src/handlers/tool-categories.test.ts +66 -66
- package/src/handlers/tool-docs.test.ts +511 -511
- package/src/handlers/tool-docs.ts +1595 -1595
- package/src/handlers/types.test.ts +259 -259
- package/src/handlers/types.ts +176 -176
- package/src/handlers/validation.test.ts +582 -582
- package/src/handlers/validation.ts +164 -164
- package/src/handlers/version.ts +63 -63
- package/src/index.test.ts +674 -674
- package/src/index.ts +884 -884
- package/src/setup.test.ts +243 -243
- package/src/setup.ts +410 -410
- package/src/templates/agent-guidelines.ts +233 -233
- package/src/templates/help-content.ts +1751 -1751
- package/src/token-tracking.test.ts +463 -463
- package/src/token-tracking.ts +167 -167
- package/src/tools/blockers.ts +122 -122
- package/src/tools/bodies-of-work.ts +283 -283
- package/src/tools/chat.ts +72 -72
- package/src/tools/cloud-agents.ts +101 -101
- package/src/tools/connectors.ts +191 -191
- package/src/tools/cost.ts +111 -111
- package/src/tools/decisions.ts +111 -111
- package/src/tools/deployment.ts +455 -455
- package/src/tools/discovery.ts +76 -76
- package/src/tools/fallback.ts +111 -111
- package/src/tools/features.ts +154 -154
- package/src/tools/file-checkouts.ts +145 -145
- package/src/tools/findings.ts +101 -101
- package/src/tools/git-issues.ts +130 -130
- package/src/tools/ideas.ts +162 -162
- package/src/tools/index.ts +145 -145
- package/src/tools/milestones.ts +118 -118
- package/src/tools/organizations.ts +224 -224
- package/src/tools/persona-templates.ts +25 -25
- package/src/tools/progress.ts +73 -73
- package/src/tools/project.ts +210 -210
- package/src/tools/requests.ts +68 -68
- package/src/tools/roles.ts +112 -112
- package/src/tools/session.ts +181 -181
- package/src/tools/sprints.ts +298 -298
- package/src/tools/tasks.ts +583 -575
- package/src/tools/tools.test.ts +222 -222
- package/src/tools/types.ts +9 -9
- package/src/tools/validation.ts +75 -75
- package/src/tools/version.ts +34 -34
- package/src/tools/worktrees.ts +66 -66
- package/src/tools.test.ts +416 -416
- package/src/utils.test.ts +1014 -1014
- package/src/utils.ts +586 -586
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +249 -249
- package/src/version.ts +162 -162
- package/tsconfig.json +16 -16
- package/vitest.config.ts +14 -14
- package/dist/tools.d.ts +0 -2
- package/dist/tools.js +0 -3602
package/src/handlers/cost.ts
CHANGED
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cost Handlers
|
|
3
|
-
*
|
|
4
|
-
* Handles cost monitoring and alerts:
|
|
5
|
-
* - get_cost_summary
|
|
6
|
-
* - get_cost_alerts
|
|
7
|
-
* - add_cost_alert
|
|
8
|
-
* - update_cost_alert
|
|
9
|
-
* - delete_cost_alert
|
|
10
|
-
* - get_task_costs
|
|
11
|
-
* - get_body_of_work_costs
|
|
12
|
-
* - get_sprint_costs
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { Handler, HandlerRegistry } from './types.js';
|
|
16
|
-
import { parseArgs, uuidValidator, createEnumValidator, ValidationError } from '../validators.js';
|
|
17
|
-
import { getApiClient } from '../api-client.js';
|
|
18
|
-
|
|
19
|
-
const VALID_PERIODS = ['daily', 'weekly', 'monthly'] as const;
|
|
20
|
-
const VALID_ALERT_TYPES = ['warning', 'critical'] as const;
|
|
21
|
-
|
|
22
|
-
type Period = typeof VALID_PERIODS[number];
|
|
23
|
-
type AlertType = typeof VALID_ALERT_TYPES[number];
|
|
24
|
-
|
|
25
|
-
// Argument schemas for type-safe parsing
|
|
26
|
-
const getCostSummarySchema = {
|
|
27
|
-
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
28
|
-
period: { type: 'string' as const, default: 'daily', validate: createEnumValidator(VALID_PERIODS) },
|
|
29
|
-
limit: { type: 'number' as const, default: 30 },
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const getCostAlertsSchema = {
|
|
33
|
-
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const addCostAlertSchema = {
|
|
37
|
-
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
38
|
-
threshold_amount: { type: 'number' as const, required: true as const },
|
|
39
|
-
threshold_period: { type: 'string' as const, required: true as const, validate: createEnumValidator(VALID_PERIODS) },
|
|
40
|
-
alert_type: { type: 'string' as const, default: 'warning', validate: createEnumValidator(VALID_ALERT_TYPES) },
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const updateCostAlertSchema = {
|
|
44
|
-
alert_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
45
|
-
threshold_amount: { type: 'number' as const },
|
|
46
|
-
threshold_period: { type: 'string' as const, validate: createEnumValidator(VALID_PERIODS) },
|
|
47
|
-
alert_type: { type: 'string' as const, validate: createEnumValidator(VALID_ALERT_TYPES) },
|
|
48
|
-
enabled: { type: 'boolean' as const },
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const deleteCostAlertSchema = {
|
|
52
|
-
alert_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const getTaskCostsSchema = {
|
|
56
|
-
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
57
|
-
limit: { type: 'number' as const, default: 20 },
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const getBodyOfWorkCostsSchema = {
|
|
61
|
-
body_of_work_id: { type: 'string' as const, validate: uuidValidator },
|
|
62
|
-
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const getSprintCostsSchema = {
|
|
66
|
-
sprint_id: { type: 'string' as const, validate: uuidValidator },
|
|
67
|
-
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Custom validator for positive numbers
|
|
71
|
-
function validatePositiveNumber(value: number | undefined, fieldName: string): void {
|
|
72
|
-
if (value !== undefined && value <= 0) {
|
|
73
|
-
throw new ValidationError(`${fieldName} must be a positive number`, { field: fieldName });
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get cost summary for a project (daily, weekly, or monthly)
|
|
79
|
-
*/
|
|
80
|
-
export const getCostSummary: Handler = async (args, _ctx) => {
|
|
81
|
-
const { project_id, period, limit } = parseArgs(args, getCostSummarySchema);
|
|
82
|
-
|
|
83
|
-
const apiClient = getApiClient();
|
|
84
|
-
const response = await apiClient.getCostSummary(project_id, {
|
|
85
|
-
period: period as Period,
|
|
86
|
-
limit
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
if (!response.ok) {
|
|
90
|
-
return {
|
|
91
|
-
result: { error: response.error || 'Failed to get cost summary' },
|
|
92
|
-
isError: true,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return { result: response.data };
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Get cost alerts for the current user
|
|
101
|
-
*/
|
|
102
|
-
export const getCostAlerts: Handler = async (args, _ctx) => {
|
|
103
|
-
const { project_id } = parseArgs(args, getCostAlertsSchema);
|
|
104
|
-
|
|
105
|
-
const apiClient = getApiClient();
|
|
106
|
-
const response = await apiClient.getCostAlerts();
|
|
107
|
-
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
return {
|
|
110
|
-
result: { error: response.error || 'Failed to get cost alerts' },
|
|
111
|
-
isError: true,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return { result: response.data };
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Add a cost alert
|
|
120
|
-
*/
|
|
121
|
-
export const addCostAlert: Handler = async (args, _ctx) => {
|
|
122
|
-
const { project_id, threshold_amount, threshold_period, alert_type } = parseArgs(args, addCostAlertSchema);
|
|
123
|
-
|
|
124
|
-
// Additional validation for positive amount
|
|
125
|
-
validatePositiveNumber(threshold_amount, 'threshold_amount');
|
|
126
|
-
|
|
127
|
-
const apiClient = getApiClient();
|
|
128
|
-
const response = await apiClient.addCostAlert({
|
|
129
|
-
project_id,
|
|
130
|
-
threshold_amount: threshold_amount!,
|
|
131
|
-
threshold_period: threshold_period as Period,
|
|
132
|
-
alert_type: alert_type as AlertType
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
if (!response.ok) {
|
|
136
|
-
return {
|
|
137
|
-
result: { error: response.error || 'Failed to create cost alert' },
|
|
138
|
-
isError: true,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return { result: response.data };
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Update a cost alert
|
|
147
|
-
*/
|
|
148
|
-
export const updateCostAlert: Handler = async (args, _ctx) => {
|
|
149
|
-
const { alert_id, threshold_amount, threshold_period, alert_type, enabled } = parseArgs(args, updateCostAlertSchema);
|
|
150
|
-
|
|
151
|
-
// Check that at least one update is provided
|
|
152
|
-
if (threshold_amount === undefined && threshold_period === undefined && alert_type === undefined && enabled === undefined) {
|
|
153
|
-
return {
|
|
154
|
-
result: { error: 'No updates provided' },
|
|
155
|
-
isError: true,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const updates: {
|
|
160
|
-
threshold_amount?: number;
|
|
161
|
-
threshold_period?: Period;
|
|
162
|
-
alert_type?: AlertType;
|
|
163
|
-
enabled?: boolean;
|
|
164
|
-
} = {};
|
|
165
|
-
if (threshold_amount !== undefined) updates.threshold_amount = threshold_amount;
|
|
166
|
-
if (threshold_period !== undefined) updates.threshold_period = threshold_period as Period;
|
|
167
|
-
if (alert_type !== undefined) updates.alert_type = alert_type as AlertType;
|
|
168
|
-
if (enabled !== undefined) updates.enabled = enabled;
|
|
169
|
-
|
|
170
|
-
const apiClient = getApiClient();
|
|
171
|
-
const response = await apiClient.updateCostAlert(alert_id, updates);
|
|
172
|
-
|
|
173
|
-
if (!response.ok) {
|
|
174
|
-
return {
|
|
175
|
-
result: { error: response.error || 'Failed to update cost alert' },
|
|
176
|
-
isError: true,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return { result: response.data };
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Delete a cost alert
|
|
185
|
-
*/
|
|
186
|
-
export const deleteCostAlert: Handler = async (args, _ctx) => {
|
|
187
|
-
const { alert_id } = parseArgs(args, deleteCostAlertSchema);
|
|
188
|
-
|
|
189
|
-
const apiClient = getApiClient();
|
|
190
|
-
const response = await apiClient.deleteCostAlert(alert_id);
|
|
191
|
-
|
|
192
|
-
if (!response.ok) {
|
|
193
|
-
return {
|
|
194
|
-
result: { error: response.error || 'Failed to delete cost alert' },
|
|
195
|
-
isError: true,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return { result: response.data };
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Get task costs for a project
|
|
204
|
-
*/
|
|
205
|
-
export const getTaskCosts: Handler = async (args, _ctx) => {
|
|
206
|
-
const { project_id, limit } = parseArgs(args, getTaskCostsSchema);
|
|
207
|
-
|
|
208
|
-
const apiClient = getApiClient();
|
|
209
|
-
const response = await apiClient.getTaskCosts(project_id, limit);
|
|
210
|
-
|
|
211
|
-
if (!response.ok) {
|
|
212
|
-
return {
|
|
213
|
-
result: { error: response.error || 'Failed to get task costs' },
|
|
214
|
-
isError: true,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return { result: response.data };
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get body of work costs with phase breakdown
|
|
223
|
-
*/
|
|
224
|
-
export const getBodyOfWorkCosts: Handler = async (args, _ctx) => {
|
|
225
|
-
const { body_of_work_id, project_id } = parseArgs(args, getBodyOfWorkCostsSchema);
|
|
226
|
-
|
|
227
|
-
if (!body_of_work_id && !project_id) {
|
|
228
|
-
return {
|
|
229
|
-
result: { error: 'Either body_of_work_id or project_id is required' },
|
|
230
|
-
isError: true,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const apiClient = getApiClient();
|
|
235
|
-
const response = await apiClient.getBodyOfWorkCosts({ body_of_work_id, project_id });
|
|
236
|
-
|
|
237
|
-
if (!response.ok) {
|
|
238
|
-
return {
|
|
239
|
-
result: { error: response.error || 'Failed to get body of work costs' },
|
|
240
|
-
isError: true,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return { result: response.data };
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Get sprint costs with velocity metrics
|
|
249
|
-
*/
|
|
250
|
-
export const getSprintCosts: Handler = async (args, _ctx) => {
|
|
251
|
-
const { sprint_id, project_id } = parseArgs(args, getSprintCostsSchema);
|
|
252
|
-
|
|
253
|
-
if (!sprint_id && !project_id) {
|
|
254
|
-
return {
|
|
255
|
-
result: { error: 'Either sprint_id or project_id is required' },
|
|
256
|
-
isError: true,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const apiClient = getApiClient();
|
|
261
|
-
const response = await apiClient.getSprintCosts({ sprint_id, project_id });
|
|
262
|
-
|
|
263
|
-
if (!response.ok) {
|
|
264
|
-
return {
|
|
265
|
-
result: { error: response.error || 'Failed to get sprint costs' },
|
|
266
|
-
isError: true,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return { result: response.data };
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Cost handlers registry
|
|
275
|
-
*/
|
|
276
|
-
export const costHandlers: HandlerRegistry = {
|
|
277
|
-
get_cost_summary: getCostSummary,
|
|
278
|
-
get_cost_alerts: getCostAlerts,
|
|
279
|
-
add_cost_alert: addCostAlert,
|
|
280
|
-
update_cost_alert: updateCostAlert,
|
|
281
|
-
delete_cost_alert: deleteCostAlert,
|
|
282
|
-
get_task_costs: getTaskCosts,
|
|
283
|
-
get_body_of_work_costs: getBodyOfWorkCosts,
|
|
284
|
-
get_sprint_costs: getSprintCosts,
|
|
285
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Cost Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles cost monitoring and alerts:
|
|
5
|
+
* - get_cost_summary
|
|
6
|
+
* - get_cost_alerts
|
|
7
|
+
* - add_cost_alert
|
|
8
|
+
* - update_cost_alert
|
|
9
|
+
* - delete_cost_alert
|
|
10
|
+
* - get_task_costs
|
|
11
|
+
* - get_body_of_work_costs
|
|
12
|
+
* - get_sprint_costs
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Handler, HandlerRegistry } from './types.js';
|
|
16
|
+
import { parseArgs, uuidValidator, createEnumValidator, ValidationError } from '../validators.js';
|
|
17
|
+
import { getApiClient } from '../api-client.js';
|
|
18
|
+
|
|
19
|
+
const VALID_PERIODS = ['daily', 'weekly', 'monthly'] as const;
|
|
20
|
+
const VALID_ALERT_TYPES = ['warning', 'critical'] as const;
|
|
21
|
+
|
|
22
|
+
type Period = typeof VALID_PERIODS[number];
|
|
23
|
+
type AlertType = typeof VALID_ALERT_TYPES[number];
|
|
24
|
+
|
|
25
|
+
// Argument schemas for type-safe parsing
|
|
26
|
+
const getCostSummarySchema = {
|
|
27
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
28
|
+
period: { type: 'string' as const, default: 'daily', validate: createEnumValidator(VALID_PERIODS) },
|
|
29
|
+
limit: { type: 'number' as const, default: 30 },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getCostAlertsSchema = {
|
|
33
|
+
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const addCostAlertSchema = {
|
|
37
|
+
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
38
|
+
threshold_amount: { type: 'number' as const, required: true as const },
|
|
39
|
+
threshold_period: { type: 'string' as const, required: true as const, validate: createEnumValidator(VALID_PERIODS) },
|
|
40
|
+
alert_type: { type: 'string' as const, default: 'warning', validate: createEnumValidator(VALID_ALERT_TYPES) },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const updateCostAlertSchema = {
|
|
44
|
+
alert_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
45
|
+
threshold_amount: { type: 'number' as const },
|
|
46
|
+
threshold_period: { type: 'string' as const, validate: createEnumValidator(VALID_PERIODS) },
|
|
47
|
+
alert_type: { type: 'string' as const, validate: createEnumValidator(VALID_ALERT_TYPES) },
|
|
48
|
+
enabled: { type: 'boolean' as const },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const deleteCostAlertSchema = {
|
|
52
|
+
alert_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const getTaskCostsSchema = {
|
|
56
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
57
|
+
limit: { type: 'number' as const, default: 20 },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const getBodyOfWorkCostsSchema = {
|
|
61
|
+
body_of_work_id: { type: 'string' as const, validate: uuidValidator },
|
|
62
|
+
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const getSprintCostsSchema = {
|
|
66
|
+
sprint_id: { type: 'string' as const, validate: uuidValidator },
|
|
67
|
+
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Custom validator for positive numbers
|
|
71
|
+
function validatePositiveNumber(value: number | undefined, fieldName: string): void {
|
|
72
|
+
if (value !== undefined && value <= 0) {
|
|
73
|
+
throw new ValidationError(`${fieldName} must be a positive number`, { field: fieldName });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get cost summary for a project (daily, weekly, or monthly)
|
|
79
|
+
*/
|
|
80
|
+
export const getCostSummary: Handler = async (args, _ctx) => {
|
|
81
|
+
const { project_id, period, limit } = parseArgs(args, getCostSummarySchema);
|
|
82
|
+
|
|
83
|
+
const apiClient = getApiClient();
|
|
84
|
+
const response = await apiClient.getCostSummary(project_id, {
|
|
85
|
+
period: period as Period,
|
|
86
|
+
limit
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
return {
|
|
91
|
+
result: { error: response.error || 'Failed to get cost summary' },
|
|
92
|
+
isError: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { result: response.data };
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get cost alerts for the current user
|
|
101
|
+
*/
|
|
102
|
+
export const getCostAlerts: Handler = async (args, _ctx) => {
|
|
103
|
+
const { project_id } = parseArgs(args, getCostAlertsSchema);
|
|
104
|
+
|
|
105
|
+
const apiClient = getApiClient();
|
|
106
|
+
const response = await apiClient.getCostAlerts();
|
|
107
|
+
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
return {
|
|
110
|
+
result: { error: response.error || 'Failed to get cost alerts' },
|
|
111
|
+
isError: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { result: response.data };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Add a cost alert
|
|
120
|
+
*/
|
|
121
|
+
export const addCostAlert: Handler = async (args, _ctx) => {
|
|
122
|
+
const { project_id, threshold_amount, threshold_period, alert_type } = parseArgs(args, addCostAlertSchema);
|
|
123
|
+
|
|
124
|
+
// Additional validation for positive amount
|
|
125
|
+
validatePositiveNumber(threshold_amount, 'threshold_amount');
|
|
126
|
+
|
|
127
|
+
const apiClient = getApiClient();
|
|
128
|
+
const response = await apiClient.addCostAlert({
|
|
129
|
+
project_id,
|
|
130
|
+
threshold_amount: threshold_amount!,
|
|
131
|
+
threshold_period: threshold_period as Period,
|
|
132
|
+
alert_type: alert_type as AlertType
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
return {
|
|
137
|
+
result: { error: response.error || 'Failed to create cost alert' },
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { result: response.data };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Update a cost alert
|
|
147
|
+
*/
|
|
148
|
+
export const updateCostAlert: Handler = async (args, _ctx) => {
|
|
149
|
+
const { alert_id, threshold_amount, threshold_period, alert_type, enabled } = parseArgs(args, updateCostAlertSchema);
|
|
150
|
+
|
|
151
|
+
// Check that at least one update is provided
|
|
152
|
+
if (threshold_amount === undefined && threshold_period === undefined && alert_type === undefined && enabled === undefined) {
|
|
153
|
+
return {
|
|
154
|
+
result: { error: 'No updates provided' },
|
|
155
|
+
isError: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const updates: {
|
|
160
|
+
threshold_amount?: number;
|
|
161
|
+
threshold_period?: Period;
|
|
162
|
+
alert_type?: AlertType;
|
|
163
|
+
enabled?: boolean;
|
|
164
|
+
} = {};
|
|
165
|
+
if (threshold_amount !== undefined) updates.threshold_amount = threshold_amount;
|
|
166
|
+
if (threshold_period !== undefined) updates.threshold_period = threshold_period as Period;
|
|
167
|
+
if (alert_type !== undefined) updates.alert_type = alert_type as AlertType;
|
|
168
|
+
if (enabled !== undefined) updates.enabled = enabled;
|
|
169
|
+
|
|
170
|
+
const apiClient = getApiClient();
|
|
171
|
+
const response = await apiClient.updateCostAlert(alert_id, updates);
|
|
172
|
+
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
return {
|
|
175
|
+
result: { error: response.error || 'Failed to update cost alert' },
|
|
176
|
+
isError: true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { result: response.data };
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Delete a cost alert
|
|
185
|
+
*/
|
|
186
|
+
export const deleteCostAlert: Handler = async (args, _ctx) => {
|
|
187
|
+
const { alert_id } = parseArgs(args, deleteCostAlertSchema);
|
|
188
|
+
|
|
189
|
+
const apiClient = getApiClient();
|
|
190
|
+
const response = await apiClient.deleteCostAlert(alert_id);
|
|
191
|
+
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
return {
|
|
194
|
+
result: { error: response.error || 'Failed to delete cost alert' },
|
|
195
|
+
isError: true,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { result: response.data };
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get task costs for a project
|
|
204
|
+
*/
|
|
205
|
+
export const getTaskCosts: Handler = async (args, _ctx) => {
|
|
206
|
+
const { project_id, limit } = parseArgs(args, getTaskCostsSchema);
|
|
207
|
+
|
|
208
|
+
const apiClient = getApiClient();
|
|
209
|
+
const response = await apiClient.getTaskCosts(project_id, limit);
|
|
210
|
+
|
|
211
|
+
if (!response.ok) {
|
|
212
|
+
return {
|
|
213
|
+
result: { error: response.error || 'Failed to get task costs' },
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { result: response.data };
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get body of work costs with phase breakdown
|
|
223
|
+
*/
|
|
224
|
+
export const getBodyOfWorkCosts: Handler = async (args, _ctx) => {
|
|
225
|
+
const { body_of_work_id, project_id } = parseArgs(args, getBodyOfWorkCostsSchema);
|
|
226
|
+
|
|
227
|
+
if (!body_of_work_id && !project_id) {
|
|
228
|
+
return {
|
|
229
|
+
result: { error: 'Either body_of_work_id or project_id is required' },
|
|
230
|
+
isError: true,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const apiClient = getApiClient();
|
|
235
|
+
const response = await apiClient.getBodyOfWorkCosts({ body_of_work_id, project_id });
|
|
236
|
+
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
return {
|
|
239
|
+
result: { error: response.error || 'Failed to get body of work costs' },
|
|
240
|
+
isError: true,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { result: response.data };
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get sprint costs with velocity metrics
|
|
249
|
+
*/
|
|
250
|
+
export const getSprintCosts: Handler = async (args, _ctx) => {
|
|
251
|
+
const { sprint_id, project_id } = parseArgs(args, getSprintCostsSchema);
|
|
252
|
+
|
|
253
|
+
if (!sprint_id && !project_id) {
|
|
254
|
+
return {
|
|
255
|
+
result: { error: 'Either sprint_id or project_id is required' },
|
|
256
|
+
isError: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const apiClient = getApiClient();
|
|
261
|
+
const response = await apiClient.getSprintCosts({ sprint_id, project_id });
|
|
262
|
+
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
return {
|
|
265
|
+
result: { error: response.error || 'Failed to get sprint costs' },
|
|
266
|
+
isError: true,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { result: response.data };
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Cost handlers registry
|
|
275
|
+
*/
|
|
276
|
+
export const costHandlers: HandlerRegistry = {
|
|
277
|
+
get_cost_summary: getCostSummary,
|
|
278
|
+
get_cost_alerts: getCostAlerts,
|
|
279
|
+
add_cost_alert: addCostAlert,
|
|
280
|
+
update_cost_alert: updateCostAlert,
|
|
281
|
+
delete_cost_alert: deleteCostAlert,
|
|
282
|
+
get_task_costs: getTaskCosts,
|
|
283
|
+
get_body_of_work_costs: getBodyOfWorkCosts,
|
|
284
|
+
get_sprint_costs: getSprintCosts,
|
|
285
|
+
};
|