@vibescope/mcp-server 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +113 -98
  2. package/dist/api-client.d.ts +1169 -0
  3. package/dist/api-client.js +713 -0
  4. package/dist/cli.d.ts +1 -6
  5. package/dist/cli.js +39 -240
  6. package/dist/config/tool-categories.d.ts +31 -0
  7. package/dist/config/tool-categories.js +253 -0
  8. package/dist/handlers/blockers.js +57 -58
  9. package/dist/handlers/bodies-of-work.d.ts +2 -0
  10. package/dist/handlers/bodies-of-work.js +108 -477
  11. package/dist/handlers/cost.d.ts +1 -0
  12. package/dist/handlers/cost.js +35 -113
  13. package/dist/handlers/decisions.d.ts +2 -0
  14. package/dist/handlers/decisions.js +28 -27
  15. package/dist/handlers/deployment.js +113 -828
  16. package/dist/handlers/discovery.d.ts +3 -0
  17. package/dist/handlers/discovery.js +26 -627
  18. package/dist/handlers/fallback.d.ts +2 -0
  19. package/dist/handlers/fallback.js +56 -142
  20. package/dist/handlers/findings.d.ts +8 -1
  21. package/dist/handlers/findings.js +65 -68
  22. package/dist/handlers/git-issues.d.ts +9 -13
  23. package/dist/handlers/git-issues.js +80 -225
  24. package/dist/handlers/ideas.d.ts +3 -0
  25. package/dist/handlers/ideas.js +53 -134
  26. package/dist/handlers/index.d.ts +2 -0
  27. package/dist/handlers/index.js +6 -0
  28. package/dist/handlers/milestones.d.ts +2 -0
  29. package/dist/handlers/milestones.js +51 -98
  30. package/dist/handlers/organizations.js +79 -275
  31. package/dist/handlers/progress.d.ts +2 -0
  32. package/dist/handlers/progress.js +25 -123
  33. package/dist/handlers/project.js +42 -221
  34. package/dist/handlers/requests.d.ts +2 -0
  35. package/dist/handlers/requests.js +23 -83
  36. package/dist/handlers/session.js +119 -590
  37. package/dist/handlers/sprints.d.ts +32 -0
  38. package/dist/handlers/sprints.js +275 -0
  39. package/dist/handlers/tasks.d.ts +7 -10
  40. package/dist/handlers/tasks.js +245 -894
  41. package/dist/handlers/tool-docs.d.ts +9 -0
  42. package/dist/handlers/tool-docs.js +904 -0
  43. package/dist/handlers/types.d.ts +11 -3
  44. package/dist/handlers/validation.d.ts +1 -1
  45. package/dist/handlers/validation.js +38 -153
  46. package/dist/index.js +493 -162
  47. package/dist/knowledge.js +106 -9
  48. package/dist/tools.js +34 -4
  49. package/dist/validators.d.ts +21 -0
  50. package/dist/validators.js +91 -0
  51. package/package.json +2 -3
  52. package/src/api-client.ts +1822 -0
  53. package/src/cli.test.ts +128 -302
  54. package/src/cli.ts +41 -285
  55. package/src/handlers/__test-setup__.ts +215 -0
  56. package/src/handlers/__test-utils__.ts +4 -134
  57. package/src/handlers/blockers.test.ts +114 -124
  58. package/src/handlers/blockers.ts +68 -70
  59. package/src/handlers/bodies-of-work.test.ts +236 -831
  60. package/src/handlers/bodies-of-work.ts +210 -525
  61. package/src/handlers/cost.test.ts +149 -113
  62. package/src/handlers/cost.ts +44 -132
  63. package/src/handlers/decisions.test.ts +111 -209
  64. package/src/handlers/decisions.ts +35 -27
  65. package/src/handlers/deployment.test.ts +193 -239
  66. package/src/handlers/deployment.ts +143 -896
  67. package/src/handlers/discovery.test.ts +20 -67
  68. package/src/handlers/discovery.ts +29 -714
  69. package/src/handlers/fallback.test.ts +206 -361
  70. package/src/handlers/fallback.ts +81 -156
  71. package/src/handlers/findings.test.ts +229 -320
  72. package/src/handlers/findings.ts +76 -64
  73. package/src/handlers/git-issues.test.ts +623 -0
  74. package/src/handlers/git-issues.ts +174 -0
  75. package/src/handlers/ideas.test.ts +229 -343
  76. package/src/handlers/ideas.ts +69 -143
  77. package/src/handlers/index.ts +6 -0
  78. package/src/handlers/milestones.test.ts +167 -281
  79. package/src/handlers/milestones.ts +54 -93
  80. package/src/handlers/organizations.test.ts +275 -467
  81. package/src/handlers/organizations.ts +84 -294
  82. package/src/handlers/progress.test.ts +112 -218
  83. package/src/handlers/progress.ts +29 -142
  84. package/src/handlers/project.test.ts +203 -226
  85. package/src/handlers/project.ts +48 -238
  86. package/src/handlers/requests.test.ts +74 -342
  87. package/src/handlers/requests.ts +25 -83
  88. package/src/handlers/session.test.ts +276 -206
  89. package/src/handlers/session.ts +136 -662
  90. package/src/handlers/sprints.test.ts +711 -0
  91. package/src/handlers/sprints.ts +510 -0
  92. package/src/handlers/tasks.test.ts +669 -353
  93. package/src/handlers/tasks.ts +263 -1015
  94. package/src/handlers/tool-docs.ts +1024 -0
  95. package/src/handlers/types.ts +12 -4
  96. package/src/handlers/validation.test.ts +237 -568
  97. package/src/handlers/validation.ts +43 -167
  98. package/src/index.ts +493 -186
  99. package/src/tools.ts +2532 -0
  100. package/src/validators.test.ts +223 -223
  101. package/src/validators.ts +127 -0
  102. package/tsconfig.json +1 -1
  103. package/vitest.config.ts +14 -13
  104. package/dist/cli.test.d.ts +0 -1
  105. package/dist/cli.test.js +0 -367
  106. package/dist/handlers/__test-utils__.d.ts +0 -72
  107. package/dist/handlers/__test-utils__.js +0 -176
  108. package/dist/handlers/checkouts.d.ts +0 -37
  109. package/dist/handlers/checkouts.js +0 -377
  110. package/dist/handlers/knowledge-query.d.ts +0 -22
  111. package/dist/handlers/knowledge-query.js +0 -253
  112. package/dist/handlers/knowledge.d.ts +0 -12
  113. package/dist/handlers/knowledge.js +0 -108
  114. package/dist/handlers/roles.d.ts +0 -30
  115. package/dist/handlers/roles.js +0 -281
  116. package/dist/handlers/tasks.test.d.ts +0 -1
  117. package/dist/handlers/tasks.test.js +0 -431
  118. package/dist/utils.test.d.ts +0 -1
  119. package/dist/utils.test.js +0 -532
  120. package/dist/validators.test.d.ts +0 -1
  121. package/dist/validators.test.js +0 -176
  122. package/src/knowledge.ts +0 -132
  123. package/src/tmpclaude-0078-cwd +0 -1
  124. package/src/tmpclaude-0ee1-cwd +0 -1
  125. package/src/tmpclaude-2dd5-cwd +0 -1
  126. package/src/tmpclaude-344c-cwd +0 -1
  127. package/src/tmpclaude-3860-cwd +0 -1
  128. package/src/tmpclaude-4b63-cwd +0 -1
  129. package/src/tmpclaude-5c73-cwd +0 -1
  130. package/src/tmpclaude-5ee3-cwd +0 -1
  131. package/src/tmpclaude-6795-cwd +0 -1
  132. package/src/tmpclaude-709e-cwd +0 -1
  133. package/src/tmpclaude-9839-cwd +0 -1
  134. package/src/tmpclaude-d829-cwd +0 -1
  135. package/src/tmpclaude-e072-cwd +0 -1
  136. package/src/tmpclaude-f6ee-cwd +0 -1
  137. package/tmpclaude-0439-cwd +0 -1
  138. package/tmpclaude-132f-cwd +0 -1
  139. package/tmpclaude-15bb-cwd +0 -1
  140. package/tmpclaude-165a-cwd +0 -1
  141. package/tmpclaude-1ba9-cwd +0 -1
  142. package/tmpclaude-21a3-cwd +0 -1
  143. package/tmpclaude-2a38-cwd +0 -1
  144. package/tmpclaude-2adf-cwd +0 -1
  145. package/tmpclaude-2f56-cwd +0 -1
  146. package/tmpclaude-3626-cwd +0 -1
  147. package/tmpclaude-3727-cwd +0 -1
  148. package/tmpclaude-40bc-cwd +0 -1
  149. package/tmpclaude-436f-cwd +0 -1
  150. package/tmpclaude-4783-cwd +0 -1
  151. package/tmpclaude-4b6d-cwd +0 -1
  152. package/tmpclaude-4ba4-cwd +0 -1
  153. package/tmpclaude-51e6-cwd +0 -1
  154. package/tmpclaude-5ecf-cwd +0 -1
  155. package/tmpclaude-6f97-cwd +0 -1
  156. package/tmpclaude-7fb2-cwd +0 -1
  157. package/tmpclaude-825c-cwd +0 -1
  158. package/tmpclaude-8baf-cwd +0 -1
  159. package/tmpclaude-8d9f-cwd +0 -1
  160. package/tmpclaude-975c-cwd +0 -1
  161. package/tmpclaude-9983-cwd +0 -1
  162. package/tmpclaude-a045-cwd +0 -1
  163. package/tmpclaude-ac4a-cwd +0 -1
  164. package/tmpclaude-b593-cwd +0 -1
  165. package/tmpclaude-b891-cwd +0 -1
  166. package/tmpclaude-c032-cwd +0 -1
  167. package/tmpclaude-cf43-cwd +0 -1
  168. package/tmpclaude-d040-cwd +0 -1
  169. package/tmpclaude-dcdd-cwd +0 -1
  170. package/tmpclaude-dcee-cwd +0 -1
  171. package/tmpclaude-e16b-cwd +0 -1
  172. package/tmpclaude-ecd2-cwd +0 -1
  173. package/tmpclaude-f48d-cwd +0 -1
