@vibescope/mcp-server 0.2.0 → 0.2.2
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/README.md +60 -7
- package/dist/api-client.d.ts +251 -1
- package/dist/api-client.js +82 -3
- package/dist/handlers/blockers.js +9 -8
- package/dist/handlers/bodies-of-work.js +96 -63
- package/dist/handlers/connectors.d.ts +45 -0
- package/dist/handlers/connectors.js +183 -0
- package/dist/handlers/cost.d.ts +10 -0
- package/dist/handlers/cost.js +112 -50
- package/dist/handlers/decisions.js +32 -19
- package/dist/handlers/deployment.js +144 -122
- package/dist/handlers/discovery.d.ts +7 -0
- package/dist/handlers/discovery.js +96 -7
- package/dist/handlers/fallback.js +29 -23
- package/dist/handlers/file-checkouts.d.ts +20 -0
- package/dist/handlers/file-checkouts.js +133 -0
- package/dist/handlers/findings.d.ts +6 -0
- package/dist/handlers/findings.js +96 -40
- package/dist/handlers/git-issues.js +40 -36
- package/dist/handlers/ideas.js +49 -31
- package/dist/handlers/index.d.ts +3 -0
- package/dist/handlers/index.js +9 -0
- package/dist/handlers/milestones.js +39 -32
- package/dist/handlers/organizations.js +99 -91
- package/dist/handlers/progress.js +24 -13
- package/dist/handlers/project.js +68 -28
- package/dist/handlers/requests.js +18 -14
- package/dist/handlers/roles.d.ts +18 -0
- package/dist/handlers/roles.js +130 -0
- package/dist/handlers/session.js +58 -17
- package/dist/handlers/sprints.js +93 -81
- package/dist/handlers/tasks.d.ts +2 -0
- package/dist/handlers/tasks.js +189 -91
- package/dist/handlers/types.d.ts +64 -2
- package/dist/handlers/types.js +48 -1
- package/dist/handlers/validation.js +21 -17
- package/dist/index.js +7 -2716
- package/dist/token-tracking.d.ts +74 -0
- package/dist/token-tracking.js +122 -0
- package/dist/tools.js +685 -9
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +17 -0
- package/docs/TOOLS.md +2053 -0
- package/package.json +4 -1
- package/scripts/generate-docs.ts +212 -0
- package/src/api-client.test.ts +718 -0
- package/src/api-client.ts +320 -6
- package/src/handlers/__test-setup__.ts +16 -0
- package/src/handlers/blockers.test.ts +31 -19
- package/src/handlers/blockers.ts +9 -8
- package/src/handlers/bodies-of-work.test.ts +55 -32
- package/src/handlers/bodies-of-work.ts +115 -115
- package/src/handlers/connectors.test.ts +834 -0
- package/src/handlers/connectors.ts +229 -0
- package/src/handlers/cost.test.ts +34 -44
- package/src/handlers/cost.ts +136 -85
- package/src/handlers/decisions.test.ts +37 -27
- package/src/handlers/decisions.ts +35 -30
- package/src/handlers/deployment.ts +180 -208
- package/src/handlers/discovery.test.ts +4 -5
- package/src/handlers/discovery.ts +98 -8
- package/src/handlers/fallback.test.ts +26 -22
- package/src/handlers/fallback.ts +36 -33
- package/src/handlers/file-checkouts.test.ts +670 -0
- package/src/handlers/file-checkouts.ts +165 -0
- package/src/handlers/findings.test.ts +178 -19
- package/src/handlers/findings.ts +112 -74
- package/src/handlers/git-issues.test.ts +51 -43
- package/src/handlers/git-issues.ts +44 -84
- package/src/handlers/ideas.test.ts +28 -23
- package/src/handlers/ideas.ts +61 -59
- package/src/handlers/index.ts +9 -0
- package/src/handlers/milestones.test.ts +33 -28
- package/src/handlers/milestones.ts +52 -50
- package/src/handlers/organizations.test.ts +104 -83
- package/src/handlers/organizations.ts +117 -142
- package/src/handlers/progress.test.ts +20 -14
- package/src/handlers/progress.ts +26 -24
- package/src/handlers/project.test.ts +34 -27
- package/src/handlers/project.ts +95 -63
- package/src/handlers/requests.test.ts +27 -18
- package/src/handlers/requests.ts +21 -17
- package/src/handlers/roles.test.ts +303 -0
- package/src/handlers/roles.ts +208 -0
- package/src/handlers/session.test.ts +47 -0
- package/src/handlers/session.ts +71 -26
- package/src/handlers/sprints.test.ts +71 -50
- package/src/handlers/sprints.ts +113 -146
- package/src/handlers/tasks.test.ts +77 -15
- package/src/handlers/tasks.ts +231 -156
- package/src/handlers/tool-categories.test.ts +66 -0
- package/src/handlers/types.ts +81 -2
- package/src/handlers/validation.test.ts +78 -45
- package/src/handlers/validation.ts +23 -25
- package/src/index.ts +12 -2732
- package/src/token-tracking.test.ts +453 -0
- package/src/token-tracking.ts +164 -0
- package/src/tools.ts +685 -9
- package/src/utils.test.ts +2 -2
- package/src/utils.ts +17 -0
- package/dist/config/tool-categories.d.ts +0 -31
- package/dist/config/tool-categories.js +0 -253
- package/dist/knowledge.d.ts +0 -6
- package/dist/knowledge.js +0 -218
|
@@ -18,40 +18,103 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
21
|
-
import {
|
|
21
|
+
import { parseArgs, uuidValidator, createEnumValidator, ValidationError } from '../validators.js';
|
|
22
22
|
import { getApiClient } from '../api-client.js';
|
|
23
23
|
|
|
24
24
|
// Valid roles in order of permission level
|
|
25
25
|
const ROLE_ORDER = ['viewer', 'member', 'admin', 'owner'] as const;
|
|
26
|
+
const ASSIGNABLE_ROLES = ['viewer', 'member', 'admin'] as const;
|
|
26
27
|
type Role = (typeof ROLE_ORDER)[number];
|
|
28
|
+
type AssignableRole = (typeof ASSIGNABLE_ROLES)[number];
|
|
27
29
|
|
|
28
30
|
// Valid share permissions
|
|
29
31
|
const PERMISSION_ORDER = ['read', 'write', 'admin'] as const;
|
|
30
32
|
type Permission = (typeof PERMISSION_ORDER)[number];
|
|
31
33
|
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Argument Schemas
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
const createOrganizationSchema = {
|
|
39
|
+
name: { type: 'string' as const, required: true as const },
|
|
40
|
+
description: { type: 'string' as const },
|
|
41
|
+
slug: { type: 'string' as const },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const updateOrganizationSchema = {
|
|
45
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
46
|
+
name: { type: 'string' as const },
|
|
47
|
+
description: { type: 'string' as const },
|
|
48
|
+
logo_url: { type: 'string' as const },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const deleteOrganizationSchema = {
|
|
52
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const listOrgMembersSchema = {
|
|
56
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const inviteMemberSchema = {
|
|
60
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
61
|
+
email: { type: 'string' as const, required: true as const },
|
|
62
|
+
role: { type: 'string' as const, default: 'member', validate: createEnumValidator(ASSIGNABLE_ROLES) },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const updateMemberRoleSchema = {
|
|
66
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
67
|
+
user_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
68
|
+
role: { type: 'string' as const, required: true as const, validate: createEnumValidator(ROLE_ORDER) },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const removeMemberSchema = {
|
|
72
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
73
|
+
user_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const leaveOrganizationSchema = {
|
|
77
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const shareProjectWithOrgSchema = {
|
|
81
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
82
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
83
|
+
permission: { type: 'string' as const, default: 'read', validate: createEnumValidator(PERMISSION_ORDER) },
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const updateProjectShareSchema = {
|
|
87
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
88
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
89
|
+
permission: { type: 'string' as const, required: true as const, validate: createEnumValidator(PERMISSION_ORDER) },
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const unshareProjectSchema = {
|
|
93
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
94
|
+
organization_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const listProjectSharesSchema = {
|
|
98
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
99
|
+
};
|
|
100
|
+
|
|
32
101
|
// ============================================================================
|
|
33
102
|
// Organization Management
|
|
34
103
|
// ============================================================================
|
|
35
104
|
|
|
36
|
-
export const listOrganizations: Handler = async (_args,
|
|
105
|
+
export const listOrganizations: Handler = async (_args, _ctx) => {
|
|
37
106
|
const apiClient = getApiClient();
|
|
38
107
|
const response = await apiClient.listOrganizations();
|
|
39
108
|
|
|
40
109
|
if (!response.ok) {
|
|
41
|
-
|
|
110
|
+
return { result: { error: response.error || 'Failed to list organizations' }, isError: true };
|
|
42
111
|
}
|
|
43
112
|
|
|
44
113
|
return { result: response.data };
|
|
45
114
|
};
|
|
46
115
|
|
|
47
|
-
export const createOrganization: Handler = async (args,
|
|
48
|
-
const { name, description, slug } = args
|
|
49
|
-
name: string;
|
|
50
|
-
description?: string;
|
|
51
|
-
slug?: string;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
validateRequired(name, 'name');
|
|
116
|
+
export const createOrganization: Handler = async (args, _ctx) => {
|
|
117
|
+
const { name, description, slug } = parseArgs(args, createOrganizationSchema);
|
|
55
118
|
|
|
56
119
|
const apiClient = getApiClient();
|
|
57
120
|
const response = await apiClient.createOrganization({
|
|
@@ -61,53 +124,43 @@ export const createOrganization: Handler = async (args, ctx) => {
|
|
|
61
124
|
});
|
|
62
125
|
|
|
63
126
|
if (!response.ok) {
|
|
64
|
-
|
|
127
|
+
return { result: { error: response.error || 'Failed to create organization' }, isError: true };
|
|
65
128
|
}
|
|
66
129
|
|
|
67
130
|
return { result: response.data };
|
|
68
131
|
};
|
|
69
132
|
|
|
70
|
-
export const updateOrganization: Handler = async (args,
|
|
71
|
-
const { organization_id, name, description, logo_url } = args
|
|
72
|
-
organization_id: string;
|
|
73
|
-
name?: string;
|
|
74
|
-
description?: string;
|
|
75
|
-
logo_url?: string;
|
|
76
|
-
};
|
|
133
|
+
export const updateOrganization: Handler = async (args, _ctx) => {
|
|
134
|
+
const { organization_id, name, description, logo_url } = parseArgs(args, updateOrganizationSchema);
|
|
77
135
|
|
|
78
|
-
|
|
79
|
-
|
|
136
|
+
// Check that at least one update is provided
|
|
137
|
+
if (name === undefined && description === undefined && logo_url === undefined) {
|
|
138
|
+
throw new ValidationError('No updates provided');
|
|
139
|
+
}
|
|
80
140
|
|
|
81
141
|
const updates: Record<string, unknown> = {};
|
|
82
142
|
if (name !== undefined) updates.name = name;
|
|
83
143
|
if (description !== undefined) updates.description = description;
|
|
84
144
|
if (logo_url !== undefined) updates.logo_url = logo_url;
|
|
85
145
|
|
|
86
|
-
if (Object.keys(updates).length === 0) {
|
|
87
|
-
throw new Error('No updates provided');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
146
|
const apiClient = getApiClient();
|
|
91
147
|
const response = await apiClient.updateOrganization(organization_id, updates);
|
|
92
148
|
|
|
93
149
|
if (!response.ok) {
|
|
94
|
-
|
|
150
|
+
return { result: { error: response.error || 'Failed to update organization' }, isError: true };
|
|
95
151
|
}
|
|
96
152
|
|
|
97
153
|
return { result: response.data };
|
|
98
154
|
};
|
|
99
155
|
|
|
100
|
-
export const deleteOrganization: Handler = async (args,
|
|
101
|
-
const { organization_id } = args
|
|
102
|
-
|
|
103
|
-
validateRequired(organization_id, 'organization_id');
|
|
104
|
-
validateUUID(organization_id, 'organization_id');
|
|
156
|
+
export const deleteOrganization: Handler = async (args, _ctx) => {
|
|
157
|
+
const { organization_id } = parseArgs(args, deleteOrganizationSchema);
|
|
105
158
|
|
|
106
159
|
const apiClient = getApiClient();
|
|
107
160
|
const response = await apiClient.deleteOrganization(organization_id);
|
|
108
161
|
|
|
109
162
|
if (!response.ok) {
|
|
110
|
-
|
|
163
|
+
return { result: { error: response.error || 'Failed to delete organization' }, isError: true };
|
|
111
164
|
}
|
|
112
165
|
|
|
113
166
|
return { result: response.data };
|
|
@@ -117,110 +170,70 @@ export const deleteOrganization: Handler = async (args, ctx) => {
|
|
|
117
170
|
// Member Management
|
|
118
171
|
// ============================================================================
|
|
119
172
|
|
|
120
|
-
export const listOrgMembers: Handler = async (args,
|
|
121
|
-
const { organization_id } = args
|
|
122
|
-
|
|
123
|
-
validateRequired(organization_id, 'organization_id');
|
|
124
|
-
validateUUID(organization_id, 'organization_id');
|
|
173
|
+
export const listOrgMembers: Handler = async (args, _ctx) => {
|
|
174
|
+
const { organization_id } = parseArgs(args, listOrgMembersSchema);
|
|
125
175
|
|
|
126
176
|
const apiClient = getApiClient();
|
|
127
177
|
const response = await apiClient.listOrgMembers(organization_id);
|
|
128
178
|
|
|
129
179
|
if (!response.ok) {
|
|
130
|
-
|
|
180
|
+
return { result: { error: response.error || 'Failed to list members' }, isError: true };
|
|
131
181
|
}
|
|
132
182
|
|
|
133
183
|
return { result: response.data };
|
|
134
184
|
};
|
|
135
185
|
|
|
136
|
-
export const inviteMember: Handler = async (args,
|
|
137
|
-
const { organization_id, email, role
|
|
138
|
-
organization_id: string;
|
|
139
|
-
email: string;
|
|
140
|
-
role?: 'admin' | 'member' | 'viewer';
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
validateRequired(organization_id, 'organization_id');
|
|
144
|
-
validateRequired(email, 'email');
|
|
145
|
-
validateUUID(organization_id, 'organization_id');
|
|
146
|
-
|
|
147
|
-
if (!['admin', 'member', 'viewer'].includes(role)) {
|
|
148
|
-
throw new Error('Invalid role. Must be admin, member, or viewer.');
|
|
149
|
-
}
|
|
186
|
+
export const inviteMember: Handler = async (args, _ctx) => {
|
|
187
|
+
const { organization_id, email, role } = parseArgs(args, inviteMemberSchema);
|
|
150
188
|
|
|
151
189
|
const apiClient = getApiClient();
|
|
152
|
-
const response = await apiClient.inviteMember(organization_id, email, role);
|
|
190
|
+
const response = await apiClient.inviteMember(organization_id, email, role as AssignableRole);
|
|
153
191
|
|
|
154
192
|
if (!response.ok) {
|
|
155
|
-
|
|
193
|
+
return { result: { error: response.error || 'Failed to create invite' }, isError: true };
|
|
156
194
|
}
|
|
157
195
|
|
|
158
196
|
return { result: response.data };
|
|
159
197
|
};
|
|
160
198
|
|
|
161
|
-
export const updateMemberRole: Handler = async (args,
|
|
162
|
-
const { organization_id, user_id, role } = args
|
|
163
|
-
organization_id: string;
|
|
164
|
-
user_id: string;
|
|
165
|
-
role: Role;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
validateRequired(organization_id, 'organization_id');
|
|
169
|
-
validateRequired(user_id, 'user_id');
|
|
170
|
-
validateRequired(role, 'role');
|
|
171
|
-
validateUUID(organization_id, 'organization_id');
|
|
172
|
-
validateUUID(user_id, 'user_id');
|
|
173
|
-
|
|
174
|
-
if (!ROLE_ORDER.includes(role)) {
|
|
175
|
-
throw new Error(`Invalid role. Must be one of: ${ROLE_ORDER.join(', ')}`);
|
|
176
|
-
}
|
|
199
|
+
export const updateMemberRole: Handler = async (args, _ctx) => {
|
|
200
|
+
const { organization_id, user_id, role } = parseArgs(args, updateMemberRoleSchema);
|
|
177
201
|
|
|
178
202
|
if (role === 'owner') {
|
|
179
|
-
throw new
|
|
203
|
+
throw new ValidationError('Cannot assign owner role. Use transfer ownership instead.');
|
|
180
204
|
}
|
|
181
205
|
|
|
182
206
|
const apiClient = getApiClient();
|
|
183
|
-
const response = await apiClient.updateMemberRole(organization_id, user_id, role);
|
|
207
|
+
const response = await apiClient.updateMemberRole(organization_id, user_id, role as Role);
|
|
184
208
|
|
|
185
209
|
if (!response.ok) {
|
|
186
|
-
|
|
210
|
+
return { result: { error: response.error || 'Failed to update member role' }, isError: true };
|
|
187
211
|
}
|
|
188
212
|
|
|
189
213
|
return { result: response.data };
|
|
190
214
|
};
|
|
191
215
|
|
|
192
|
-
export const removeMember: Handler = async (args,
|
|
193
|
-
const { organization_id, user_id } = args
|
|
194
|
-
organization_id: string;
|
|
195
|
-
user_id: string;
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
validateRequired(organization_id, 'organization_id');
|
|
199
|
-
validateRequired(user_id, 'user_id');
|
|
200
|
-
validateUUID(organization_id, 'organization_id');
|
|
201
|
-
validateUUID(user_id, 'user_id');
|
|
216
|
+
export const removeMember: Handler = async (args, _ctx) => {
|
|
217
|
+
const { organization_id, user_id } = parseArgs(args, removeMemberSchema);
|
|
202
218
|
|
|
203
219
|
const apiClient = getApiClient();
|
|
204
220
|
const response = await apiClient.removeMember(organization_id, user_id);
|
|
205
221
|
|
|
206
222
|
if (!response.ok) {
|
|
207
|
-
|
|
223
|
+
return { result: { error: response.error || 'Failed to remove member' }, isError: true };
|
|
208
224
|
}
|
|
209
225
|
|
|
210
226
|
return { result: response.data };
|
|
211
227
|
};
|
|
212
228
|
|
|
213
|
-
export const leaveOrganization: Handler = async (args,
|
|
214
|
-
const { organization_id } = args
|
|
215
|
-
|
|
216
|
-
validateRequired(organization_id, 'organization_id');
|
|
217
|
-
validateUUID(organization_id, 'organization_id');
|
|
229
|
+
export const leaveOrganization: Handler = async (args, _ctx) => {
|
|
230
|
+
const { organization_id } = parseArgs(args, leaveOrganizationSchema);
|
|
218
231
|
|
|
219
232
|
const apiClient = getApiClient();
|
|
220
233
|
const response = await apiClient.leaveOrganization(organization_id);
|
|
221
234
|
|
|
222
235
|
if (!response.ok) {
|
|
223
|
-
|
|
236
|
+
return { result: { error: response.error || 'Failed to leave organization' }, isError: true };
|
|
224
237
|
}
|
|
225
238
|
|
|
226
239
|
return { result: response.data };
|
|
@@ -230,91 +243,53 @@ export const leaveOrganization: Handler = async (args, ctx) => {
|
|
|
230
243
|
// Project Sharing
|
|
231
244
|
// ============================================================================
|
|
232
245
|
|
|
233
|
-
export const shareProjectWithOrg: Handler = async (args,
|
|
234
|
-
const { project_id, organization_id, permission
|
|
235
|
-
project_id: string;
|
|
236
|
-
organization_id: string;
|
|
237
|
-
permission?: Permission;
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
validateRequired(project_id, 'project_id');
|
|
241
|
-
validateRequired(organization_id, 'organization_id');
|
|
242
|
-
validateUUID(project_id, 'project_id');
|
|
243
|
-
validateUUID(organization_id, 'organization_id');
|
|
244
|
-
|
|
245
|
-
if (!PERMISSION_ORDER.includes(permission)) {
|
|
246
|
-
throw new Error(`Invalid permission. Must be one of: ${PERMISSION_ORDER.join(', ')}`);
|
|
247
|
-
}
|
|
246
|
+
export const shareProjectWithOrg: Handler = async (args, _ctx) => {
|
|
247
|
+
const { project_id, organization_id, permission } = parseArgs(args, shareProjectWithOrgSchema);
|
|
248
248
|
|
|
249
249
|
const apiClient = getApiClient();
|
|
250
|
-
const response = await apiClient.shareProjectWithOrg(project_id, organization_id, permission);
|
|
250
|
+
const response = await apiClient.shareProjectWithOrg(project_id, organization_id, permission as Permission);
|
|
251
251
|
|
|
252
252
|
if (!response.ok) {
|
|
253
|
-
|
|
253
|
+
return { result: { error: response.error || 'Failed to share project' }, isError: true };
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
return { result: response.data };
|
|
257
257
|
};
|
|
258
258
|
|
|
259
|
-
export const updateProjectShare: Handler = async (args,
|
|
260
|
-
const { project_id, organization_id, permission } = args
|
|
261
|
-
project_id: string;
|
|
262
|
-
organization_id: string;
|
|
263
|
-
permission: Permission;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
validateRequired(project_id, 'project_id');
|
|
267
|
-
validateRequired(organization_id, 'organization_id');
|
|
268
|
-
validateRequired(permission, 'permission');
|
|
269
|
-
validateUUID(project_id, 'project_id');
|
|
270
|
-
validateUUID(organization_id, 'organization_id');
|
|
271
|
-
|
|
272
|
-
if (!PERMISSION_ORDER.includes(permission)) {
|
|
273
|
-
throw new Error(`Invalid permission. Must be one of: ${PERMISSION_ORDER.join(', ')}`);
|
|
274
|
-
}
|
|
259
|
+
export const updateProjectShare: Handler = async (args, _ctx) => {
|
|
260
|
+
const { project_id, organization_id, permission } = parseArgs(args, updateProjectShareSchema);
|
|
275
261
|
|
|
276
262
|
const apiClient = getApiClient();
|
|
277
|
-
const response = await apiClient.updateProjectShare(project_id, organization_id, permission);
|
|
263
|
+
const response = await apiClient.updateProjectShare(project_id, organization_id, permission as Permission);
|
|
278
264
|
|
|
279
265
|
if (!response.ok) {
|
|
280
|
-
|
|
266
|
+
return { result: { error: response.error || 'Failed to update share' }, isError: true };
|
|
281
267
|
}
|
|
282
268
|
|
|
283
269
|
return { result: response.data };
|
|
284
270
|
};
|
|
285
271
|
|
|
286
|
-
export const unshareProject: Handler = async (args,
|
|
287
|
-
const { project_id, organization_id } = args
|
|
288
|
-
project_id: string;
|
|
289
|
-
organization_id: string;
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
validateRequired(project_id, 'project_id');
|
|
293
|
-
validateRequired(organization_id, 'organization_id');
|
|
294
|
-
validateUUID(project_id, 'project_id');
|
|
295
|
-
validateUUID(organization_id, 'organization_id');
|
|
272
|
+
export const unshareProject: Handler = async (args, _ctx) => {
|
|
273
|
+
const { project_id, organization_id } = parseArgs(args, unshareProjectSchema);
|
|
296
274
|
|
|
297
275
|
const apiClient = getApiClient();
|
|
298
276
|
const response = await apiClient.unshareProject(project_id, organization_id);
|
|
299
277
|
|
|
300
278
|
if (!response.ok) {
|
|
301
|
-
|
|
279
|
+
return { result: { error: response.error || 'Failed to unshare project' }, isError: true };
|
|
302
280
|
}
|
|
303
281
|
|
|
304
282
|
return { result: response.data };
|
|
305
283
|
};
|
|
306
284
|
|
|
307
|
-
export const listProjectShares: Handler = async (args,
|
|
308
|
-
const { project_id } = args
|
|
309
|
-
|
|
310
|
-
validateRequired(project_id, 'project_id');
|
|
311
|
-
validateUUID(project_id, 'project_id');
|
|
285
|
+
export const listProjectShares: Handler = async (args, _ctx) => {
|
|
286
|
+
const { project_id } = parseArgs(args, listProjectSharesSchema);
|
|
312
287
|
|
|
313
288
|
const apiClient = getApiClient();
|
|
314
289
|
const response = await apiClient.listProjectShares(project_id);
|
|
315
290
|
|
|
316
291
|
if (!response.ok) {
|
|
317
|
-
|
|
292
|
+
return { result: { error: response.error || 'Failed to list shares' }, isError: true };
|
|
318
293
|
}
|
|
319
294
|
|
|
320
295
|
return { result: response.data };
|
|
@@ -103,19 +103,22 @@ describe('logProgress', () => {
|
|
|
103
103
|
expect(mockApiClient.logProgress).toHaveBeenCalled();
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
it('should
|
|
106
|
+
it('should return error when API call fails', async () => {
|
|
107
107
|
mockApiClient.logProgress.mockResolvedValue({
|
|
108
108
|
ok: false,
|
|
109
109
|
error: 'Insert failed',
|
|
110
110
|
});
|
|
111
111
|
const ctx = createMockContext();
|
|
112
112
|
|
|
113
|
-
await
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
).
|
|
113
|
+
const result = await logProgress({
|
|
114
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
115
|
+
summary: 'Progress',
|
|
116
|
+
}, ctx);
|
|
117
|
+
|
|
118
|
+
expect(result.isError).toBe(true);
|
|
119
|
+
expect(result.result).toMatchObject({
|
|
120
|
+
error: 'Insert failed',
|
|
121
|
+
});
|
|
119
122
|
});
|
|
120
123
|
|
|
121
124
|
it('should throw error for missing project_id', async () => {
|
|
@@ -246,18 +249,21 @@ describe('getActivityFeed', () => {
|
|
|
246
249
|
);
|
|
247
250
|
});
|
|
248
251
|
|
|
249
|
-
it('should
|
|
252
|
+
it('should return error when API call fails', async () => {
|
|
250
253
|
mockApiClient.getActivityFeed.mockResolvedValue({
|
|
251
254
|
ok: false,
|
|
252
255
|
error: 'Query failed',
|
|
253
256
|
});
|
|
254
257
|
const ctx = createMockContext();
|
|
255
258
|
|
|
256
|
-
await
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
).
|
|
259
|
+
const result = await getActivityFeed(
|
|
260
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
261
|
+
ctx
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
expect(result.isError).toBe(true);
|
|
265
|
+
expect(result.result).toMatchObject({
|
|
266
|
+
error: 'Query failed',
|
|
267
|
+
});
|
|
262
268
|
});
|
|
263
269
|
});
|
package/src/handlers/progress.ts
CHANGED
|
@@ -9,20 +9,27 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
12
|
-
import {
|
|
12
|
+
import { parseArgs, uuidValidator } from '../validators.js';
|
|
13
13
|
import { getApiClient } from '../api-client.js';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
// Argument schemas for type-safe parsing
|
|
16
|
+
const logProgressSchema = {
|
|
17
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
18
|
+
task_id: { type: 'string' as const, validate: uuidValidator },
|
|
19
|
+
summary: { type: 'string' as const, required: true as const },
|
|
20
|
+
details: { type: 'string' as const },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getActivityFeedSchema = {
|
|
24
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
25
|
+
limit: { type: 'number' as const, default: 50 },
|
|
26
|
+
since: { type: 'string' as const },
|
|
27
|
+
types: { type: 'array' as const },
|
|
28
|
+
created_by: { type: 'string' as const },
|
|
29
|
+
};
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
validateRequired(summary, 'summary');
|
|
31
|
+
export const logProgress: Handler = async (args, ctx) => {
|
|
32
|
+
const { project_id, task_id, summary, details } = parseArgs(args, logProgressSchema);
|
|
26
33
|
|
|
27
34
|
const { session } = ctx;
|
|
28
35
|
const apiClient = getApiClient();
|
|
@@ -35,32 +42,27 @@ export const logProgress: Handler = async (args, ctx) => {
|
|
|
35
42
|
});
|
|
36
43
|
|
|
37
44
|
if (!response.ok) {
|
|
38
|
-
|
|
45
|
+
return { result: { error: response.error || 'Failed to log progress' }, isError: true };
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
return { result: { success: true, progress_id: response.data?.progress_id } };
|
|
42
49
|
};
|
|
43
50
|
|
|
44
|
-
export const getActivityFeed: Handler = async (args,
|
|
45
|
-
const { project_id, limit
|
|
46
|
-
project_id: string;
|
|
47
|
-
limit?: number;
|
|
48
|
-
since?: string;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
validateRequired(project_id, 'project_id');
|
|
52
|
-
validateUUID(project_id, 'project_id');
|
|
51
|
+
export const getActivityFeed: Handler = async (args, _ctx) => {
|
|
52
|
+
const { project_id, limit, since, types, created_by } = parseArgs(args, getActivityFeedSchema);
|
|
53
53
|
|
|
54
54
|
const apiClient = getApiClient();
|
|
55
|
-
const effectiveLimit = Math.min(limit, 200);
|
|
55
|
+
const effectiveLimit = Math.min(limit ?? 50, 200);
|
|
56
56
|
|
|
57
57
|
const response = await apiClient.getActivityFeed(project_id, {
|
|
58
58
|
limit: effectiveLimit,
|
|
59
|
-
since
|
|
59
|
+
since,
|
|
60
|
+
types: types as string[] | undefined,
|
|
61
|
+
created_by
|
|
60
62
|
});
|
|
61
63
|
|
|
62
64
|
if (!response.ok) {
|
|
63
|
-
|
|
65
|
+
return { result: { error: response.error || 'Failed to fetch activity feed' }, isError: true };
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
return { result: { activities: response.data?.activities || [] } };
|
|
@@ -120,14 +120,17 @@ describe('getProjectContext', () => {
|
|
|
120
120
|
expect(result.result).toHaveProperty('active_tasks');
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
it('should
|
|
123
|
+
it('should return error when API call fails', async () => {
|
|
124
124
|
mockApiClient.listProjects.mockResolvedValue({
|
|
125
125
|
ok: false,
|
|
126
126
|
error: 'Database error',
|
|
127
127
|
});
|
|
128
128
|
const ctx = createMockContext();
|
|
129
129
|
|
|
130
|
-
await
|
|
130
|
+
const result = await getProjectContext({}, ctx);
|
|
131
|
+
|
|
132
|
+
expect(result.isError).toBe(true);
|
|
133
|
+
expect(result.result).toMatchObject({ error: 'Database error' });
|
|
131
134
|
});
|
|
132
135
|
});
|
|
133
136
|
|
|
@@ -237,16 +240,17 @@ describe('getGitWorkflow', () => {
|
|
|
237
240
|
expect((result.result as { instructions: string[] }).instructions[0]).toContain('No git workflow configured');
|
|
238
241
|
});
|
|
239
242
|
|
|
240
|
-
it('should
|
|
243
|
+
it('should return error when project not found', async () => {
|
|
241
244
|
mockApiClient.getGitWorkflow.mockResolvedValue({
|
|
242
245
|
ok: false,
|
|
243
246
|
error: 'Project not found',
|
|
244
247
|
});
|
|
245
248
|
const ctx = createMockContext();
|
|
246
249
|
|
|
247
|
-
await
|
|
248
|
-
|
|
249
|
-
).
|
|
250
|
+
const result = await getGitWorkflow({ project_id: VALID_UUID }, ctx);
|
|
251
|
+
|
|
252
|
+
expect(result.isError).toBe(true);
|
|
253
|
+
expect(result.result).toMatchObject({ error: 'Project not found' });
|
|
250
254
|
});
|
|
251
255
|
});
|
|
252
256
|
|
|
@@ -327,16 +331,17 @@ describe('createProject', () => {
|
|
|
327
331
|
});
|
|
328
332
|
});
|
|
329
333
|
|
|
330
|
-
it('should
|
|
334
|
+
it('should return error when insert fails', async () => {
|
|
331
335
|
mockApiClient.createProject.mockResolvedValue({
|
|
332
336
|
ok: false,
|
|
333
|
-
error: 'Failed to create project
|
|
337
|
+
error: 'Failed to create project',
|
|
334
338
|
});
|
|
335
339
|
const ctx = createMockContext();
|
|
336
340
|
|
|
337
|
-
await
|
|
338
|
-
|
|
339
|
-
).
|
|
341
|
+
const result = await createProject({ name: 'Test Project' }, ctx);
|
|
342
|
+
|
|
343
|
+
expect(result.isError).toBe(true);
|
|
344
|
+
expect(result.result).toMatchObject({ error: 'Failed to create project' });
|
|
340
345
|
});
|
|
341
346
|
|
|
342
347
|
it('should throw error when name is missing', async () => {
|
|
@@ -445,19 +450,20 @@ describe('updateProject', () => {
|
|
|
445
450
|
);
|
|
446
451
|
});
|
|
447
452
|
|
|
448
|
-
it('should
|
|
453
|
+
it('should return error when update fails', async () => {
|
|
449
454
|
mockApiClient.updateProject.mockResolvedValue({
|
|
450
455
|
ok: false,
|
|
451
|
-
error: 'Failed to update project
|
|
456
|
+
error: 'Failed to update project',
|
|
452
457
|
});
|
|
453
458
|
const ctx = createMockContext();
|
|
454
459
|
|
|
455
|
-
await
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
).
|
|
460
|
+
const result = await updateProject({
|
|
461
|
+
project_id: VALID_UUID,
|
|
462
|
+
name: 'Test',
|
|
463
|
+
}, ctx);
|
|
464
|
+
|
|
465
|
+
expect(result.isError).toBe(true);
|
|
466
|
+
expect(result.result).toMatchObject({ error: 'Failed to update project' });
|
|
461
467
|
});
|
|
462
468
|
});
|
|
463
469
|
|
|
@@ -522,18 +528,19 @@ describe('updateProjectReadme', () => {
|
|
|
522
528
|
);
|
|
523
529
|
});
|
|
524
530
|
|
|
525
|
-
it('should
|
|
531
|
+
it('should return error when update fails', async () => {
|
|
526
532
|
mockApiClient.updateProjectReadme.mockResolvedValue({
|
|
527
533
|
ok: false,
|
|
528
|
-
error: 'Failed to update README
|
|
534
|
+
error: 'Failed to update README',
|
|
529
535
|
});
|
|
530
536
|
const ctx = createMockContext();
|
|
531
537
|
|
|
532
|
-
await
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
).
|
|
538
|
+
const result = await updateProjectReadme({
|
|
539
|
+
project_id: VALID_UUID,
|
|
540
|
+
readme_content: '# README',
|
|
541
|
+
}, ctx);
|
|
542
|
+
|
|
543
|
+
expect(result.isError).toBe(true);
|
|
544
|
+
expect(result.result).toMatchObject({ error: 'Failed to update README' });
|
|
538
545
|
});
|
|
539
546
|
});
|