digital-tools 2.0.2 → 2.1.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 (93) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +3 -4
  3. package/src/define.js +267 -0
  4. package/src/entities/advertising.js +999 -0
  5. package/src/entities/ai.js +756 -0
  6. package/src/entities/analytics.js +1588 -0
  7. package/src/entities/automation.js +601 -0
  8. package/src/entities/communication.js +1150 -0
  9. package/src/entities/crm.js +1386 -0
  10. package/src/entities/design.js +546 -0
  11. package/src/entities/development.js +2212 -0
  12. package/src/entities/document.js +874 -0
  13. package/src/entities/ecommerce.js +1429 -0
  14. package/src/entities/experiment.js +1039 -0
  15. package/src/entities/finance.js +3478 -0
  16. package/src/entities/forms.js +1892 -0
  17. package/src/entities/hr.js +661 -0
  18. package/src/entities/identity.js +997 -0
  19. package/src/entities/index.js +282 -0
  20. package/src/entities/infrastructure.js +1153 -0
  21. package/src/entities/knowledge.js +1438 -0
  22. package/src/entities/marketing.js +1610 -0
  23. package/src/entities/media.js +1634 -0
  24. package/src/entities/notification.js +1199 -0
  25. package/src/entities/presentation.js +1274 -0
  26. package/src/entities/productivity.js +1317 -0
  27. package/src/entities/project-management.js +1136 -0
  28. package/src/entities/recruiting.js +736 -0
  29. package/src/entities/shipping.js +509 -0
  30. package/src/entities/signature.js +1102 -0
  31. package/src/entities/site.js +222 -0
  32. package/src/entities/spreadsheet.js +1341 -0
  33. package/src/entities/storage.js +1198 -0
  34. package/src/entities/support.js +1166 -0
  35. package/src/entities/video-conferencing.js +1750 -0
  36. package/src/entities/video.js +950 -0
  37. package/src/entities.js +1663 -0
  38. package/src/index.js +74 -0
  39. package/src/providers/analytics/index.js +17 -0
  40. package/src/providers/analytics/mixpanel.js +255 -0
  41. package/src/providers/calendar/cal-com.js +303 -0
  42. package/src/providers/calendar/google-calendar.js +335 -0
  43. package/src/providers/calendar/index.js +20 -0
  44. package/src/providers/crm/hubspot.js +566 -0
  45. package/src/providers/crm/index.js +17 -0
  46. package/src/providers/development/github.js +472 -0
  47. package/src/providers/development/index.js +17 -0
  48. package/src/providers/ecommerce/index.js +17 -0
  49. package/src/providers/ecommerce/shopify.js +378 -0
  50. package/src/providers/email/index.js +20 -0
  51. package/src/providers/email/resend.js +258 -0
  52. package/src/providers/email/sendgrid.js +161 -0
  53. package/src/providers/finance/index.js +17 -0
  54. package/src/providers/finance/stripe.js +549 -0
  55. package/src/providers/forms/index.js +17 -0
  56. package/src/providers/forms/typeform.js +500 -0
  57. package/src/providers/index.js +123 -0
  58. package/src/providers/knowledge/index.js +17 -0
  59. package/src/providers/knowledge/notion.js +389 -0
  60. package/src/providers/marketing/index.js +17 -0
  61. package/src/providers/marketing/mailchimp.js +443 -0
  62. package/src/providers/media/cloudinary.js +318 -0
  63. package/src/providers/media/index.js +17 -0
  64. package/src/providers/messaging/index.js +20 -0
  65. package/src/providers/messaging/slack.js +393 -0
  66. package/src/providers/messaging/twilio-sms.js +249 -0
  67. package/src/providers/project-management/index.js +17 -0
  68. package/src/providers/project-management/linear.js +575 -0
  69. package/src/providers/registry.js +86 -0
  70. package/src/providers/spreadsheet/google-sheets.js +375 -0
  71. package/src/providers/spreadsheet/index.js +20 -0
  72. package/src/providers/spreadsheet/xlsx.js +423 -0
  73. package/src/providers/storage/index.js +24 -0
  74. package/src/providers/storage/s3.js +419 -0
  75. package/src/providers/support/index.js +17 -0
  76. package/src/providers/support/zendesk.js +373 -0
  77. package/src/providers/tasks/index.js +17 -0
  78. package/src/providers/tasks/todoist.js +286 -0
  79. package/src/providers/types.js +9 -0
  80. package/src/providers/video-conferencing/google-meet.js +286 -0
  81. package/src/providers/video-conferencing/index.js +31 -0
  82. package/src/providers/video-conferencing/jitsi.js +254 -0
  83. package/src/providers/video-conferencing/teams.js +270 -0
  84. package/src/providers/video-conferencing/zoom.js +332 -0
  85. package/src/registry.js +128 -0
  86. package/src/tools/communication.js +184 -0
  87. package/src/tools/data.js +205 -0
  88. package/src/tools/index.js +11 -0
  89. package/src/tools/web.js +137 -0
  90. package/src/types.js +10 -0
  91. package/test/define.test.js +306 -0
  92. package/test/registry.test.js +357 -0
  93. package/test/tools.test.js +363 -0