@@ -7,53 +7,30 @@
7
7
  * - complete_milestone
8
8
  * - delete_milestone
9
9
  * - get_milestones
10
+ *
11
+ * MIGRATED: Uses Vibescope API client instead of direct Supabase
10
12
  */
11
13
  import { ValidationError, validateRequired, validateUUID } from '../validators.js';
14
+ import { getApiClient } from '../api-client.js';
12
15
  export const addMilestone = async (args, ctx) => {
13
16
  const { task_id, title, description, order_index } = args;
14
17
  validateRequired(task_id, 'task_id');
15
18
  validateUUID(task_id, 'task_id');
16
19
  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,
20
+ const { session } = ctx;
21
+ const apiClient = getApiClient();
22
+ const response = await apiClient.addMilestone(task_id, {
43
23
  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;
24
+ description,
25
+ order_index
26
+ }, session.currentSessionId || undefined);
27
+ if (!response.ok) {
28
+ throw new Error(`Failed to add milestone: ${response.error}`);
29
+ }
53
30
  return {
54
31
  result: {
55
32
  success: true,
56
- milestone,
33
+ milestone_id: response.data?.milestone_id,
57
34
  },
58
35
  };
59
36
  };
@@ -61,40 +38,30 @@ export const updateMilestone = async (args, ctx) => {
61
38
  const { milestone_id, title, description, status, order_index } = args;
62
39
  validateRequired(milestone_id, 'milestone_id');
63
40
  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;
41
+ // Validate status if provided
71
42
  if (status !== undefined) {
72
43
  if (!['pending', 'in_progress', 'completed'].includes(status)) {
73
44
  throw new ValidationError('status must be pending, in_progress, or completed');
74
45
  }
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
46
  }
83
- if (Object.keys(updates).length === 0) {
47
+ // Check that at least one field is provided
48
+ if (title === undefined && description === undefined && status === undefined && order_index === undefined) {
84
49
  throw new ValidationError('At least one field to update is required');
85
50
  }
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;
51
+ const apiClient = getApiClient();
52
+ const response = await apiClient.updateMilestone(milestone_id, {
53
+ title,
54
+ description,
55
+ status: status,
56
+ order_index
57
+ });
58
+ if (!response.ok) {
59
+ throw new Error(`Failed to update milestone: ${response.error}`);
60
+ }
94
61
  return {
95
62
  result: {
96
63
  success: true,
97
- milestone,
64
+ milestone: response.data?.milestone,
98
65
  },
99
66
  };
100
67
  };
@@ -102,21 +69,15 @@ export const completeMilestone = async (args, ctx) => {
102
69
  const { milestone_id } = args;
103
70
  validateRequired(milestone_id, 'milestone_id');
104
71
  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;
72
+ const apiClient = getApiClient();
73
+ const response = await apiClient.completeMilestone(milestone_id);
74
+ if (!response.ok) {
75
+ throw new Error(`Failed to complete milestone: ${response.error}`);
76
+ }
116
77
  return {
117
78
  result: {
118
79
  success: true,
119
- milestone,
80
+ milestone: response.data?.milestone,
120
81
  },
121
82
  };
122
83
  };
@@ -124,12 +85,11 @@ export const deleteMilestone = async (args, ctx) => {
124
85
  const { milestone_id } = args;
125
86
  validateRequired(milestone_id, 'milestone_id');
126
87
  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;
88
+ const apiClient = getApiClient();
89
+ const response = await apiClient.deleteMilestone(milestone_id);
90
+ if (!response.ok) {
91
+ throw new Error(`Failed to delete milestone: ${response.error}`);
92
+ }
133
93
  return {
134
94
  result: {
135
95
  success: true,
@@ -141,28 +101,21 @@ export const getMilestones = async (args, ctx) => {
141
101
  const { task_id } = args;
142
102
  validateRequired(task_id, 'task_id');
143
103
  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;
104
+ const apiClient = getApiClient();
105
+ const response = await apiClient.getMilestones(task_id);
106
+ if (!response.ok) {
107
+ throw new Error(`Failed to get milestones: ${response.error}`);
108
+ }
109
+ // Stats are calculated server-side now
157
110
  return {
158
111
  result: {
159
- milestones: milestones || [],
160
- stats: {
161
- total,
162
- completed,
163
- in_progress,
164
- pending,
165
- progress_percentage,
112
+ milestones: response.data?.milestones || [],
113
+ stats: response.data?.stats || {
114
+ total: 0,
115
+ completed: 0,
116
+ in_progress: 0,
117
+ pending: 0,
118
+ progress_percentage: 0,
166
119
  },
167
120
  },
168
121
  };
@@ -17,97 +17,40 @@
17
17
  * - list_project_shares
18
18
  */
19
19
  import { validateRequired, validateUUID } from '../validators.js';
20
- import { randomBytes } from 'crypto';
20
+ import { getApiClient } from '../api-client.js';
21
21
  // Valid roles in order of permission level
22
22
  const ROLE_ORDER = ['viewer', 'member', 'admin', 'owner'];
23
23
  // Valid share permissions
24
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
25
  // ============================================================================
42
26
  // Organization Management
43
27
  // ============================================================================
44
28
  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 } };
29
+ const apiClient = getApiClient();
30
+ const response = await apiClient.listOrganizations();
31
+ if (!response.ok) {
32
+ throw new Error(response.error || 'Failed to list organizations');
33
+ }
34
+ return { result: response.data };
71
35
  };
72
36
  export const createOrganization = async (args, ctx) => {
73
- const { name, description, slug: customSlug } = args;
37
+ const { name, description, slug } = args;
74
38
  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({
39
+ const apiClient = getApiClient();
40
+ const response = await apiClient.createOrganization({
89
41
  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
- };
42
+ description,
43
+ slug
44
+ });
45
+ if (!response.ok) {
46
+ throw new Error(response.error || 'Failed to create organization');
47
+ }
48
+ return { result: response.data };
105
49
  };
106
50
  export const updateOrganization = async (args, ctx) => {
107
51
  const { organization_id, name, description, logo_url } = args;
108
52
  validateRequired(organization_id, 'organization_id');
109
53
  validateUUID(organization_id, 'organization_id');
110
- const { supabase } = ctx;
111
54
  const updates = {};
112
55
  if (name !== undefined)
113
56
  updates.name = name;
@@ -118,33 +61,23 @@ export const updateOrganization = async (args, ctx) => {
118
61
  if (Object.keys(updates).length === 0) {
119
62
  throw new Error('No updates provided');
120
63
  }
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 } };
64
+ const apiClient = getApiClient();
65
+ const response = await apiClient.updateOrganization(organization_id, updates);
66
+ if (!response.ok) {
67
+ throw new Error(response.error || 'Failed to update organization');
68
+ }
69
+ return { result: response.data };
130
70
  };
131
71
  export const deleteOrganization = async (args, ctx) => {
132
72
  const { organization_id } = args;
133
73
  validateRequired(organization_id, 'organization_id');
134
74
  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
- };
75
+ const apiClient = getApiClient();
76
+ const response = await apiClient.deleteOrganization(organization_id);
77
+ if (!response.ok) {
78
+ throw new Error(response.error || 'Failed to delete organization');
79
+ }
80
+ return { result: response.data };
148
81
  };
149
82
  // ============================================================================
150
83
  // Member Management
@@ -153,16 +86,12 @@ export const listOrgMembers = async (args, ctx) => {
153
86
  const { organization_id } = args;
154
87
  validateRequired(organization_id, 'organization_id');
155
88
  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 } };
89
+ const apiClient = getApiClient();
90
+ const response = await apiClient.listOrgMembers(organization_id);
91
+ if (!response.ok) {
92
+ throw new Error(response.error || 'Failed to list members');
93
+ }
94
+ return { result: response.data };
166
95
  };
