@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.
Files changed (104) hide show
  1. package/README.md +60 -7
  2. package/dist/api-client.d.ts +251 -1
  3. package/dist/api-client.js +82 -3
  4. package/dist/handlers/blockers.js +9 -8
  5. package/dist/handlers/bodies-of-work.js +96 -63
  6. package/dist/handlers/connectors.d.ts +45 -0
  7. package/dist/handlers/connectors.js +183 -0
  8. package/dist/handlers/cost.d.ts +10 -0
  9. package/dist/handlers/cost.js +112 -50
  10. package/dist/handlers/decisions.js +32 -19
  11. package/dist/handlers/deployment.js +144 -122
  12. package/dist/handlers/discovery.d.ts +7 -0
  13. package/dist/handlers/discovery.js +96 -7
  14. package/dist/handlers/fallback.js +29 -23
  15. package/dist/handlers/file-checkouts.d.ts +20 -0
  16. package/dist/handlers/file-checkouts.js +133 -0
  17. package/dist/handlers/findings.d.ts +6 -0
  18. package/dist/handlers/findings.js +96 -40
  19. package/dist/handlers/git-issues.js +40 -36
  20. package/dist/handlers/ideas.js +49 -31
  21. package/dist/handlers/index.d.ts +3 -0
  22. package/dist/handlers/index.js +9 -0
  23. package/dist/handlers/milestones.js +39 -32
  24. package/dist/handlers/organizations.js +99 -91
  25. package/dist/handlers/progress.js +24 -13
  26. package/dist/handlers/project.js +68 -28
  27. package/dist/handlers/requests.js +18 -14
  28. package/dist/handlers/roles.d.ts +18 -0
  29. package/dist/handlers/roles.js +130 -0
  30. package/dist/handlers/session.js +58 -17
  31. package/dist/handlers/sprints.js +93 -81
  32. package/dist/handlers/tasks.d.ts +2 -0
  33. package/dist/handlers/tasks.js +189 -91
  34. package/dist/handlers/types.d.ts +64 -2
  35. package/dist/handlers/types.js +48 -1
  36. package/dist/handlers/validation.js +21 -17
  37. package/dist/index.js +7 -2716
  38. package/dist/token-tracking.d.ts +74 -0
  39. package/dist/token-tracking.js +122 -0
  40. package/dist/tools.js +685 -9
  41. package/dist/utils.d.ts +5 -0
  42. package/dist/utils.js +17 -0
  43. package/docs/TOOLS.md +2053 -0
  44. package/package.json +4 -1
  45. package/scripts/generate-docs.ts +212 -0
  46. package/src/api-client.test.ts +718 -0
  47. package/src/api-client.ts +320 -6
  48. package/src/handlers/__test-setup__.ts +16 -0
  49. package/src/handlers/blockers.test.ts +31 -19
  50. package/src/handlers/blockers.ts +9 -8
  51. package/src/handlers/bodies-of-work.test.ts +55 -32
  52. package/src/handlers/bodies-of-work.ts +115 -115
  53. package/src/handlers/connectors.test.ts +834 -0
  54. package/src/handlers/connectors.ts +229 -0
  55. package/src/handlers/cost.test.ts +34 -44
  56. package/src/handlers/cost.ts +136 -85
  57. package/src/handlers/decisions.test.ts +37 -27
  58. package/src/handlers/decisions.ts +35 -30
  59. package/src/handlers/deployment.ts +180 -208
  60. package/src/handlers/discovery.test.ts +4 -5
  61. package/src/handlers/discovery.ts +98 -8
  62. package/src/handlers/fallback.test.ts +26 -22
  63. package/src/handlers/fallback.ts +36 -33
  64. package/src/handlers/file-checkouts.test.ts +670 -0
  65. package/src/handlers/file-checkouts.ts +165 -0
  66. package/src/handlers/findings.test.ts +178 -19
  67. package/src/handlers/findings.ts +112 -74
  68. package/src/handlers/git-issues.test.ts +51 -43
  69. package/src/handlers/git-issues.ts +44 -84
  70. package/src/handlers/ideas.test.ts +28 -23
  71. package/src/handlers/ideas.ts +61 -59
  72. package/src/handlers/index.ts +9 -0
  73. package/src/handlers/milestones.test.ts +33 -28
  74. package/src/handlers/milestones.ts +52 -50
  75. package/src/handlers/organizations.test.ts +104 -83
  76. package/src/handlers/organizations.ts +117 -142
  77. package/src/handlers/progress.test.ts +20 -14
  78. package/src/handlers/progress.ts +26 -24
  79. package/src/handlers/project.test.ts +34 -27
  80. package/src/handlers/project.ts +95 -63
  81. package/src/handlers/requests.test.ts +27 -18
  82. package/src/handlers/requests.ts +21 -17
  83. package/src/handlers/roles.test.ts +303 -0
  84. package/src/handlers/roles.ts +208 -0
  85. package/src/handlers/session.test.ts +47 -0
  86. package/src/handlers/session.ts +71 -26
  87. package/src/handlers/sprints.test.ts +71 -50
  88. package/src/handlers/sprints.ts +113 -146
  89. package/src/handlers/tasks.test.ts +77 -15
  90. package/src/handlers/tasks.ts +231 -156
  91. package/src/handlers/tool-categories.test.ts +66 -0
  92. package/src/handlers/types.ts +81 -2
  93. package/src/handlers/validation.test.ts +78 -45
  94. package/src/handlers/validation.ts +23 -25
  95. package/src/index.ts +12 -2732
  96. package/src/token-tracking.test.ts +453 -0
  97. package/src/token-tracking.ts +164 -0
  98. package/src/tools.ts +685 -9
  99. package/src/utils.test.ts +2 -2
  100. package/src/utils.ts +17 -0
  101. package/dist/config/tool-categories.d.ts +0 -31
  102. package/dist/config/tool-categories.js +0 -253
  103. package/dist/knowledge.d.ts +0 -6
  104. package/dist/knowledge.js +0 -218
