@vibescope/mcp-server 0.0.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.
Files changed (170) hide show
  1. package/README.md +98 -0
  2. package/dist/cli.d.ts +34 -0
  3. package/dist/cli.js +356 -0
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +367 -0
  6. package/dist/handlers/__test-utils__.d.ts +72 -0
  7. package/dist/handlers/__test-utils__.js +176 -0
  8. package/dist/handlers/blockers.d.ts +18 -0
  9. package/dist/handlers/blockers.js +81 -0
  10. package/dist/handlers/bodies-of-work.d.ts +34 -0
  11. package/dist/handlers/bodies-of-work.js +614 -0
  12. package/dist/handlers/checkouts.d.ts +37 -0
  13. package/dist/handlers/checkouts.js +377 -0
  14. package/dist/handlers/cost.d.ts +39 -0
  15. package/dist/handlers/cost.js +247 -0
  16. package/dist/handlers/decisions.d.ts +16 -0
  17. package/dist/handlers/decisions.js +64 -0
  18. package/dist/handlers/deployment.d.ts +36 -0
  19. package/dist/handlers/deployment.js +1062 -0
  20. package/dist/handlers/discovery.d.ts +14 -0
  21. package/dist/handlers/discovery.js +870 -0
  22. package/dist/handlers/fallback.d.ts +18 -0
  23. package/dist/handlers/fallback.js +216 -0
  24. package/dist/handlers/findings.d.ts +18 -0
  25. package/dist/handlers/findings.js +110 -0
  26. package/dist/handlers/git-issues.d.ts +22 -0
  27. package/dist/handlers/git-issues.js +247 -0
  28. package/dist/handlers/ideas.d.ts +19 -0
  29. package/dist/handlers/ideas.js +188 -0
  30. package/dist/handlers/index.d.ts +29 -0
  31. package/dist/handlers/index.js +65 -0
  32. package/dist/handlers/knowledge-query.d.ts +22 -0
  33. package/dist/handlers/knowledge-query.js +253 -0
  34. package/dist/handlers/knowledge.d.ts +12 -0
  35. package/dist/handlers/knowledge.js +108 -0
  36. package/dist/handlers/milestones.d.ts +20 -0
  37. package/dist/handlers/milestones.js +179 -0
  38. package/dist/handlers/organizations.d.ts +36 -0
  39. package/dist/handlers/organizations.js +428 -0
  40. package/dist/handlers/progress.d.ts +14 -0
  41. package/dist/handlers/progress.js +149 -0
  42. package/dist/handlers/project.d.ts +20 -0
  43. package/dist/handlers/project.js +278 -0
  44. package/dist/handlers/requests.d.ts +16 -0
  45. package/dist/handlers/requests.js +131 -0
  46. package/dist/handlers/roles.d.ts +30 -0
  47. package/dist/handlers/roles.js +281 -0
  48. package/dist/handlers/session.d.ts +20 -0
  49. package/dist/handlers/session.js +791 -0
  50. package/dist/handlers/tasks.d.ts +52 -0
  51. package/dist/handlers/tasks.js +1111 -0
  52. package/dist/handlers/tasks.test.d.ts +1 -0
  53. package/dist/handlers/tasks.test.js +431 -0
  54. package/dist/handlers/types.d.ts +94 -0
  55. package/dist/handlers/types.js +1 -0
  56. package/dist/handlers/validation.d.ts +16 -0
  57. package/dist/handlers/validation.js +188 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +2707 -0
  60. package/dist/knowledge.d.ts +6 -0
  61. package/dist/knowledge.js +121 -0
  62. package/dist/tools.d.ts +2 -0
  63. package/dist/tools.js +2498 -0
  64. package/dist/utils.d.ts +149 -0
  65. package/dist/utils.js +317 -0
  66. package/dist/utils.test.d.ts +1 -0
  67. package/dist/utils.test.js +532 -0
  68. package/dist/validators.d.ts +35 -0
  69. package/dist/validators.js +111 -0
  70. package/dist/validators.test.d.ts +1 -0
  71. package/dist/validators.test.js +176 -0
  72. package/package.json +44 -0
  73. package/src/cli.test.ts +442 -0
  74. package/src/cli.ts +439 -0
  75. package/src/handlers/__test-utils__.ts +217 -0
  76. package/src/handlers/blockers.test.ts +390 -0
  77. package/src/handlers/blockers.ts +110 -0
  78. package/src/handlers/bodies-of-work.test.ts +1276 -0
  79. package/src/handlers/bodies-of-work.ts +783 -0
  80. package/src/handlers/cost.test.ts +436 -0
  81. package/src/handlers/cost.ts +322 -0
  82. package/src/handlers/decisions.test.ts +401 -0
  83. package/src/handlers/decisions.ts +86 -0
  84. package/src/handlers/deployment.test.ts +516 -0
  85. package/src/handlers/deployment.ts +1289 -0
  86. package/src/handlers/discovery.test.ts +254 -0
  87. package/src/handlers/discovery.ts +969 -0
  88. package/src/handlers/fallback.test.ts +687 -0
  89. package/src/handlers/fallback.ts +260 -0
  90. package/src/handlers/findings.test.ts +565 -0
  91. package/src/handlers/findings.ts +153 -0
  92. package/src/handlers/ideas.test.ts +753 -0
  93. package/src/handlers/ideas.ts +247 -0
  94. package/src/handlers/index.ts +69 -0
  95. package/src/handlers/milestones.test.ts +584 -0
  96. package/src/handlers/milestones.ts +217 -0
  97. package/src/handlers/organizations.test.ts +997 -0
  98. package/src/handlers/organizations.ts +550 -0
  99. package/src/handlers/progress.test.ts +369 -0
  100. package/src/handlers/progress.ts +188 -0
  101. package/src/handlers/project.test.ts +562 -0
  102. package/src/handlers/project.ts +352 -0
  103. package/src/handlers/requests.test.ts +531 -0
  104. package/src/handlers/requests.ts +150 -0
  105. package/src/handlers/session.test.ts +459 -0
  106. package/src/handlers/session.ts +912 -0
  107. package/src/handlers/tasks.test.ts +602 -0
  108. package/src/handlers/tasks.ts +1393 -0
  109. package/src/handlers/types.ts +88 -0
  110. package/src/handlers/validation.test.ts +880 -0
  111. package/src/handlers/validation.ts +223 -0
  112. package/src/index.ts +3205 -0
  113. package/src/knowledge.ts +132 -0
  114. package/src/tmpclaude-0078-cwd +1 -0
  115. package/src/tmpclaude-0ee1-cwd +1 -0
  116. package/src/tmpclaude-2dd5-cwd +1 -0
  117. package/src/tmpclaude-344c-cwd +1 -0
  118. package/src/tmpclaude-3860-cwd +1 -0
  119. package/src/tmpclaude-4b63-cwd +1 -0
  120. package/src/tmpclaude-5c73-cwd +1 -0
  121. package/src/tmpclaude-5ee3-cwd +1 -0
  122. package/src/tmpclaude-6795-cwd +1 -0
  123. package/src/tmpclaude-709e-cwd +1 -0
  124. package/src/tmpclaude-9839-cwd +1 -0
  125. package/src/tmpclaude-d829-cwd +1 -0
  126. package/src/tmpclaude-e072-cwd +1 -0
  127. package/src/tmpclaude-f6ee-cwd +1 -0
  128. package/src/utils.test.ts +681 -0
  129. package/src/utils.ts +375 -0
  130. package/src/validators.test.ts +223 -0
  131. package/src/validators.ts +122 -0
  132. package/tmpclaude-0439-cwd +1 -0
  133. package/tmpclaude-132f-cwd +1 -0
  134. package/tmpclaude-15bb-cwd +1 -0
  135. package/tmpclaude-165a-cwd +1 -0
  136. package/tmpclaude-1ba9-cwd +1 -0
  137. package/tmpclaude-21a3-cwd +1 -0
  138. package/tmpclaude-2a38-cwd +1 -0
  139. package/tmpclaude-2adf-cwd +1 -0
  140. package/tmpclaude-2f56-cwd +1 -0
  141. package/tmpclaude-3626-cwd +1 -0
  142. package/tmpclaude-3727-cwd +1 -0
  143. package/tmpclaude-40bc-cwd +1 -0
  144. package/tmpclaude-436f-cwd +1 -0
  145. package/tmpclaude-4783-cwd +1 -0
  146. package/tmpclaude-4b6d-cwd +1 -0
  147. package/tmpclaude-4ba4-cwd +1 -0
  148. package/tmpclaude-51e6-cwd +1 -0
  149. package/tmpclaude-5ecf-cwd +1 -0
  150. package/tmpclaude-6f97-cwd +1 -0
  151. package/tmpclaude-7fb2-cwd +1 -0
  152. package/tmpclaude-825c-cwd +1 -0
  153. package/tmpclaude-8baf-cwd +1 -0
  154. package/tmpclaude-8d9f-cwd +1 -0
  155. package/tmpclaude-975c-cwd +1 -0
  156. package/tmpclaude-9983-cwd +1 -0
  157. package/tmpclaude-a045-cwd +1 -0
  158. package/tmpclaude-ac4a-cwd +1 -0
  159. package/tmpclaude-b593-cwd +1 -0
  160. package/tmpclaude-b891-cwd +1 -0
  161. package/tmpclaude-c032-cwd +1 -0
  162. package/tmpclaude-cf43-cwd +1 -0
  163. package/tmpclaude-d040-cwd +1 -0
  164. package/tmpclaude-dcdd-cwd +1 -0
  165. package/tmpclaude-dcee-cwd +1 -0
  166. package/tmpclaude-e16b-cwd +1 -0
  167. package/tmpclaude-ecd2-cwd +1 -0
  168. package/tmpclaude-f48d-cwd +1 -0
  169. package/tsconfig.json +16 -0
  170. package/vitest.config.ts +13 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Milestone Handlers