167
96
  export const inviteMember = async (args, ctx) => {
168
97
  const { organization_id, email, role = 'member' } = args;
@@ -172,40 +101,12 @@ export const inviteMember = async (args, ctx) => {
172
101
  if (!['admin', 'member', 'viewer'].includes(role)) {
173
102
  throw new Error('Invalid role. Must be admin, member, or viewer.');
174
103
  }
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}`);
104
+ const apiClient = getApiClient();
105
+ const response = await apiClient.inviteMember(organization_id, email, role);
106
+ if (!response.ok) {
107
+ throw new Error(response.error || 'Failed to create invite');
188
108
  }
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
- };
109
+ return { result: response.data };
209
110
  };
210
111
  export const updateMemberRole = async (args, ctx) => {
211
112
  const { organization_id, user_id, role } = args;
@@ -220,22 +121,12 @@ export const updateMemberRole = async (args, ctx) => {
220
121
  if (role === 'owner') {
221
122
  throw new Error('Cannot assign owner role. Use transfer ownership instead.');
222
123
  }
223
- const { supabase, auth } = ctx;
224
- // Prevent demoting yourself
225
- if (user_id === auth.userId) {
226
- throw new Error('Cannot change your own role');
124
+ const apiClient = getApiClient();
125
+ const response = await apiClient.updateMemberRole(organization_id, user_id, role);
126
+ if (!response.ok) {
127
+ throw new Error(response.error || 'Failed to update member role');
227
128
  }
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 } };
129
+ return { result: response.data };
239
130
  };
240
131
  export const removeMember = async (args, ctx) => {
241
132
  const { organization_id, user_id } = args;
@@ -243,50 +134,23 @@ export const removeMember = async (args, ctx) => {
243
134
  validateRequired(user_id, 'user_id');
244
135
  validateUUID(organization_id, 'organization_id');
245
136
  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
- };
137
+ const apiClient = getApiClient();
138
+ const response = await apiClient.removeMember(organization_id, user_id);
139
+ if (!response.ok) {
140
+ throw new Error(response.error || 'Failed to remove member');
141
+ }
142
+ return { result: response.data };
261
143
  };
262
144
  export const leaveOrganization = async (args, ctx) => {
263
145
  const { organization_id } = args;
264
146
  validateRequired(organization_id, 'organization_id');
265
147
  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.');
148
+ const apiClient = getApiClient();
149
+ const response = await apiClient.leaveOrganization(organization_id);
150
+ if (!response.ok) {
151
+ throw new Error(response.error || 'Failed to leave organization');
276
152
  }
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
- };
153
+ return { result: response.data };
290
154
  };
291
155
  // ============================================================================
292
156
  // Project Sharing
@@ -300,46 +164,12 @@ export const shareProjectWithOrg = async (args, ctx) => {
300
164
  if (!PERMISSION_ORDER.includes(permission)) {
301
165
  throw new Error(`Invalid permission. Must be one of: ${PERMISSION_ORDER.join(', ')}`);
302
166
  }
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');
167
+ const apiClient = getApiClient();
168
+ const response = await apiClient.shareProjectWithOrg(project_id, organization_id, permission);
169
+ if (!response.ok) {
170
+ throw new Error(response.error || 'Failed to share project');
323
171
  }
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
- };
172
+ return { result: response.data };
343
173
  };
344
174
  export const updateProjectShare = async (args, ctx) => {
345
175
  const { project_id, organization_id, permission } = args;
@@ -351,17 +181,12 @@ export const updateProjectShare = async (args, ctx) => {
351
181
  if (!PERMISSION_ORDER.includes(permission)) {
352
182
  throw new Error(`Invalid permission. Must be one of: ${PERMISSION_ORDER.join(', ')}`);
353
183
  }
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 } };
184
+ const apiClient = getApiClient();
185
+ const response = await apiClient.updateProjectShare(project_id, organization_id, permission);
186
+ if (!response.ok) {
187
+ throw new Error(response.error || 'Failed to update share');
188
+ }
189
+ return { result: response.data };
365
190
  };
366
191
  export const unshareProject = async (args, ctx) => {
367
192
  const { project_id, organization_id } = args;
@@ -369,44 +194,23 @@ export const unshareProject = async (args, ctx) => {
369
194
  validateRequired(organization_id, 'organization_id');
370
195
  validateUUID(project_id, 'project_id');
371
196
  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
- };
197
+ const apiClient = getApiClient();
198
+ const response = await apiClient.unshareProject(project_id, organization_id);
199
+ if (!response.ok) {
200
+ throw new Error(response.error || 'Failed to unshare project');
201
+ }
202
+ return { result: response.data };
386
203
  };
387
204
  export const listProjectShares = async (args, ctx) => {
388
205
  const { project_id } = args;
389
206
  validateRequired(project_id, 'project_id');
390
207
  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 } };
208
+ const apiClient = getApiClient();
209
+ const response = await apiClient.listProjectShares(project_id);
210
+ if (!response.ok) {
211
+ throw new Error(response.error || 'Failed to list shares');
212
+ }
213
+ return { result: response.data };
410
214
  };
411
215
  /**
412
216
  * Organizations handlers registry
@@ -4,6 +4,8 @@
4
4
  * Handles progress logging and activity feed:
5
5
  * - log_progress
6
6
  * - get_activity_feed
7
+ *
8
+ * MIGRATED: Uses Vibescope API client instead of direct Supabase
7
9
  */
8
10
  import type { Handler, HandlerRegistry } from './types.js';
9
11
  export declare const logProgress: Handler;