@@ -10,54 +10,78 @@
10
10
  *
11
11
  * MIGRATED: Uses Vibescope API client instead of direct Supabase
12
12
  */
13
- import { validateRequired, validateUUID, validatePriority, validateEstimatedMinutes } from '../validators.js';
13
+ import { parseArgs, uuidValidator, priorityValidator, minutesValidator, createEnumValidator, } from '../validators.js';
14
14
  import { getApiClient } from '../api-client.js';
15
+ const VALID_IDEA_STATUSES = ['raw', 'exploring', 'planned', 'in_development', 'shipped'];
16
+ // Argument schemas for type-safe parsing
17
+ const addIdeaSchema = {
18
+ project_id: { type: 'string', required: true, validate: uuidValidator },
19
+ title: { type: 'string', required: true },
20
+ description: { type: 'string' },
21
+ status: { type: 'string', validate: createEnumValidator(VALID_IDEA_STATUSES) },
22
+ };
23
+ const updateIdeaSchema = {
24
+ idea_id: { type: 'string', required: true, validate: uuidValidator },
25
+ title: { type: 'string' },
26
+ description: { type: 'string' },
27
+ status: { type: 'string', validate: createEnumValidator(VALID_IDEA_STATUSES) },
28
+ doc_url: { type: 'string' },
29
+ };
30
+ const getIdeasSchema = {
31
+ project_id: { type: 'string', required: true, validate: uuidValidator },
32
+ status: { type: 'string', validate: createEnumValidator(VALID_IDEA_STATUSES) },
33
+ limit: { type: 'number', default: 50 },
34
+ offset: { type: 'number', default: 0 },
35
+ search_query: { type: 'string' },
36
+ };
37
+ const deleteIdeaSchema = {
38
+ idea_id: { type: 'string', required: true, validate: uuidValidator },
39
+ };
40
+ const convertIdeaToTaskSchema = {
41
+ idea_id: { type: 'string', required: true, validate: uuidValidator },
42
+ priority: { type: 'number', default: 3, validate: priorityValidator },
43
+ estimated_minutes: { type: 'number', validate: minutesValidator },
44
+ update_status: { type: 'boolean', default: true },
45
+ };
15
46
  export const addIdea = async (args, ctx) => {
16
- const { project_id, title, description, status } = args;
17
- validateRequired(project_id, 'project_id');
18
- validateUUID(project_id, 'project_id');
19
- validateRequired(title, 'title');
47
+ const { project_id, title, description, status } = parseArgs(args, addIdeaSchema);
20
48
  const { session } = ctx;
21
49
  const apiClient = getApiClient();
22
50
  const response = await apiClient.addIdea(project_id, {
23
51
  title,
24
52
  description,
25
- status
53
+ status: status
26
54
  }, session.currentSessionId || undefined);
27
55
  if (!response.ok) {
28
- throw new Error(`Failed to add idea: ${response.error}`);
56
+ return { result: { error: response.error || 'Failed to add idea' }, isError: true };
29
57
  }
30
58
  return { result: { success: true, idea_id: response.data?.idea_id, title } };
31
59
  };