3
+ *
4
+ * Handles task milestone CRUD operations:
5
+ * - add_milestone
6
+ * - update_milestone
7
+ * - complete_milestone
8
+ * - delete_milestone
9
+ * - get_milestones
10
+ */
11
+ import type { Handler, HandlerRegistry } from './types.js';
12
+ export declare const addMilestone: Handler;
13
+ export declare const updateMilestone: Handler;
14
+ export declare const completeMilestone: Handler;
15
+ export declare const deleteMilestone: Handler;
16
+ export declare const getMilestones: Handler;
17
+ /**
18
+ * Milestone handlers registry
19
+ */
20
+ export declare const milestoneHandlers: HandlerRegistry;
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Milestone Handlers
3
+ *
4
+ * Handles task milestone CRUD operations:
5
+ * - add_milestone
6
+ * - update_milestone
7
+ * - complete_milestone
8
+ * - delete_milestone
9
+ * - get_milestones
10
+ */
11
+ import { ValidationError, validateRequired, validateUUID } from '../validators.js';
12
+ export const addMilestone = async (args, ctx) => {
13
+ const { task_id, title, description, order_index } = args;
14
+ validateRequired(task_id, 'task_id');
15
+ validateUUID(task_id, 'task_id');
16
+ validateRequired(title, 'title');
17
+ const { supabase, session } = ctx;
18
+ // Verify task exists
19
+ const { data: task, error: taskError } = await supabase
20
+ .from('tasks')
21
+ .select('id, project_id')
22
+ .eq('id', task_id)
23
+ .single();
24
+ if (taskError || !task) {
25
+ throw new Error('Task not found');
26
+ }
27
+ // Get the next order_index if not provided
28
+ let orderIdx = order_index;
29
+ if (orderIdx === undefined) {
30
+ const { data: maxOrder } = await supabase
31
+ .from('task_milestones')
32
+ .select('order_index')
33
+ .eq('task_id', task_id)
34
+ .order('order_index', { ascending: false })
35
+ .limit(1)
36
+ .single();
37
+ orderIdx = maxOrder ? maxOrder.order_index + 1 : 0;
38
+ }
39
+ const { data: milestone, error } = await supabase
40
+ .from('task_milestones')
41
+ .insert({
42
+ task_id,
43
+ title,
44
+ description: description || null,
45
+ order_index: orderIdx,
46
+ created_by: 'agent',
47
+ created_by_session_id: session.currentSessionId,
48
+ })
49
+ .select()
50
+ .single();
51
+ if (error)
52
+ throw error;
53
+ return {
54
+ result: {
55
+ success: true,
56
+ milestone,
57
+ },
58
+ };
59
+ };
60
+ export const updateMilestone = async (args, ctx) => {
61
+ const { milestone_id, title, description, status, order_index } = args;
62
+ validateRequired(milestone_id, 'milestone_id');
63
+ validateUUID(milestone_id, 'milestone_id');
64
+ const updates = {};
65
+ if (title !== undefined)
66
+ updates.title = title;
67
+ if (description !== undefined)
68
+ updates.description = description;
69
+ if (order_index !== undefined)
70
+ updates.order_index = order_index;
71
+ if (status !== undefined) {
72
+ if (!['pending', 'in_progress', 'completed'].includes(status)) {
73
+ throw new ValidationError('status must be pending, in_progress, or completed');
74
+ }
75
+ updates.status = status;
76
+ if (status === 'completed') {
77
+ updates.completed_at = new Date().toISOString();
78
+ }
79
+ else {
80
+ updates.completed_at = null;
81
+ }
82
+ }
83
+ if (Object.keys(updates).length === 0) {
84
+ throw new ValidationError('At least one field to update is required');
85
+ }
86
+ const { data: milestone, error } = await ctx.supabase
87
+ .from('task_milestones')
88
+ .update(updates)
89
+ .eq('id', milestone_id)
90
+ .select()
91
+ .single();
92
+ if (error)
93
+ throw error;
94
+ return {
95
+ result: {
96
+ success: true,
97
+ milestone,
98
+ },
99
+ };
100
+ };
101
+ export const completeMilestone = async (args, ctx) => {
102
+ const { milestone_id } = args;
103
+ validateRequired(milestone_id, 'milestone_id');
104
+ validateUUID(milestone_id, 'milestone_id');
105
+ const { data: milestone, error } = await ctx.supabase
106
+ .from('task_milestones')
107
+ .update({
108
+ status: 'completed',
109
+ completed_at: new Date().toISOString(),
110
+ })
111
+ .eq('id', milestone_id)
112
+ .select()
113
+ .single();
114
+ if (error)
115
+ throw error;
116
+ return {
117
+ result: {
118
+ success: true,
119
+ milestone,
120
+ },
121
+ };
122
+ };
123
+ export const deleteMilestone = async (args, ctx) => {
124
+ const { milestone_id } = args;
125
+ validateRequired(milestone_id, 'milestone_id');
126
+ validateUUID(milestone_id, 'milestone_id');
127
+ const { error } = await ctx.supabase
128
+ .from('task_milestones')
129
+ .delete()
130
+ .eq('id', milestone_id);
131
+ if (error)
132
+ throw error;
133
+ return {
134
+ result: {
135
+ success: true,
136
+ message: 'Milestone deleted',
137
+ },
138
+ };
139
+ };
140
+ export const getMilestones = async (args, ctx) => {
141
+ const { task_id } = args;
142
+ validateRequired(task_id, 'task_id');
143
+ validateUUID(task_id, 'task_id');
144
+ const { data: milestones, error } = await ctx.supabase
145
+ .from('task_milestones')
146
+ .select('*')
147
+ .eq('task_id', task_id)
148
+ .order('order_index', { ascending: true });
149
+ if (error)
150
+ throw error;
151
+ // Calculate progress stats
152
+ const total = milestones?.length || 0;
153
+ const completed = milestones?.filter(m => m.status === 'completed').length || 0;
154
+ const in_progress = milestones?.filter(m => m.status === 'in_progress').length || 0;
155
+ const pending = total - completed - in_progress;
156
+ const progress_percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
157
+ return {
158
+ result: {
159
+ milestones: milestones || [],
160
+ stats: {
161
+ total,
162
+ completed,
163
+ in_progress,
164
+ pending,
165
+ progress_percentage,
166
+ },
167
+ },
168
+ };
169
+ };
170
+ /**
171
+ * Milestone handlers registry
172
+ */
173
+ export const milestoneHandlers = {
174
+ add_milestone: addMilestone,
175
+ update_milestone: updateMilestone,
176
+ complete_milestone: completeMilestone,
177
+ delete_milestone: deleteMilestone,
178
+ get_milestones: getMilestones,
179
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Organizations Handlers
3
+ *
4
+ * Handles organization management, membership, and project sharing:
5
+ * - list_organizations
6
+ * - create_organization
7
+ * - update_organization
8
+ * - delete_organization
9
+ * - list_org_members
10
+ * - invite_member
11
+ * - update_member_role
12
+ * - remove_member
13
+ * - leave_organization
14
+ * - share_project_with_org
15
+ * - update_project_share
16
+ * - unshare_project
17
+ * - list_project_shares
18
+ */
19
+ import type { Handler, HandlerRegistry } from './types.js';
20
+ export declare const listOrganizations: Handler;
21
+ export declare const createOrganization: Handler;
22
+ export declare const updateOrganization: Handler;
23
+ export declare const deleteOrganization: Handler;
24
+ export declare const listOrgMembers: Handler;
25
+ export declare const inviteMember: Handler;
26
+ export declare const updateMemberRole: Handler;
27
+ export declare const removeMember: Handler;
28
+ export declare const leaveOrganization: Handler;
29
+ export declare const shareProjectWithOrg: Handler;
30
+ export declare const updateProjectShare: Handler;
31
+ export declare const unshareProject: Handler;
32
+ export declare const listProjectShares: Handler;
33
+ /**
34
+ * Organizations handlers registry
35
+ */
36
+ export declare const organizationHandlers: HandlerRegistry;
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Organizations Handlers
3
+ *
4
+ * Handles organization management, membership, and project sharing:
5
+ * - list_organizations
6
+ * - create_organization
7
+ * - update_organization
8
+ * - delete_organization
9
+ * - list_org_members
10
+ * - invite_member
11
+ * - update_member_role
12
+ * - remove_member
13
+ * - leave_organization
14
+ * - share_project_with_org
15
+ * - update_project_share
16
+ * - unshare_project
17
+ * - list_project_shares
18
+ */
19
+ import { validateRequired, validateUUID } from '../validators.js';
20
+ import { randomBytes } from 'crypto';
21
+ // Valid roles in order of permission level
22
+ const ROLE_ORDER = ['viewer', 'member', 'admin', 'owner'];
23
+ // Valid share permissions
24
+ const PERMISSION_ORDER = ['read', 'write', 'admin'];
25
+ /**
26
+ * Generate a URL-friendly slug from a name
27
+ */
28
+ function generateSlug(name) {
29
+ return name
30
+ .toLowerCase()
31
+ .replace(/[^a-z0-9]+/g, '-')
32
+ .replace(/^-|-$/g, '')
33
+ .slice(0, 50);
34
+ }
35
+ /**
36
+ * Generate a secure invite token
37
+ */
38
+ function generateInviteToken() {
39
+ return randomBytes(32).toString('base64url');
40
+ }
41
+ // ============================================================================
42
+ // Organization Management
43
+ // ============================================================================
44
+ export const listOrganizations = async (_args, ctx) => {
45
+ const { supabase, auth } = ctx;
46
+ const { data, error } = await supabase
47
+ .from('organization_members')
48
+ .select(`
49
+ role,
50
+ joined_at,
51
+ organizations (
52
+ id,
53
+ name,
54
+ slug,
55
+ description,
56
+ logo_url,
57
+ owner_id,
58
+ created_at
59
+ )
60
+ `)
61
+ .eq('user_id', auth.userId)
62
+ .order('joined_at', { ascending: false });
63
+ if (error)
64
+ throw new Error(`Failed to list organizations: ${error.message}`);
65
+ const organizations = (data || []).map((m) => ({
66
+ ...m.organizations,
67
+ role: m.role,
68
+ joined_at: m.joined_at,
69
+ }));
70
+ return { result: { organizations, count: organizations.length } };
71
+ };
72
+ export const createOrganization = async (args, ctx) => {
73
+ const { name, description, slug: customSlug } = args;
74
+ validateRequired(name, 'name');
75
+ const { supabase, auth } = ctx;
76
+ const slug = customSlug || generateSlug(name);
77
+ // Check if slug is available
78
+ const { data: existing } = await supabase
79
+ .from('organizations')
80
+ .select('id')
81
+ .eq('slug', slug)
82
+ .maybeSingle();
83
+ if (existing) {
84
+ throw new Error(`Organization slug "${slug}" is already taken`);
85
+ }
86
+ const { data, error } = await supabase
87
+ .from('organizations')
88
+ .insert({
89
+ name,
90
+ slug,
91
+ description: description || null,
92
+ owner_id: auth.userId,
93
+ })
94
+ .select()
95
+ .single();
96
+ if (error)
97
+ throw new Error(`Failed to create organization: ${error.message}`);
98
+ return {
99
+ result: {
100
+ success: true,
101
+ organization: data,
102
+ message: `Organization "${name}" created. You are the owner.`,
103
+ },
104
+ };
105
+ };
106
+ export const updateOrganization = async (args, ctx) => {
107
+ const { organization_id, name, description, logo_url } = args;
108
+ validateRequired(organization_id, 'organization_id');
109
+ validateUUID(organization_id, 'organization_id');
110
+ const { supabase } = ctx;
111
+ const updates = {};
112
+ if (name !== undefined)
113
+ updates.name = name;
114
+ if (description !== undefined)
115
+ updates.description = description;
116
+ if (logo_url !== undefined)
117
+ updates.logo_url = logo_url;
118
+ if (Object.keys(updates).length === 0) {
119
+ throw new Error('No updates provided');
120
+ }
121
+ const { data, error } = await supabase
122
+ .from('organizations')
123
+ .update(updates)
124
+ .eq('id', organization_id)
125
+ .select()
126
+ .single();
127
+ if (error)
128
+ throw new Error(`Failed to update organization: ${error.message}`);
129
+ return { result: { success: true, organization: data } };
130
+ };
131
+ export const deleteOrganization = async (args, ctx) => {
132
+ const { organization_id } = args;
133
+ validateRequired(organization_id, 'organization_id');
134
+ validateUUID(organization_id, 'organization_id');
135
+ const { supabase } = ctx;
136
+ const { error } = await supabase
137
+ .from('organizations')
138
+ .delete()
139
+ .eq('id', organization_id);
140
+ if (error)
141
+ throw new Error(`Failed to delete organization: ${error.message}`);
142
+ return {
143
+ result: {
144
+ success: true,
145
+ message: 'Organization deleted. All shares have been removed.',
146
+ },
147
+ };
148
+ };
149
+ // ============================================================================
150
+ // Member Management
151
+ // ============================================================================
152
+ export const listOrgMembers = async (args, ctx) => {
153
+ const { organization_id } = args;
154
+ validateRequired(organization_id, 'organization_id');
155
+ validateUUID(organization_id, 'organization_id');
156
+ const { supabase } = ctx;
157
+ const { data, error } = await supabase
158
+ .from('organization_members')
159
+ .select('id, user_id, role, joined_at, invited_by')
160
+ .eq('organization_id', organization_id)
161
+ .order('role', { ascending: true })
162
+ .order('joined_at', { ascending: true });
163
+ if (error)
164
+ throw new Error(`Failed to list members: ${error.message}`);
165
+ return { result: { members: data || [], count: data?.length || 0 } };
166
+ };
167
+ export const inviteMember = async (args, ctx) => {
168
+ const { organization_id, email, role = 'member' } = args;
169
+ validateRequired(organization_id, 'organization_id');
170
+ validateRequired(email, 'email');
171
+ validateUUID(organization_id, 'organization_id');
172
+ if (!['admin', 'member', 'viewer'].includes(role)) {
173
+ throw new Error('Invalid role. Must be admin, member, or viewer.');
174
+ }
175
+ const { supabase, auth } = ctx;
176
+ const token = generateInviteToken();
177
+ // Check for existing pending invite
178
+ const { data: existing } = await supabase
179
+ .from('organization_invites')
180
+ .select('id')
181
+ .eq('organization_id', organization_id)
182
+ .eq('email', email)
183
+ .is('accepted_at', null)
184
+ .gt('expires_at', new Date().toISOString())
185
+ .maybeSingle();
186
+ if (existing) {
187
+ throw new Error(`A pending invite already exists for ${email}`);
188
+ }
189
+ const { data, error } = await supabase
190
+ .from('organization_invites')
191
+ .insert({
192
+ organization_id,
193
+ email,
194
+ role,
195
+ token,
196
+ invited_by: auth.userId,
197
+ })
198
+ .select()
199
+ .single();
200
+ if (error)
201
+ throw new Error(`Failed to create invite: ${error.message}`);
202
+ return {
203
+ result: {
204
+ success: true,
205
+ invite: data,
206
+ message: `Invite sent to ${email} with role "${role}"`,
207
+ },
208
+ };
209
+ };
210
+ export const updateMemberRole = async (args, ctx) => {
211
+ const { organization_id, user_id, role } = args;
212
+ validateRequired(organization_id, 'organization_id');
213
+ validateRequired(user_id, 'user_id');
214
+ validateRequired(role, 'role');
215
+ validateUUID(organization_id, 'organization_id');
216
+ validateUUID(user_id, 'user_id');
217
+ if (!ROLE_ORDER.includes(role)) {
218
+ throw new Error(`Invalid role. Must be one of: ${ROLE_ORDER.join(', ')}`);
219
+ }
220
+ if (role === 'owner') {
221
+ throw new Error('Cannot assign owner role. Use transfer ownership instead.');
222
+ }
223
+ const { supabase, auth } = ctx;
224
+ // Prevent demoting yourself
225
+ if (user_id === auth.userId) {
226
+ throw new Error('Cannot change your own role');
227
+ }
228
+ const { data, error } = await supabase
229
+ .from('organization_members')
230
+ .update({ role })
231
+ .eq('organization_id', organization_id)
232
+ .eq('user_id', user_id)
233
+ .neq('role', 'owner') // Cannot change owner's role
234
+ .select()
235
+ .single();
236
+ if (error)
237
+ throw new Error(`Failed to update member role: ${error.message}`);
238
+ return { result: { success: true, member: data } };
239
+ };
240
+ export const removeMember = async (args, ctx) => {
241
+ const { organization_id, user_id } = args;
242
+ validateRequired(organization_id, 'organization_id');
243
+ validateRequired(user_id, 'user_id');
244
+ validateUUID(organization_id, 'organization_id');
245
+ validateUUID(user_id, 'user_id');
246
+ const { supabase } = ctx;
247
+ const { error } = await supabase
248
+ .from('organization_members')
249
+ .delete()
250
+ .eq('organization_id', organization_id)
251
+ .eq('user_id', user_id)
252
+ .neq('role', 'owner'); // Cannot remove owner
253
+ if (error)
254
+ throw new Error(`Failed to remove member: ${error.message}`);
255
+ return {
256
+ result: {
257
+ success: true,
258
+ message: 'Member removed. Their org-scoped API keys have been invalidated.',
259
+ },
260
+ };
261
+ };
262
+ export const leaveOrganization = async (args, ctx) => {
263
+ const { organization_id } = args;
264
+ validateRequired(organization_id, 'organization_id');
265
+ validateUUID(organization_id, 'organization_id');
266
+ const { supabase, auth } = ctx;
267
+ // Check if user is owner
268
+ const { data: membership } = await supabase
269
+ .from('organization_members')
270
+ .select('role')
271
+ .eq('organization_id', organization_id)
272
+ .eq('user_id', auth.userId)
273
+ .single();
274
+ if (membership?.role === 'owner') {
275
+ throw new Error('Owner cannot leave. Transfer ownership first or delete the organization.');
276
+ }
277
+ const { error } = await supabase
278
+ .from('organization_members')
279
+ .delete()
280
+ .eq('organization_id', organization_id)
281
+ .eq('user_id', auth.userId);
282
+ if (error)
283
+ throw new Error(`Failed to leave organization: ${error.message}`);
284
+ return {
285
+ result: {
286
+ success: true,
287
+ message: 'You have left the organization.',
288
+ },
289
+ };
290
+ };
291
+ // ============================================================================
292
+ // Project Sharing
293
+ // ============================================================================
294
+ export const shareProjectWithOrg = async (args, ctx) => {
295
+ const { project_id, organization_id, permission = 'read' } = args;
296
+ validateRequired(project_id, 'project_id');
297
+ validateRequired(organization_id, 'organization_id');
298
+ validateUUID(project_id, 'project_id');
299
+ validateUUID(organization_id, 'organization_id');
300
+ if (!PERMISSION_ORDER.includes(permission)) {
301
+ throw new Error(`Invalid permission. Must be one of: ${PERMISSION_ORDER.join(', ')}`);
302
+ }
303
+ const { supabase, auth } = ctx;
304
+ // Verify user owns the project
305
+ const { data: project } = await supabase
306
+ .from('projects')
307
+ .select('id, name')
308
+ .eq('id', project_id)
309
+ .eq('user_id', auth.userId)
310
+ .single();
311
+ if (!project) {
312
+ throw new Error('Project not found or you are not the owner');
313
+ }
314
+ // Check if share already exists
315
+ const { data: existing } = await supabase
316
+ .from('project_shares')
317
+ .select('id')
318
+ .eq('project_id', project_id)
319
+ .eq('organization_id', organization_id)
320
+ .maybeSingle();
321
+ if (existing) {
322
+ throw new Error('Project is already shared with this organization');
323
+ }
324
+ const { data, error } = await supabase
325
+ .from('project_shares')
326
+ .insert({
327
+ project_id,
328
+ organization_id,
329
+ permission,
330
+ shared_by: auth.userId,
331
+ })
332
+ .select()
333
+ .single();
334
+ if (error)
335
+ throw new Error(`Failed to share project: ${error.message}`);
336
+ return {
337
+ result: {
338
+ success: true,
339
+ share: data,
340
+ message: `Project "${project.name}" shared with organization (${permission} access)`,
341
+ },
342
+ };
343
+ };
344
+ export const updateProjectShare = async (args, ctx) => {
345
+ const { project_id, organization_id, permission } = args;
346
+ validateRequired(project_id, 'project_id');
347
+ validateRequired(organization_id, 'organization_id');
348
+ validateRequired(permission, 'permission');
349
+ validateUUID(project_id, 'project_id');
350
+ validateUUID(organization_id, 'organization_id');
351
+ if (!PERMISSION_ORDER.includes(permission)) {
352
+ throw new Error(`Invalid permission. Must be one of: ${PERMISSION_ORDER.join(', ')}`);
353
+ }
354
+ const { supabase } = ctx;
355
+ const { data, error } = await supabase
356
+ .from('project_shares')
357
+ .update({ permission })
358
+ .eq('project_id', project_id)
359
+ .eq('organization_id', organization_id)
360
+ .select()
361
+ .single();
362
+ if (error)
363
+ throw new Error(`Failed to update share: ${error.message}`);
364
+ return { result: { success: true, share: data } };
365
+ };
366
+ export const unshareProject = async (args, ctx) => {
367
+ const { project_id, organization_id } = args;
368
+ validateRequired(project_id, 'project_id');
369
+ validateRequired(organization_id, 'organization_id');
370
+ validateUUID(project_id, 'project_id');
371
+ validateUUID(organization_id, 'organization_id');
372
+ const { supabase } = ctx;
373
+ const { error } = await supabase
374
+ .from('project_shares')
375
+ .delete()
376
+ .eq('project_id', project_id)
377
+ .eq('organization_id', organization_id);
378
+ if (error)
379
+ throw new Error(`Failed to unshare project: ${error.message}`);
380
+ return {
381
+ result: {
382
+ success: true,
383
+ message: 'Project share removed. Org members can no longer access this project.',
384
+ },
385
+ };
386
+ };
387
+ export const listProjectShares = async (args, ctx) => {
388
+ const { project_id } = args;
389
+ validateRequired(project_id, 'project_id');
390
+ validateUUID(project_id, 'project_id');
391
+ const { supabase } = ctx;
392
+ const { data, error } = await supabase
393
+ .from('project_shares')
394
+ .select(`
395
+ id,
396
+ permission,
397
+ shared_at,
398
+ shared_by,
399
+ organizations (
400
+ id,
401
+ name,
402
+ slug
403
+ )
404
+ `)
405
+ .eq('project_id', project_id)
406
+ .order('shared_at', { ascending: false });
407
+ if (error)
408
+ throw new Error(`Failed to list shares: ${error.message}`);
409
+ return { result: { shares: data || [], count: data?.length || 0 } };
410
+ };
411
+ /**
412
+ * Organizations handlers registry
413
+ */
414
+ export const organizationHandlers = {
415
+ list_organizations: listOrganizations,
416
+ create_organization: createOrganization,
417
+ update_organization: updateOrganization,
418
+ delete_organization: deleteOrganization,
419
+ list_org_members: listOrgMembers,
420
+ invite_member: inviteMember,
421
+ update_member_role: updateMemberRole,
422
+ remove_member: removeMember,
423
+ leave_organization: leaveOrganization,
424
+ share_project_with_org: shareProjectWithOrg,
425
+ update_project_share: updateProjectShare,
426
+ unshare_project: unshareProject,
427
+ list_project_shares: listProjectShares,
428
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Progress Handlers
3
+ *
4
+ * Handles progress logging and activity feed:
5
+ * - log_progress
6
+ * - get_activity_feed
7
+ */
8
+ import type { Handler, HandlerRegistry } from './types.js';
9
+ export declare const logProgress: Handler;
10
+ export declare const getActivityFeed: Handler;
11
+ /**
12
+ * Progress handlers registry
13
+ */
14
+ export declare const progressHandlers: HandlerRegistry;