package/src/index.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * digital-tools - Tools that can be used by both humans and AI agents
3
+ *
4
+ * This package provides:
5
+ * - Core Tool interface and types
6
+ * - Tool ontology/categories for organization
7
+ * - Tool registry for registration and discovery
8
+ * - Tool definition helpers with type safety
9
+ * - Common pre-built tool implementations
10
+ * - MCP (Model Context Protocol) compatibility
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ // Export entity type definitions (Nouns for digital tools)
15
+ export {
16
+ // Email
17
+ Email, EmailThread,
18
+ // Spreadsheet
19
+ Spreadsheet, Sheet, Cell,
20
+ // Document
21
+ Document,
22
+ // Presentation
23
+ Presentation, Slide,
24
+ // Phone
25
+ PhoneCall, Voicemail,
26
+ // Team Messaging (Slack/Teams/Discord equivalent)
27
+ Workspace, Channel, Message, Thread, DirectMessage, Member, Reaction,
28
+ // Supporting
29
+ Attachment, Contact, Comment, Revision,
30
+ // Collections
31
+ DigitalToolEntities, DigitalToolCategories, } from './entities.js';
32
+ // Export registry
33
+ export { registry, createRegistry, registerTool, getTool, executeTool, toMCP, listMCPTools, } from './registry.js';
34
+ // Export tool definition helpers
35
+ export { defineTool, defineAndRegister, createToolExecutor, toolBuilder, } from './define.js';
36
+ // Export pre-built tools
37
+ export {
38
+ // Web tools
39
+ fetchUrl, parseHtml, readUrl, webTools,
40
+ // Data tools
41
+ parseJson, stringifyJson, parseCsv, transformData, filterData, dataTools,
42
+ // Communication tools
43
+ sendEmail, sendSlackMessage, sendNotification, sendSms, communicationTools, } from './tools/index.js';
44
+ // Export providers (concrete implementations using third-party APIs)
45
+ export {
46
+ // Provider registry
47
+ providerRegistry, createProviderRegistry, registerProvider, getProvider, createProvider, listProviders, defineProvider,
48
+ // Email providers
49
+ sendgridProvider, resendProvider, createSendGridProvider, createResendProvider,
50
+ // Messaging providers
51
+ slackProvider, twilioSmsProvider, createSlackProvider, createTwilioSmsProvider,
52
+ // Spreadsheet providers
53
+ xlsxProvider, googleSheetsProvider, createXlsxProvider, createGoogleSheetsProvider,
54
+ // Registration helpers
55
+ registerAllProviders, registerEmailProviders, registerMessagingProviders, registerSpreadsheetProviders, allProviders, } from './providers/index.js';
56
+ // Convenience function to register all built-in tools
57
+ import { registry } from './registry.js';
58
+ import { webTools } from './tools/web.js';
59
+ import { dataTools } from './tools/data.js';
60
+ import { communicationTools } from './tools/communication.js';
61
+ /**
62
+ * Register all built-in tools in the global registry
63
+ */
64
+ export function registerBuiltinTools() {
65
+ for (const tool of [...webTools, ...dataTools, ...communicationTools]) {
66
+ registry.register(tool);
67
+ }
68
+ }
69
+ /**
70
+ * Get all built-in tools
71
+ */
72
+ export function getBuiltinTools() {
73
+ return [...webTools, ...dataTools, ...communicationTools];
74
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Analytics Providers
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ export { mixpanelInfo, mixpanelProvider, createMixpanelProvider } from './mixpanel.js';
7
+ import { mixpanelProvider } from './mixpanel.js';
8
+ /**
9
+ * Register all analytics providers
10
+ */
11
+ export function registerAnalyticsProviders() {
12
+ mixpanelProvider.register();
13
+ }
14
+ /**
15
+ * All analytics providers
16
+ */
17
+ export const analyticsProviders = [mixpanelProvider];
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Mixpanel Analytics Provider
3
+ *
4
+ * Concrete implementation of AnalyticsProvider using Mixpanel API.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import { defineProvider } from '../registry.js';
9
+ const MIXPANEL_TRACK_URL = 'https://api.mixpanel.com/track';
10
+ const MIXPANEL_ENGAGE_URL = 'https://api.mixpanel.com/engage';
11
+ const MIXPANEL_QUERY_URL = 'https://mixpanel.com/api/2.0';
12
+ /**
13
+ * Mixpanel provider info
14
+ */
15
+ export const mixpanelInfo = {
16
+ id: 'analytics.mixpanel',
17
+ name: 'Mixpanel',
18
+ description: 'Mixpanel product analytics and user engagement platform',
19
+ category: 'analytics',
20
+ website: 'https://mixpanel.com',
21
+ docsUrl: 'https://developer.mixpanel.com/docs',
22
+ requiredConfig: ['projectToken'],
23
+ optionalConfig: ['apiSecret'],
24
+ };
25
+ /**
26
+ * Create Mixpanel analytics provider
27
+ */
28
+ export function createMixpanelProvider(config) {
29
+ let projectToken;
30
+ let apiSecret;
31
+ return {
32
+ info: mixpanelInfo,
33
+ async initialize(cfg) {
34
+ projectToken = cfg.projectToken;
35
+ apiSecret = cfg.apiSecret;
36
+ if (!projectToken) {
37
+ throw new Error('Mixpanel project token is required');
38
+ }
39
+ },
40
+ async healthCheck() {
41
+ const start = Date.now();
42
+ try {
43
+ // Try to track a test event to verify connectivity
44
+ const testEvent = {
45
+ event: 'health_check',
46
+ properties: {
47
+ token: projectToken,
48
+ time: Math.floor(Date.now() / 1000),
49
+ $insert_id: `health_check_${Date.now()}`,
50
+ },
51
+ };
52
+ const response = await fetch(MIXPANEL_TRACK_URL, {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ },
57
+ body: JSON.stringify([testEvent]),
58
+ });
59
+ const result = await response.json();
60
+ return {
61
+ healthy: response.ok && result.status === 1,
62
+ latencyMs: Date.now() - start,
63
+ message: response.ok ? 'Connected' : `HTTP ${response.status}`,
64
+ checkedAt: new Date(),
65
+ };
66
+ }
67
+ catch (error) {
68
+ return {
69
+ healthy: false,
70
+ latencyMs: Date.now() - start,
71
+ message: error instanceof Error ? error.message : 'Unknown error',
72
+ checkedAt: new Date(),
73
+ };
74
+ }
75
+ },
76
+ async dispose() {
77
+ // No cleanup needed
78
+ },
79
+ async track(event) {
80
+ try {
81
+ const eventData = {
82
+ event: event.event,
83
+ properties: {
84
+ token: projectToken,
85
+ distinct_id: event.userId || event.anonymousId || 'unknown',
86
+ time: event.timestamp ? Math.floor(event.timestamp.getTime() / 1000) : Math.floor(Date.now() / 1000),
87
+ $insert_id: `${event.event}_${Date.now()}_${Math.random().toString(36).substring(7)}`,
88
+ ...event.properties,
89
+ },
90
+ };
91
+ const response = await fetch(MIXPANEL_TRACK_URL, {
92
+ method: 'POST',
93
+ headers: {
94
+ 'Content-Type': 'application/json',
95
+ },
96
+ body: JSON.stringify([eventData]),
97
+ });
98
+ const result = await response.json();
99
+ return response.ok && result.status === 1;
100
+ }
101
+ catch (error) {
102
+ console.error('Mixpanel track error:', error);
103
+ return false;
104
+ }
105
+ },
106
+ async identify(userId, traits) {
107
+ try {
108
+ const engageData = {
109
+ $token: projectToken,
110
+ $distinct_id: userId,
111
+ $set: traits || {},
112
+ };
113
+ const response = await fetch(MIXPANEL_ENGAGE_URL, {
114
+ method: 'POST',
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ },
118
+ body: JSON.stringify([engageData]),
119
+ });
120
+ const result = await response.json();
121
+ return response.ok && result.status === 1;
122
+ }
123
+ catch (error) {
124
+ console.error('Mixpanel identify error:', error);
125
+ return false;
126
+ }
127
+ },
128
+ async page(name, properties) {
129
+ return this.track({
130
+ event: 'Page Viewed',
131
+ properties: {
132
+ page_name: name,
133
+ ...properties,
134
+ },
135
+ });
136
+ },
137
+ async alias(userId, previousId) {
138
+ try {
139
+ const aliasEvent = {
140
+ event: '$create_alias',
141
+ properties: {
142
+ token: projectToken,
143
+ distinct_id: previousId,
144
+ alias: userId,
145
+ },
146
+ };
147
+ const response = await fetch(MIXPANEL_TRACK_URL, {
148
+ method: 'POST',
149
+ headers: {
150
+ 'Content-Type': 'application/json',
151
+ },
152
+ body: JSON.stringify([aliasEvent]),
153
+ });
154
+ const result = await response.json();
155
+ return response.ok && result.status === 1;
156
+ }
157
+ catch (error) {
158
+ console.error('Mixpanel alias error:', error);
159
+ return false;
160
+ }
161
+ },
162
+ async getReport(reportId) {
163
+ if (!apiSecret) {
164
+ throw new Error('Mixpanel API secret is required for querying reports');
165
+ }
166
+ try {
167
+ const auth = Buffer.from(`${apiSecret}:`).toString('base64');
168
+ const response = await fetch(`${MIXPANEL_QUERY_URL}/reports/${reportId}`, {
169
+ headers: {
170
+ Authorization: `Basic ${auth}`,
171
+ },
172
+ });
173
+ if (!response.ok) {
174
+ return null;
175
+ }
176
+ const data = await response.json();
177
+ return {
178
+ id: reportId,
179
+ name: data.name || reportId,
180
+ description: data.description,
181
+ query: data.query || { metrics: [], dateRange: { start: new Date(), end: new Date() } },
182
+ result: data.result,
183
+ createdAt: data.created ? new Date(data.created) : new Date(),
184
+ updatedAt: data.updated ? new Date(data.updated) : new Date(),
185
+ };
186
+ }
187
+ catch (error) {
188
+ console.error('Mixpanel getReport error:', error);
189
+ return null;
190
+ }
191
+ },
192
+ async runQuery(query) {
193
+ if (!apiSecret) {
194
+ throw new Error('Mixpanel API secret is required for running queries');
195
+ }
196
+ try {
197
+ const auth = Buffer.from(`${apiSecret}:`).toString('base64');
198
+ // Construct query parameters
199
+ const params = new URLSearchParams({
200
+ from_date: query.dateRange.start.toISOString().split('T')[0],
201
+ to_date: query.dateRange.end.toISOString().split('T')[0],
202
+ });
203
+ if (query.metrics.length > 0) {
204
+ params.append('event', query.metrics.join(','));
205
+ }
206
+ if (query.limit) {
207
+ params.append('limit', query.limit.toString());
208
+ }
209
+ // Use the segmentation endpoint for queries
210
+ const endpoint = query.dimensions && query.dimensions.length > 0
211
+ ? 'segmentation'
212
+ : 'events';
213
+ const response = await fetch(`${MIXPANEL_QUERY_URL}/${endpoint}?${params.toString()}`, {
214
+ headers: {
215
+ Authorization: `Basic ${auth}`,
216
+ },
217
+ });
218
+ if (!response.ok) {
219
+ throw new Error(`Query failed: ${response.statusText}`);
220
+ }
221
+ const data = await response.json();
222
+ // Transform Mixpanel response to our format
223
+ const rows = [];
224
+ const totals = {};
225
+ if (data.data) {
226
+ Object.entries(data.data).forEach(([key, values]) => {
227
+ const row = { metric: key };
228
+ if (typeof values === 'object' && values !== null) {
229
+ Object.entries(values).forEach(([date, value]) => {
230
+ row[date] = value;
231
+ if (typeof value === 'number') {
232
+ totals[key] = (totals[key] || 0) + value;
233
+ }
234
+ });
235
+ }
236
+ rows.push(row);
237
+ });
238
+ }
239
+ return {
240
+ rows,
241
+ totals,
242
+ rowCount: rows.length,
243
+ };
244
+ }
245
+ catch (error) {
246
+ console.error('Mixpanel runQuery error:', error);
247
+ throw error;
248
+ }
249
+ },
250
+ };
251
+ }
252
+ /**
253
+ * Mixpanel provider definition
254
+ */
255
+ export const mixpanelProvider = defineProvider(mixpanelInfo, async (config) => createMixpanelProvider(config));
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Cal.com Provider
3
+ *
4
+ * Concrete implementation of CalendarProvider using Cal.com API v1.
5
+ * Cal.com is an open-source Calendly alternative for scheduling.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import { defineProvider } from '../registry.js';
10
+ const DEFAULT_BASE_URL = 'https://api.cal.com/v1';
11
+ /**
12
+ * Cal.com provider info
13
+ */
14
+ export const calComInfo = {
15
+ id: 'calendar.cal-com',
16
+ name: 'Cal.com',
17
+ description: 'Cal.com API for scheduling and calendar management (open-source Calendly alternative)',
18
+ category: 'calendar',
19
+ website: 'https://cal.com',
20
+ docsUrl: 'https://cal.com/docs/api-reference',
21
+ requiredConfig: ['apiKey'],
22
+ optionalConfig: ['baseUrl'],
23
+ };
24
+ /**
25
+ * Create Cal.com provider
26
+ */
27
+ export function createCalComProvider(config) {
28
+ let apiKey;
29
+ let baseUrl;
30
+ /**
31
+ * Helper to make authenticated API requests
32
+ */
33
+ async function apiRequest(endpoint, options = {}) {
34
+ try {
35
+ const url = `${baseUrl}${endpoint}`;
36
+ const params = new URLSearchParams();
37
+ // Cal.com uses apiKey query parameter for authentication
38
+ params.append('apiKey', apiKey);
39
+ const fullUrl = `${url}${url.includes('?') ? '&' : '?'}${params}`;
40
+ const response = await fetch(fullUrl, {
41
+ ...options,
42
+ headers: {
43
+ 'Content-Type': 'application/json',
44
+ ...options.headers,
45
+ },
46
+ });
47
+ if (response.ok) {
48
+ const data = (await response.json());
49
+ return { ok: true, status: response.status, data };
50
+ }
51
+ const errorData = await response.json().catch(() => ({}));
52
+ return {
53
+ ok: false,
54
+ status: response.status,
55
+ error: errorData?.message || response.statusText,
56
+ };
57
+ }
58
+ catch (error) {
59
+ return {
60
+ ok: false,
61
+ status: 0,
62
+ error: error instanceof Error ? error.message : 'Unknown error',
63
+ };
64
+ }
65
+ }
66
+ return {
67
+ info: calComInfo,
68
+ async initialize(cfg) {
69
+ apiKey = cfg.apiKey;
70
+ baseUrl = cfg.baseUrl || DEFAULT_BASE_URL;
71
+ if (!apiKey) {
72
+ throw new Error('Cal.com API key is required');
73
+ }
74
+ },
75
+ async healthCheck() {
76
+ const start = Date.now();
77
+ const result = await apiRequest('/me', { method: 'GET' });
78
+ return {
79
+ healthy: result.ok,
80
+ latencyMs: Date.now() - start,
81
+ message: result.ok ? 'Connected' : result.error || `HTTP ${result.status}`,
82
+ checkedAt: new Date(),
83
+ };
84
+ },
85
+ async dispose() {
86
+ // No cleanup needed
87
+ },
88
+ async listCalendars(options) {
89
+ // Cal.com uses "event types" as calendar configurations
90
+ const params = new URLSearchParams();
91
+ if (options?.limit)
92
+ params.append('take', String(options.limit));
93
+ if (options?.cursor)
94
+ params.append('skip', String(options.cursor));
95
+ const result = await apiRequest(`/event-types?${params}`);
96
+ if (!result.ok || !result.data) {
97
+ return { items: [], hasMore: false };
98
+ }
99
+ const items = result.data.event_types
100
+ .filter((et) => !et.hidden)
101
+ .map((et) => ({
102
+ id: String(et.id),
103
+ name: et.title,
104
+ description: et.description,
105
+ timeZone: 'UTC', // Cal.com handles timezone per user
106
+ primary: et.position === 0,
107
+ accessRole: 'owner',
108
+ }));
109
+ return {
110
+ items,
111
+ hasMore: result.data.event_types.length >= (options?.limit || 10),
112
+ };
113
+ },
114
+ async getCalendar(calendarId) {
115
+ const result = await apiRequest(`/event-types/${calendarId}`);
116
+ if (!result.ok || !result.data) {
117
+ return null;
118
+ }
119
+ const et = result.data.event_type;
120
+ return {
121
+ id: String(et.id),
122
+ name: et.title,
123
+ description: et.description,
124
+ timeZone: 'UTC',
125
+ primary: et.position === 0,
126
+ accessRole: 'owner',
127
+ };
128
+ },
129
+ async createEvent(calendarId, event) {
130
+ // In Cal.com, creating a booking is done through the booking endpoint
131
+ const body = {
132
+ eventTypeId: parseInt(calendarId, 10),
133
+ start: event.start.toISOString(),
134
+ end: event.end.toISOString(),
135
+ responses: {
136
+ name: event.summary,
137
+ email: event.attendees?.[0] || 'guest@example.com',
138
+ notes: event.description,
139
+ location: event.location,
140
+ },
141
+ timeZone: 'UTC',
142
+ language: 'en',
143
+ metadata: {},
144
+ };
145
+ const result = await apiRequest('/bookings', {
146
+ method: 'POST',
147
+ body: JSON.stringify(body),
148
+ });
149
+ if (!result.ok || !result.data) {
150
+ throw new Error(result.error || 'Failed to create booking');
151
+ }
152
+ const booking = result.data.booking;
153
+ return {
154
+ id: booking.uid,
155
+ calendarId,
156
+ summary: booking.title,
157
+ description: booking.description,
158
+ location: booking.location,
159
+ start: new Date(booking.startTime),
160
+ end: new Date(booking.endTime),
161
+ attendees: booking.attendees.map((a) => ({
162
+ email: a.email,
163
+ responseStatus: booking.status === 'ACCEPTED' ? 'accepted' : 'needsAction',
164
+ })),
165
+ status: (booking.status === 'CANCELLED' ? 'cancelled' : 'confirmed'),
166
+ htmlLink: `${baseUrl.replace('/v1', '')}/booking/${booking.uid}`,
167
+ };
168
+ },
169
+ async getEvent(calendarId, eventId) {
170
+ const result = await apiRequest(`/bookings/${eventId}`);
171
+ if (!result.ok || !result.data) {
172
+ return null;
173
+ }
174
+ const booking = result.data.booking;
175
+ return {
176
+ id: booking.uid,
177
+ calendarId: String(booking.eventTypeId),
178
+ summary: booking.title,
179
+ description: booking.description,
180
+ location: booking.location,
181
+ start: new Date(booking.startTime),
182
+ end: new Date(booking.endTime),
183
+ attendees: booking.attendees.map((a) => ({
184
+ email: a.email,
185
+ responseStatus: booking.status === 'ACCEPTED' ? 'accepted' : 'needsAction',
186
+ })),
187
+ status: (booking.status === 'CANCELLED' ? 'cancelled' : 'confirmed'),
188
+ htmlLink: `${baseUrl.replace('/v1', '')}/booking/${booking.uid}`,
189
+ };
190
+ },
191
+ async updateEvent(calendarId, eventId, updates) {
192
+ // Cal.com bookings are typically rescheduled rather than updated
193
+ const body = {};
194
+ if (updates.start && updates.end) {
195
+ body.start = updates.start.toISOString();
196
+ body.end = updates.end.toISOString();
197
+ }
198
+ if (updates.summary) {
199
+ body.title = updates.summary;
200
+ }
201
+ if (updates.description) {
202
+ body.description = updates.description;
203
+ }
204
+ const result = await apiRequest(`/bookings/${eventId}`, {
205
+ method: 'PATCH',
206
+ body: JSON.stringify(body),
207
+ });
208
+ if (!result.ok || !result.data) {
209
+ throw new Error(result.error || 'Failed to update booking');
210
+ }
211
+ const booking = result.data.booking;
212
+ return {
213
+ id: booking.uid,
214
+ calendarId: String(booking.eventTypeId),
215
+ summary: booking.title,
216
+ description: booking.description,
217
+ location: booking.location,
218
+ start: new Date(booking.startTime),
219
+ end: new Date(booking.endTime),
220
+ attendees: booking.attendees.map((a) => ({
221
+ email: a.email,
222
+ responseStatus: booking.status === 'ACCEPTED' ? 'accepted' : 'needsAction',
223
+ })),
224
+ status: (booking.status === 'CANCELLED' ? 'cancelled' : 'confirmed'),
225
+ htmlLink: `${baseUrl.replace('/v1', '')}/booking/${booking.uid}`,
226
+ };
227
+ },
228
+ async deleteEvent(calendarId, eventId) {
229
+ // Cancel the booking
230
+ const result = await apiRequest(`/bookings/${eventId}`, {
231
+ method: 'DELETE',
232
+ });
233
+ return result.ok;
234
+ },
235
+ async listEvents(calendarId, options) {
236
+ const params = new URLSearchParams();
237
+ if (options?.limit)
238
+ params.append('take', String(options.limit));
239
+ if (options?.cursor)
240
+ params.append('skip', String(options.cursor));
241
+ // Filter by event type (calendar)
242
+ params.append('eventTypeId', calendarId);
243
+ if (options?.timeMin)
244
+ params.append('afterStart', options.timeMin.toISOString());
245
+ if (options?.timeMax)
246
+ params.append('beforeEnd', options.timeMax.toISOString());
247
+ const result = await apiRequest(`/bookings?${params}`);
248
+ if (!result.ok || !result.data) {
249
+ return { items: [], hasMore: false };
250
+ }
251
+ const items = result.data.bookings.map((booking) => ({
252
+ id: booking.uid,
253
+ calendarId: String(booking.eventTypeId),
254
+ summary: booking.title,
255
+ description: booking.description,
256
+ location: booking.location,
257
+ start: new Date(booking.startTime),
258
+ end: new Date(booking.endTime),
259
+ attendees: booking.attendees.map((a) => ({
260
+ email: a.email,
261
+ responseStatus: booking.status === 'ACCEPTED' ? 'accepted' : 'needsAction',
262
+ })),
263
+ status: (booking.status === 'CANCELLED' ? 'cancelled' : 'confirmed'),
264
+ htmlLink: `${baseUrl.replace('/v1', '')}/booking/${booking.uid}`,
265
+ }));
266
+ return {
267
+ items,
268
+ hasMore: result.data.bookings.length >= (options?.limit || 10),
269
+ };
270
+ },
271
+ async findAvailability(calendarIds, timeMin, timeMax) {
272
+ // Cal.com's availability endpoint
273
+ const results = [];
274
+ for (const calendarId of calendarIds) {
275
+ const params = new URLSearchParams();
276
+ params.append('eventTypeId', calendarId);
277
+ params.append('dateFrom', timeMin.toISOString());
278
+ params.append('dateTo', timeMax.toISOString());
279
+ const result = await apiRequest(`/availability?${params}`);
280
+ if (result.ok && result.data) {
281
+ results.push({
282
+ calendarId,
283
+ busy: result.data.busy.map((slot) => ({
284
+ start: new Date(slot.start),
285
+ end: new Date(slot.end),
286
+ })),
287
+ });
288
+ }
289
+ else {
290
+ results.push({
291
+ calendarId,
292
+ busy: [],
293
+ });
294
+ }
295
+ }
296
+ return results;
297
+ },
298
+ };
299
+ }
300
+ /**
301
+ * Cal.com provider definition
302
+ */
303
+ export const calComProvider = defineProvider(calComInfo, async (config) => createCalComProvider(config));