32
- export const updateIdea = async (args, ctx) => {
33
- const { idea_id, title, description, status, doc_url } = args;
34
- validateRequired(idea_id, 'idea_id');
35
- validateUUID(idea_id, 'idea_id');
60
+ export const updateIdea = async (args, _ctx) => {
61
+ const { idea_id, title, description, status, doc_url } = parseArgs(args, updateIdeaSchema);
36
62
  const apiClient = getApiClient();
37
63
  const response = await apiClient.updateIdea(idea_id, {
38
64
  title,
39
65
  description,
40
- status,
66
+ status: status,
41
67
  doc_url
42
68
  });
43
69
  if (!response.ok) {
44
- throw new Error(`Failed to update idea: ${response.error}`);
70
+ return { result: { error: response.error || 'Failed to update idea' }, isError: true };
45
71
  }
46
72
  return { result: { success: true, idea_id } };
47
73
  };
48
- export const getIdeas = async (args, ctx) => {
49
- const { project_id, status, limit = 50, offset = 0, search_query } = args;
50
- validateRequired(project_id, 'project_id');
51
- validateUUID(project_id, 'project_id');
74
+ export const getIdeas = async (args, _ctx) => {
75
+ const { project_id, status, limit, offset, search_query } = parseArgs(args, getIdeasSchema);
52
76
  const apiClient = getApiClient();
53
77
  const response = await apiClient.getIdeas(project_id, {
54
- status,
78
+ status: status,
55
79
  limit,
56
80
  offset,
57
81
  search_query
58
82
  });
59
83
  if (!response.ok) {
60
- throw new Error(`Failed to fetch ideas: ${response.error}`);
84
+ return { result: { error: response.error || 'Failed to fetch ideas' }, isError: true };
61
85
  }
62
86
  return {
63
87
  result: {
@@ -65,23 +89,17 @@ export const getIdeas = async (args, ctx) => {
65
89
  },
66
90
  };
67
91
  };
68
- export const deleteIdea = async (args, ctx) => {
69
- const { idea_id } = args;
70
- validateRequired(idea_id, 'idea_id');
71
- validateUUID(idea_id, 'idea_id');
92
+ export const deleteIdea = async (args, _ctx) => {
93
+ const { idea_id } = parseArgs(args, deleteIdeaSchema);
72
94
  const apiClient = getApiClient();
73
95
  const response = await apiClient.deleteIdea(idea_id);
74
96
  if (!response.ok) {
75
- throw new Error(`Failed to delete idea: ${response.error}`);
97
+ return { result: { error: response.error || 'Failed to delete idea' }, isError: true };
76
98
  }
77
99
  return { result: { success: true } };
78
100
  };
79
- export const convertIdeaToTask = async (args, ctx) => {
80
- const { idea_id, priority = 3, estimated_minutes, update_status = true } = args;
81
- validateRequired(idea_id, 'idea_id');
82
- validateUUID(idea_id, 'idea_id');
83
- validatePriority(priority);
84
- validateEstimatedMinutes(estimated_minutes);
101
+ export const convertIdeaToTask = async (args, _ctx) => {
102
+ const { idea_id, priority, estimated_minutes, update_status } = parseArgs(args, convertIdeaToTaskSchema);
85
103
  const apiClient = getApiClient();
86
104
  // Use proxy for convert_idea_to_task operation
87
105
  const response = await apiClient.proxy('convert_idea_to_task', {
@@ -91,7 +109,7 @@ export const convertIdeaToTask = async (args, ctx) => {
91
109
  update_status
92
110
  });
93
111
  if (!response.ok) {
94
- throw new Error(`Failed to convert idea: ${response.error}`);
112
+ return { result: { error: response.error || 'Failed to convert idea' }, isError: true };
95
113
  }
96
114
  return { result: response.data };
97
115
  };
@@ -24,6 +24,9 @@ export * from './organizations.js';
24
24
  export * from './cost.js';
25
25
  export * from './git-issues.js';
26
26
  export * from './sprints.js';
27
+ export * from './file-checkouts.js';
28
+ export * from './roles.js';
29
+ export * from './connectors.js';
27
30
  import type { HandlerRegistry } from './types.js';
28
31
  /**
29
32
  * Build the complete handler registry from all modules
@@ -24,6 +24,9 @@ export * from './organizations.js';
24
24
  export * from './cost.js';
25
25
  export * from './git-issues.js';
26
26
  export * from './sprints.js';
27
+ export * from './file-checkouts.js';
28
+ export * from './roles.js';
29
+ export * from './connectors.js';
27
30
  import { milestoneHandlers } from './milestones.js';
28
31
  import { sessionHandlers } from './session.js';
29
32
  import { ideaHandlers } from './ideas.js';
@@ -43,6 +46,9 @@ import { organizationHandlers } from './organizations.js';
43
46
  import { costHandlers } from './cost.js';
44
47
  import { gitIssueHandlers } from './git-issues.js';
45
48
  import { sprintHandlers } from './sprints.js';
49
+ import { fileCheckoutHandlers } from './file-checkouts.js';
50
+ import { roleHandlers } from './roles.js';
51
+ import { connectorHandlers } from './connectors.js';
46
52
  /**
47
53
  * Build the complete handler registry from all modules
48
54
  */
@@ -67,5 +73,8 @@ export function buildHandlerRegistry() {
67
73
  ...costHandlers,
68
74
  ...gitIssueHandlers,
69
75
  ...sprintHandlers,
76
+ ...fileCheckoutHandlers,
77
+ ...roleHandlers,
78
+ ...connectorHandlers,
70
79
  };
71
80
  }
@@ -10,13 +10,34 @@
10
10
  *
11
11
  * MIGRATED: Uses Vibescope API client instead of direct Supabase
12
12
  */
13
- import { ValidationError, validateRequired, validateUUID } from '../validators.js';
13
+ import { parseArgs, uuidValidator, createEnumValidator, ValidationError, } from '../validators.js';
14
14
  import { getApiClient } from '../api-client.js';
15
+ const VALID_MILESTONE_STATUSES = ['pending', 'in_progress', 'completed'];
16
+ // Argument schemas for type-safe parsing
17
+ const addMilestoneSchema = {
18
+ task_id: { type: 'string', required: true, validate: uuidValidator },
19
+ title: { type: 'string', required: true },
20
+ description: { type: 'string' },
21
+ order_index: { type: 'number' },
22
+ };
23
+ const updateMilestoneSchema = {
24
+ milestone_id: { type: 'string', required: true, validate: uuidValidator },
25
+ title: { type: 'string' },
26
+ description: { type: 'string' },
27
+ status: { type: 'string', validate: createEnumValidator(VALID_MILESTONE_STATUSES) },
28
+ order_index: { type: 'number' },
29
+ };
30
+ const completeMilestoneSchema = {
31
+ milestone_id: { type: 'string', required: true, validate: uuidValidator },
32
+ };
33
+ const deleteMilestoneSchema = {
34
+ milestone_id: { type: 'string', required: true, validate: uuidValidator },
35
+ };
36
+ const getMilestonesSchema = {
37
+ task_id: { type: 'string', required: true, validate: uuidValidator },
38
+ };
15
39
  export const addMilestone = async (args, ctx) => {
16
- const { task_id, title, description, order_index } = args;
17
- validateRequired(task_id, 'task_id');
18
- validateUUID(task_id, 'task_id');
19
- validateRequired(title, 'title');
40
+ const { task_id, title, description, order_index } = parseArgs(args, addMilestoneSchema);
20
41
  const { session } = ctx;
21
42
  const apiClient = getApiClient();
22
43
  const response = await apiClient.addMilestone(task_id, {
@@ -25,7 +46,7 @@ export const addMilestone = async (args, ctx) => {
25
46
  order_index
26
47
  }, session.currentSessionId || undefined);
27
48
  if (!response.ok) {
28
- throw new Error(`Failed to add milestone: ${response.error}`);
49
+ return { result: { error: response.error || 'Failed to add milestone' }, isError: true };
29
50
  }
30
51
  return {
31
52
  result: {
@@ -34,16 +55,8 @@ export const addMilestone = async (args, ctx) => {
34
55
  },
35
56
  };
36
57
  };
37
- export const updateMilestone = async (args, ctx) => {
38
- const { milestone_id, title, description, status, order_index } = args;
39
- validateRequired(milestone_id, 'milestone_id');
40
- validateUUID(milestone_id, 'milestone_id');
41
- // Validate status if provided
42
- if (status !== undefined) {
43
- if (!['pending', 'in_progress', 'completed'].includes(status)) {
44
- throw new ValidationError('status must be pending, in_progress, or completed');
45
- }
46
- }
58
+ export const updateMilestone = async (args, _ctx) => {
59
+ const { milestone_id, title, description, status, order_index } = parseArgs(args, updateMilestoneSchema);
47
60
  // Check that at least one field is provided
48
61
  if (title === undefined && description === undefined && status === undefined && order_index === undefined) {
49
62
  throw new ValidationError('At least one field to update is required');
@@ -56,7 +69,7 @@ export const updateMilestone = async (args, ctx) => {
56
69
  order_index
57
70
  });
58
71
  if (!response.ok) {
59
- throw new Error(`Failed to update milestone: ${response.error}`);
72
+ return { result: { error: response.error || 'Failed to update milestone' }, isError: true };
60
73
  }
61
74
  return {
62
75
  result: {
@@ -65,14 +78,12 @@ export const updateMilestone = async (args, ctx) => {
65
78
  },
66
79
  };
67
80
  };
68
- export const completeMilestone = async (args, ctx) => {
69
- const { milestone_id } = args;
70
- validateRequired(milestone_id, 'milestone_id');
71
- validateUUID(milestone_id, 'milestone_id');
81
+ export const completeMilestone = async (args, _ctx) => {
82
+ const { milestone_id } = parseArgs(args, completeMilestoneSchema);
72
83
  const apiClient = getApiClient();
73
84
  const response = await apiClient.completeMilestone(milestone_id);
74
85
  if (!response.ok) {
75
- throw new Error(`Failed to complete milestone: ${response.error}`);
86
+ return { result: { error: response.error || 'Failed to complete milestone' }, isError: true };
76
87
  }
77
88
  return {
78
89
  result: {
@@ -81,14 +92,12 @@ export const completeMilestone = async (args, ctx) => {
81
92
  },
82
93
  };
83
94
  };
84
- export const deleteMilestone = async (args, ctx) => {
85
- const { milestone_id } = args;
86
- validateRequired(milestone_id, 'milestone_id');
87
- validateUUID(milestone_id, 'milestone_id');
95
+ export const deleteMilestone = async (args, _ctx) => {
96
+ const { milestone_id } = parseArgs(args, deleteMilestoneSchema);
88
97
  const apiClient = getApiClient();
89
98
  const response = await apiClient.deleteMilestone(milestone_id);
90
99
  if (!response.ok) {
91
- throw new Error(`Failed to delete milestone: ${response.error}`);
100
+ return { result: { error: response.error || 'Failed to delete milestone' }, isError: true };
92
101
  }
93
102
  return {
94
103
  result: {
@@ -97,14 +106,12 @@ export const deleteMilestone = async (args, ctx) => {
97
106
  },
98
107
  };
99
108
  };
100
- export const getMilestones = async (args, ctx) => {
101
- const { task_id } = args;
102
- validateRequired(task_id, 'task_id');
103
- validateUUID(task_id, 'task_id');
109
+ export const getMilestones = async (args, _ctx) => {
110
+ const { task_id } = parseArgs(args, getMilestonesSchema);
104
111
  const apiClient = getApiClient();
105
112
  const response = await apiClient.getMilestones(task_id);
106
113
  if (!response.ok) {
107
- throw new Error(`Failed to get milestones: ${response.error}`);
114
+ return { result: { error: response.error || 'Failed to get milestones' }, isError: true };
108
115
  }
109
116
  // Stats are calculated server-side now
110
117
  return {
@@ -16,26 +16,80 @@
16
16
  * - unshare_project
17
17
  * - list_project_shares
18
18
  */
19
- import { validateRequired, validateUUID } from '../validators.js';
19
+ import { parseArgs, uuidValidator, createEnumValidator, ValidationError } from '../validators.js';
20
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
+ const ASSIGNABLE_ROLES = ['viewer', 'member', 'admin'];
23
24
  // Valid share permissions
24
25
  const PERMISSION_ORDER = ['read', 'write', 'admin'];
25
26
  // ============================================================================
27
+ // Argument Schemas
28
+ // ============================================================================
29
+ const createOrganizationSchema = {
30
+ name: { type: 'string', required: true },
31
+ description: { type: 'string' },
32
+ slug: { type: 'string' },
33
+ };
34
+ const updateOrganizationSchema = {
35
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
36
+ name: { type: 'string' },
37
+ description: { type: 'string' },
38
+ logo_url: { type: 'string' },
39
+ };
40
+ const deleteOrganizationSchema = {
41
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
42
+ };
43
+ const listOrgMembersSchema = {
44
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
45
+ };
46
+ const inviteMemberSchema = {
47
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
48
+ email: { type: 'string', required: true },
49
+ role: { type: 'string', default: 'member', validate: createEnumValidator(ASSIGNABLE_ROLES) },
50
+ };
51
+ const updateMemberRoleSchema = {
52
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
53
+ user_id: { type: 'string', required: true, validate: uuidValidator },
54
+ role: { type: 'string', required: true, validate: createEnumValidator(ROLE_ORDER) },
55
+ };
56
+ const removeMemberSchema = {
57
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
58
+ user_id: { type: 'string', required: true, validate: uuidValidator },
59
+ };
60
+ const leaveOrganizationSchema = {
61
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
62
+ };
63
+ const shareProjectWithOrgSchema = {
64
+ project_id: { type: 'string', required: true, validate: uuidValidator },
65
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
66
+ permission: { type: 'string', default: 'read', validate: createEnumValidator(PERMISSION_ORDER) },
67
+ };
68
+ const updateProjectShareSchema = {
69
+ project_id: { type: 'string', required: true, validate: uuidValidator },
70
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
71
+ permission: { type: 'string', required: true, validate: createEnumValidator(PERMISSION_ORDER) },
72
+ };
73
+ const unshareProjectSchema = {
74
+ project_id: { type: 'string', required: true, validate: uuidValidator },
75
+ organization_id: { type: 'string', required: true, validate: uuidValidator },
76
+ };
77
+ const listProjectSharesSchema = {
78
+ project_id: { type: 'string', required: true, validate: uuidValidator },
79
+ };
80
+ // ============================================================================
26
81
  // Organization Management
27
82
  // ============================================================================
28
- export const listOrganizations = async (_args, ctx) => {
83
+ export const listOrganizations = async (_args, _ctx) => {
29
84
  const apiClient = getApiClient();
30
85
  const response = await apiClient.listOrganizations();
31
86
  if (!response.ok) {
32
- throw new Error(response.error || 'Failed to list organizations');
87
+ return { result: { error: response.error || 'Failed to list organizations' }, isError: true };
33
88
  }
34
89
  return { result: response.data };
35
90
  };
36
- export const createOrganization = async (args, ctx) => {
37
- const { name, description, slug } = args;
38
- validateRequired(name, 'name');
91
+ export const createOrganization = async (args, _ctx) => {
92
+ const { name, description, slug } = parseArgs(args, createOrganizationSchema);
39
93
  const apiClient = getApiClient();
40
94
  const response = await apiClient.createOrganization({
41
95
  name,
@@ -43,14 +97,16 @@ export const createOrganization = async (args, ctx) => {
43
97
  slug
44
98
  });
45
99
  if (!response.ok) {
46
- throw new Error(response.error || 'Failed to create organization');
100
+ return { result: { error: response.error || 'Failed to create organization' }, isError: true };
47
101
  }
48
102
  return { result: response.data };
49
103
  };
50
- export const updateOrganization = async (args, ctx) => {
51
- const { organization_id, name, description, logo_url } = args;
52
- validateRequired(organization_id, 'organization_id');
53
- validateUUID(organization_id, 'organization_id');
104
+ export const updateOrganization = async (args, _ctx) => {
105
+ const { organization_id, name, description, logo_url } = parseArgs(args, updateOrganizationSchema);
106
+ // Check that at least one update is provided
107
+ if (name === undefined && description === undefined && logo_url === undefined) {
108
+ throw new ValidationError('No updates provided');
109
+ }
54
110
  const updates = {};
55
111
  if (name !== undefined)
56
112
  updates.name = name;
@@ -58,157 +114,109 @@ export const updateOrganization = async (args, ctx) => {
58
114
  updates.description = description;
59
115
  if (logo_url !== undefined)
60
116
  updates.logo_url = logo_url;
61
- if (Object.keys(updates).length === 0) {
62
- throw new Error('No updates provided');
63
- }
64
117
  const apiClient = getApiClient();
65
118
  const response = await apiClient.updateOrganization(organization_id, updates);
66
119
  if (!response.ok) {
67
- throw new Error(response.error || 'Failed to update organization');
120
+ return { result: { error: response.error || 'Failed to update organization' }, isError: true };
68
121
  }
69
122
  return { result: response.data };
70
123
  };
71
- export const deleteOrganization = async (args, ctx) => {
72
- const { organization_id } = args;
73
- validateRequired(organization_id, 'organization_id');
74
- validateUUID(organization_id, 'organization_id');
124
+ export const deleteOrganization = async (args, _ctx) => {
125
+ const { organization_id } = parseArgs(args, deleteOrganizationSchema);
75
126
  const apiClient = getApiClient();
76
127
  const response = await apiClient.deleteOrganization(organization_id);
77
128
  if (!response.ok) {
78
- throw new Error(response.error || 'Failed to delete organization');
129
+ return { result: { error: response.error || 'Failed to delete organization' }, isError: true };
79
130
  }
80
131
  return { result: response.data };
81
132
  };
82
133
  // ============================================================================
83
134
  // Member Management
84
135
  // ============================================================================
85
- export const listOrgMembers = async (args, ctx) => {
86
- const { organization_id } = args;
87
- validateRequired(organization_id, 'organization_id');
88
- validateUUID(organization_id, 'organization_id');
136
+ export const listOrgMembers = async (args, _ctx) => {
137
+ const { organization_id } = parseArgs(args, listOrgMembersSchema);
89
138
  const apiClient = getApiClient();
90
139
  const response = await apiClient.listOrgMembers(organization_id);
91
140
  if (!response.ok) {
92
- throw new Error(response.error || 'Failed to list members');
141
+ return { result: { error: response.error || 'Failed to list members' }, isError: true };
93
142
  }
94
143
  return { result: response.data };
95
144
  };
96
- export const inviteMember = async (args, ctx) => {
97
- const { organization_id, email, role = 'member' } = args;
98
- validateRequired(organization_id, 'organization_id');
99
- validateRequired(email, 'email');
100
- validateUUID(organization_id, 'organization_id');
101
- if (!['admin', 'member', 'viewer'].includes(role)) {
102
- throw new Error('Invalid role. Must be admin, member, or viewer.');
103
- }
145
+ export const inviteMember = async (args, _ctx) => {
146
+ const { organization_id, email, role } = parseArgs(args, inviteMemberSchema);
104
147
  const apiClient = getApiClient();
105
148
  const response = await apiClient.inviteMember(organization_id, email, role);
106
149
  if (!response.ok) {
107
- throw new Error(response.error || 'Failed to create invite');
150
+ return { result: { error: response.error || 'Failed to create invite' }, isError: true };
108
151
  }
109
152
  return { result: response.data };
110
153
  };
111
- export const updateMemberRole = async (args, ctx) => {
112
- const { organization_id, user_id, role } = args;
113
- validateRequired(organization_id, 'organization_id');
114
- validateRequired(user_id, 'user_id');
115
- validateRequired(role, 'role');
116
- validateUUID(organization_id, 'organization_id');
117
- validateUUID(user_id, 'user_id');
118
- if (!ROLE_ORDER.includes(role)) {
119
- throw new Error(`Invalid role. Must be one of: ${ROLE_ORDER.join(', ')}`);
120
- }
154
+ export const updateMemberRole = async (args, _ctx) => {
155
+ const { organization_id, user_id, role } = parseArgs(args, updateMemberRoleSchema);
121
156
  if (role === 'owner') {
122
- throw new Error('Cannot assign owner role. Use transfer ownership instead.');
157
+ throw new ValidationError('Cannot assign owner role. Use transfer ownership instead.');
123
158
  }
124
159
  const apiClient = getApiClient();
125
160
  const response = await apiClient.updateMemberRole(organization_id, user_id, role);
126
161
  if (!response.ok) {
127
- throw new Error(response.error || 'Failed to update member role');
162
+ return { result: { error: response.error || 'Failed to update member role' }, isError: true };
128
163
  }
129
164
  return { result: response.data };
130
165
  };
131
- export const removeMember = async (args, ctx) => {
132
- const { organization_id, user_id } = args;
133
- validateRequired(organization_id, 'organization_id');
134
- validateRequired(user_id, 'user_id');
135
- validateUUID(organization_id, 'organization_id');
136
- validateUUID(user_id, 'user_id');
166
+ export const removeMember = async (args, _ctx) => {
167
+ const { organization_id, user_id } = parseArgs(args, removeMemberSchema);
137
168
  const apiClient = getApiClient();
138
169
  const response = await apiClient.removeMember(organization_id, user_id);
139
170
  if (!response.ok) {
140
- throw new Error(response.error || 'Failed to remove member');
171
+ return { result: { error: response.error || 'Failed to remove member' }, isError: true };
141
172
  }
142
173
  return { result: response.data };
143
174
  };
144
- export const leaveOrganization = async (args, ctx) => {
145
- const { organization_id } = args;
146
- validateRequired(organization_id, 'organization_id');
147
- validateUUID(organization_id, 'organization_id');
175
+ export const leaveOrganization = async (args, _ctx) => {
176
+ const { organization_id } = parseArgs(args, leaveOrganizationSchema);
148
177
  const apiClient = getApiClient();
149
178
  const response = await apiClient.leaveOrganization(organization_id);
150
179
  if (!response.ok) {
151
- throw new Error(response.error || 'Failed to leave organization');
180
+ return { result: { error: response.error || 'Failed to leave organization' }, isError: true };
152
181
  }
153
182
  return { result: response.data };
154
183
  };
155
184
  // ============================================================================
156
185
  // Project Sharing
157
186
  // ============================================================================
158
- export const shareProjectWithOrg = async (args, ctx) => {
159
- const { project_id, organization_id, permission = 'read' } = args;
160
- validateRequired(project_id, 'project_id');
161
- validateRequired(organization_id, 'organization_id');
162
- validateUUID(project_id, 'project_id');
163
- validateUUID(organization_id, 'organization_id');
164
- if (!PERMISSION_ORDER.includes(permission)) {
165
- throw new Error(`Invalid permission. Must be one of: ${PERMISSION_ORDER.join(', ')}`);
166
- }
187
+ export const shareProjectWithOrg = async (args, _ctx) => {
188
+ const { project_id, organization_id, permission } = parseArgs(args, shareProjectWithOrgSchema);
167
189
  const apiClient = getApiClient();
168
190
  const response = await apiClient.shareProjectWithOrg(project_id, organization_id, permission);
169
191
  if (!response.ok) {
170
- throw new Error(response.error || 'Failed to share project');
192
+ return { result: { error: response.error || 'Failed to share project' }, isError: true };
171
193
  }
172
194
  return { result: response.data };
173
195
  };
174
- export const updateProjectShare = async (args, ctx) => {
175
- const { project_id, organization_id, permission } = args;
176
- validateRequired(project_id, 'project_id');
177
- validateRequired(organization_id, 'organization_id');
178
- validateRequired(permission, 'permission');
179
- validateUUID(project_id, 'project_id');
180
- validateUUID(organization_id, 'organization_id');
181
- if (!PERMISSION_ORDER.includes(permission)) {
182
- throw new Error(`Invalid permission. Must be one of: ${PERMISSION_ORDER.join(', ')}`);
183
- }
196
+ export const updateProjectShare = async (args, _ctx) => {
197
+ const { project_id, organization_id, permission } = parseArgs(args, updateProjectShareSchema);
184
198
  const apiClient = getApiClient();
185
199
  const response = await apiClient.updateProjectShare(project_id, organization_id, permission);
186
200
  if (!response.ok) {
187
- throw new Error(response.error || 'Failed to update share');
201
+ return { result: { error: response.error || 'Failed to update share' }, isError: true };
188
202
  }
189
203
  return { result: response.data };
190
204
  };
191
- export const unshareProject = async (args, ctx) => {
192
- const { project_id, organization_id } = args;
193
- validateRequired(project_id, 'project_id');
194
- validateRequired(organization_id, 'organization_id');
195
- validateUUID(project_id, 'project_id');
196
- validateUUID(organization_id, 'organization_id');
205
+ export const unshareProject = async (args, _ctx) => {
206
+ const { project_id, organization_id } = parseArgs(args, unshareProjectSchema);
197
207
  const apiClient = getApiClient();
198
208
  const response = await apiClient.unshareProject(project_id, organization_id);
199
209
  if (!response.ok) {
200
- throw new Error(response.error || 'Failed to unshare project');
210
+ return { result: { error: response.error || 'Failed to unshare project' }, isError: true };
201
211
  }
202
212
  return { result: response.data };
203
213
  };
204
- export const listProjectShares = async (args, ctx) => {
205
- const { project_id } = args;
206
- validateRequired(project_id, 'project_id');
207
- validateUUID(project_id, 'project_id');
214
+ export const listProjectShares = async (args, _ctx) => {
215
+ const { project_id } = parseArgs(args, listProjectSharesSchema);
208
216
  const apiClient = getApiClient();
209
217
  const response = await apiClient.listProjectShares(project_id);
210
218
  if (!response.ok) {
211
- throw new Error(response.error || 'Failed to list shares');
219
+ return { result: { error: response.error || 'Failed to list shares' }, isError: true };
212
220
  }
213
221
  return { result: response.data };
214
222
  };