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
@@ -0,0 +1,575 @@
1
+ /**
2
+ * Linear Project Management Provider
3
+ *
4
+ * Concrete implementation of ProjectManagementProvider using Linear GraphQL API.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import { defineProvider } from '../registry.js';
9
+ const LINEAR_API_URL = 'https://api.linear.app/graphql';
10
+ /**
11
+ * Linear provider info
12
+ */
13
+ export const linearInfo = {
14
+ id: 'project-management.linear',
15
+ name: 'Linear',
16
+ description: 'Linear issue tracking and project management',
17
+ category: 'project-management',
18
+ website: 'https://linear.app',
19
+ docsUrl: 'https://developers.linear.app',
20
+ requiredConfig: ['apiKey'],
21
+ optionalConfig: [],
22
+ };
23
+ /**
24
+ * GraphQL query helper
25
+ */
26
+ async function graphqlRequest(apiKey, query, variables) {
27
+ const response = await fetch(LINEAR_API_URL, {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ Authorization: apiKey,
32
+ },
33
+ body: JSON.stringify({ query, variables }),
34
+ });
35
+ const result = (await response.json());
36
+ if (result.errors) {
37
+ throw new Error(result.errors[0]?.message || 'GraphQL query failed');
38
+ }
39
+ return result.data;
40
+ }
41
+ /**
42
+ * Create Linear project management provider
43
+ */
44
+ export function createLinearProvider(config) {
45
+ let apiKey;
46
+ return {
47
+ info: linearInfo,
48
+ async initialize(cfg) {
49
+ apiKey = cfg.apiKey;
50
+ if (!apiKey) {
51
+ throw new Error('Linear API key is required');
52
+ }
53
+ },
54
+ async healthCheck() {
55
+ const start = Date.now();
56
+ try {
57
+ await graphqlRequest(apiKey, `query { viewer { id name } }`);
58
+ return {
59
+ healthy: true,
60
+ latencyMs: Date.now() - start,
61
+ message: 'Connected',
62
+ checkedAt: new Date(),
63
+ };
64
+ }
65
+ catch (error) {
66
+ return {
67
+ healthy: false,
68
+ latencyMs: Date.now() - start,
69
+ message: error instanceof Error ? error.message : 'Unknown error',
70
+ checkedAt: new Date(),
71
+ };
72
+ }
73
+ },
74
+ async dispose() {
75
+ // No cleanup needed
76
+ },
77
+ async listProjects(options) {
78
+ const first = options?.limit || 50;
79
+ const after = options?.cursor;
80
+ const query = `
81
+ query($first: Int!, $after: String) {
82
+ projects(first: $first, after: $after) {
83
+ nodes {
84
+ id
85
+ key
86
+ name
87
+ description
88
+ lead {
89
+ id
90
+ }
91
+ url
92
+ }
93
+ pageInfo {
94
+ hasNextPage
95
+ endCursor
96
+ }
97
+ }
98
+ }
99
+ `;
100
+ const data = await graphqlRequest(apiKey, query, { first, after });
101
+ return {
102
+ items: data.projects.nodes.map((p) => ({
103
+ id: p.id,
104
+ key: p.key,
105
+ name: p.name,
106
+ description: p.description,
107
+ lead: p.lead?.id,
108
+ url: p.url,
109
+ })),
110
+ hasMore: data.projects.pageInfo.hasNextPage,
111
+ nextCursor: data.projects.pageInfo.endCursor,
112
+ };
113
+ },
114
+ async getProject(projectId) {
115
+ const query = `
116
+ query($id: String!) {
117
+ project(id: $id) {
118
+ id
119
+ key
120
+ name
121
+ description
122
+ lead {
123
+ id
124
+ }
125
+ url
126
+ }
127
+ }
128
+ `;
129
+ try {
130
+ const data = await graphqlRequest(apiKey, query, { id: projectId });
131
+ if (!data.project) {
132
+ return null;
133
+ }
134
+ return {
135
+ id: data.project.id,
136
+ key: data.project.key,
137
+ name: data.project.name,
138
+ description: data.project.description,
139
+ lead: data.project.lead?.id,
140
+ url: data.project.url,
141
+ };
142
+ }
143
+ catch {
144
+ return null;
145
+ }
146
+ },
147
+ async createIssue(projectId, issue) {
148
+ const query = `
149
+ mutation($input: IssueCreateInput!) {
150
+ issueCreate(input: $input) {
151
+ success
152
+ issue {
153
+ id
154
+ identifier
155
+ title
156
+ description
157
+ state {
158
+ name
159
+ }
160
+ priority
161
+ priorityLabel
162
+ labels {
163
+ nodes {
164
+ name
165
+ }
166
+ }
167
+ assignee {
168
+ id
169
+ }
170
+ creator {
171
+ id
172
+ }
173
+ createdAt
174
+ updatedAt
175
+ url
176
+ }
177
+ }
178
+ }
179
+ `;
180
+ const input = {
181
+ projectId,
182
+ title: issue.title,
183
+ description: issue.description,
184
+ ...(issue.type && { type: issue.type }),
185
+ ...(issue.priority && { priority: parsePriority(issue.priority) }),
186
+ ...(issue.assigneeId && { assigneeId: issue.assigneeId }),
187
+ ...(issue.parentId && { parentId: issue.parentId }),
188
+ ...(issue.estimate && { estimate: issue.estimate }),
189
+ };
190
+ if (issue.labels?.length) {
191
+ input.labelIds = issue.labels;
192
+ }
193
+ const data = await graphqlRequest(apiKey, query, { input });
194
+ if (!data.issueCreate.success) {
195
+ throw new Error('Failed to create issue');
196
+ }
197
+ const created = data.issueCreate.issue;
198
+ return {
199
+ id: created.id,
200
+ key: created.identifier,
201
+ title: created.title,
202
+ description: created.description,
203
+ type: issue.type || 'Issue',
204
+ status: created.state.name,
205
+ priority: created.priorityLabel,
206
+ labels: created.labels.nodes.map((l) => l.name),
207
+ assigneeId: created.assignee?.id,
208
+ reporterId: created.creator?.id,
209
+ createdAt: new Date(created.createdAt),
210
+ updatedAt: new Date(created.updatedAt),
211
+ url: created.url,
212
+ };
213
+ },
214
+ async getIssue(issueId) {
215
+ const query = `
216
+ query($id: String!) {
217
+ issue(id: $id) {
218
+ id
219
+ identifier
220
+ title
221
+ description
222
+ state {
223
+ name
224
+ }
225
+ priority
226
+ priorityLabel
227
+ labels {
228
+ nodes {
229
+ name
230
+ }
231
+ }
232
+ assignee {
233
+ id
234
+ }
235
+ creator {
236
+ id
237
+ }
238
+ createdAt
239
+ updatedAt
240
+ url
241
+ }
242
+ }
243
+ `;
244
+ try {
245
+ const data = await graphqlRequest(apiKey, query, { id: issueId });
246
+ if (!data.issue) {
247
+ return null;
248
+ }
249
+ const issue = data.issue;
250
+ return {
251
+ id: issue.id,
252
+ key: issue.identifier,
253
+ title: issue.title,
254
+ description: issue.description,
255
+ type: 'Issue',
256
+ status: issue.state.name,
257
+ priority: issue.priorityLabel,
258
+ labels: issue.labels.nodes.map((l) => l.name),
259
+ assigneeId: issue.assignee?.id,
260
+ reporterId: issue.creator?.id,
261
+ createdAt: new Date(issue.createdAt),
262
+ updatedAt: new Date(issue.updatedAt),
263
+ url: issue.url,
264
+ };
265
+ }
266
+ catch {
267
+ return null;
268
+ }
269
+ },
270
+ async updateIssue(issueId, updates) {
271
+ const query = `
272
+ mutation($id: String!, $input: IssueUpdateInput!) {
273
+ issueUpdate(id: $id, input: $input) {
274
+ success
275
+ issue {
276
+ id
277
+ identifier
278
+ title
279
+ description
280
+ state {
281
+ name
282
+ }
283
+ priority
284
+ priorityLabel
285
+ labels {
286
+ nodes {
287
+ name
288
+ }
289
+ }
290
+ assignee {
291
+ id
292
+ }
293
+ creator {
294
+ id
295
+ }
296
+ createdAt
297
+ updatedAt
298
+ url
299
+ }
300
+ }
301
+ }
302
+ `;
303
+ const input = {};
304
+ if (updates.title)
305
+ input.title = updates.title;
306
+ if (updates.description !== undefined)
307
+ input.description = updates.description;
308
+ if (updates.priority)
309
+ input.priority = parsePriority(updates.priority);
310
+ if (updates.assigneeId)
311
+ input.assigneeId = updates.assigneeId;
312
+ if (updates.estimate !== undefined)
313
+ input.estimate = updates.estimate;
314
+ const data = await graphqlRequest(apiKey, query, { id: issueId, input });
315
+ if (!data.issueUpdate.success) {
316
+ throw new Error('Failed to update issue');
317
+ }
318
+ const updated = data.issueUpdate.issue;
319
+ return {
320
+ id: updated.id,
321
+ key: updated.identifier,
322
+ title: updated.title,
323
+ description: updated.description,
324
+ type: 'Issue',
325
+ status: updated.state.name,
326
+ priority: updated.priorityLabel,
327
+ labels: updated.labels.nodes.map((l) => l.name),
328
+ assigneeId: updated.assignee?.id,
329
+ reporterId: updated.creator?.id,
330
+ createdAt: new Date(updated.createdAt),
331
+ updatedAt: new Date(updated.updatedAt),
332
+ url: updated.url,
333
+ };
334
+ },
335
+ async deleteIssue(issueId) {
336
+ const query = `
337
+ mutation($id: String!) {
338
+ issueDelete(id: $id) {
339
+ success
340
+ }
341
+ }
342
+ `;
343
+ try {
344
+ const data = await graphqlRequest(apiKey, query, { id: issueId });
345
+ return data.issueDelete.success;
346
+ }
347
+ catch {
348
+ return false;
349
+ }
350
+ },
351
+ async listIssues(projectId, options) {
352
+ const first = options?.limit || 50;
353
+ const after = options?.cursor;
354
+ // Build filter
355
+ const filter = { project: { id: { eq: projectId } } };
356
+ if (options?.status?.length) {
357
+ filter.state = { name: { in: options.status } };
358
+ }
359
+ if (options?.assignee) {
360
+ filter.assignee = { id: { eq: options.assignee } };
361
+ }
362
+ if (options?.labels?.length) {
363
+ filter.labels = { name: { in: options.labels } };
364
+ }
365
+ const query = `
366
+ query($first: Int!, $after: String, $filter: IssueFilter) {
367
+ issues(first: $first, after: $after, filter: $filter) {
368
+ nodes {
369
+ id
370
+ identifier
371
+ title
372
+ description
373
+ state {
374
+ name
375
+ }
376
+ priority
377
+ priorityLabel
378
+ labels {
379
+ nodes {
380
+ name
381
+ }
382
+ }
383
+ assignee {
384
+ id
385
+ }
386
+ creator {
387
+ id
388
+ }
389
+ createdAt
390
+ updatedAt
391
+ url
392
+ }
393
+ pageInfo {
394
+ hasNextPage
395
+ endCursor
396
+ }
397
+ }
398
+ }
399
+ `;
400
+ const data = await graphqlRequest(apiKey, query, { first, after, filter });
401
+ return {
402
+ items: data.issues.nodes.map((issue) => ({
403
+ id: issue.id,
404
+ key: issue.identifier,
405
+ title: issue.title,
406
+ description: issue.description,
407
+ type: 'Issue',
408
+ status: issue.state.name,
409
+ priority: issue.priorityLabel,
410
+ labels: issue.labels.nodes.map((l) => l.name),
411
+ assigneeId: issue.assignee?.id,
412
+ reporterId: issue.creator?.id,
413
+ createdAt: new Date(issue.createdAt),
414
+ updatedAt: new Date(issue.updatedAt),
415
+ url: issue.url,
416
+ })),
417
+ hasMore: data.issues.pageInfo.hasNextPage,
418
+ nextCursor: data.issues.pageInfo.endCursor,
419
+ };
420
+ },
421
+ async searchIssues(query, options) {
422
+ const first = options?.limit || 50;
423
+ const after = options?.cursor;
424
+ const graphqlQuery = `
425
+ query($first: Int!, $after: String, $filter: IssueFilter) {
426
+ issueSearch(first: $first, after: $after, query: $query, filter: $filter) {
427
+ nodes {
428
+ id
429
+ identifier
430
+ title
431
+ description
432
+ state {
433
+ name
434
+ }
435
+ priority
436
+ priorityLabel
437
+ labels {
438
+ nodes {
439
+ name
440
+ }
441
+ }
442
+ assignee {
443
+ id
444
+ }
445
+ creator {
446
+ id
447
+ }
448
+ createdAt
449
+ updatedAt
450
+ url
451
+ }
452
+ pageInfo {
453
+ hasNextPage
454
+ endCursor
455
+ }
456
+ }
457
+ }
458
+ `;
459
+ // Build filter
460
+ const filter = {};
461
+ if (options?.status?.length) {
462
+ filter.state = { name: { in: options.status } };
463
+ }
464
+ if (options?.assignee) {
465
+ filter.assignee = { id: { eq: options.assignee } };
466
+ }
467
+ if (options?.labels?.length) {
468
+ filter.labels = { name: { in: options.labels } };
469
+ }
470
+ const data = await graphqlRequest(apiKey, graphqlQuery, { first, after, query, filter });
471
+ return {
472
+ items: data.issueSearch.nodes.map((issue) => ({
473
+ id: issue.id,
474
+ key: issue.identifier,
475
+ title: issue.title,
476
+ description: issue.description,
477
+ type: 'Issue',
478
+ status: issue.state.name,
479
+ priority: issue.priorityLabel,
480
+ labels: issue.labels.nodes.map((l) => l.name),
481
+ assigneeId: issue.assignee?.id,
482
+ reporterId: issue.creator?.id,
483
+ createdAt: new Date(issue.createdAt),
484
+ updatedAt: new Date(issue.updatedAt),
485
+ url: issue.url,
486
+ })),
487
+ hasMore: data.issueSearch.pageInfo.hasNextPage,
488
+ nextCursor: data.issueSearch.pageInfo.endCursor,
489
+ };
490
+ },
491
+ async addComment(issueId, body) {
492
+ const query = `
493
+ mutation($input: CommentCreateInput!) {
494
+ commentCreate(input: $input) {
495
+ success
496
+ comment {
497
+ id
498
+ body
499
+ user {
500
+ id
501
+ }
502
+ createdAt
503
+ }
504
+ }
505
+ }
506
+ `;
507
+ const data = await graphqlRequest(apiKey, query, {
508
+ input: {
509
+ issueId,
510
+ body,
511
+ },
512
+ });
513
+ if (!data.commentCreate.success) {
514
+ throw new Error('Failed to create comment');
515
+ }
516
+ const comment = data.commentCreate.comment;
517
+ return {
518
+ id: comment.id,
519
+ issueId,
520
+ body: comment.body,
521
+ authorId: comment.user.id,
522
+ createdAt: new Date(comment.createdAt),
523
+ };
524
+ },
525
+ async transition(issueId, statusId) {
526
+ const query = `
527
+ mutation($id: String!, $stateId: String!) {
528
+ issueUpdate(id: $id, input: { stateId: $stateId }) {
529
+ success
530
+ }
531
+ }
532
+ `;
533
+ try {
534
+ const data = await graphqlRequest(apiKey, query, { id: issueId, stateId: statusId });
535
+ return data.issueUpdate.success;
536
+ }
537
+ catch {
538
+ return false;
539
+ }
540
+ },
541
+ async assign(issueId, userId) {
542
+ const query = `
543
+ mutation($id: String!, $assigneeId: String!) {
544
+ issueUpdate(id: $id, input: { assigneeId: $assigneeId }) {
545
+ success
546
+ }
547
+ }
548
+ `;
549
+ try {
550
+ const data = await graphqlRequest(apiKey, query, { id: issueId, assigneeId: userId });
551
+ return data.issueUpdate.success;
552
+ }
553
+ catch {
554
+ return false;
555
+ }
556
+ },
557
+ };
558
+ }
559
+ /**
560
+ * Parse priority string to Linear priority number
561
+ */
562
+ function parsePriority(priority) {
563
+ const map = {
564
+ urgent: 1,
565
+ high: 2,
566
+ medium: 3,
567
+ low: 4,
568
+ none: 0,
569
+ };
570
+ return map[priority.toLowerCase()] ?? 0;
571
+ }
572
+ /**
573
+ * Linear provider definition
574
+ */
575
+ export const linearProvider = defineProvider(linearInfo, async (config) => createLinearProvider(config));
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Provider Registry Implementation
3
+ *
4
+ * Central registry for discovering and instantiating providers.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ /**
9
+ * Create a new provider registry
10
+ */
11
+ export function createProviderRegistry() {
12
+ const providers = new Map();
13
+ return {
14
+ register(info, factory) {
15
+ if (providers.has(info.id)) {
16
+ throw new Error(`Provider '${info.id}' is already registered`);
17
+ }
18
+ providers.set(info.id, { info, factory });
19
+ },
20
+ get(providerId) {
21
+ return providers.get(providerId);
22
+ },
23
+ list(category) {
24
+ const all = Array.from(providers.values());
25
+ if (category) {
26
+ return all.filter((p) => p.info.category === category);
27
+ }
28
+ return all;
29
+ },
30
+ async create(providerId, config) {
31
+ const registered = providers.get(providerId);
32
+ if (!registered) {
33
+ throw new Error(`Provider '${providerId}' not found. Available: ${Array.from(providers.keys()).join(', ')}`);
34
+ }
35
+ // Validate required config
36
+ const missing = registered.info.requiredConfig.filter((key) => !(key in config));
37
+ if (missing.length > 0) {
38
+ throw new Error(`Provider '${providerId}' missing required config: ${missing.join(', ')}`);
39
+ }
40
+ const provider = await registered.factory(config);
41
+ await provider.initialize(config);
42
+ return provider;
43
+ },
44
+ has(providerId) {
45
+ return providers.has(providerId);
46
+ },
47
+ };
48
+ }
49
+ /**
50
+ * Global provider registry instance
51
+ */
52
+ export const providerRegistry = createProviderRegistry();
53
+ /**
54
+ * Register a provider in the global registry
55
+ */
56
+ export function registerProvider(info, factory) {
57
+ providerRegistry.register(info, factory);
58
+ }
59
+ /**
60
+ * Get a provider from the global registry
61
+ */
62
+ export function getProvider(providerId) {
63
+ return providerRegistry.get(providerId);
64
+ }
65
+ /**
66
+ * Create a provider instance from the global registry
67
+ */
68
+ export async function createProvider(providerId, config) {
69
+ return providerRegistry.create(providerId, config);
70
+ }
71
+ /**
72
+ * List providers by category
73
+ */
74
+ export function listProviders(category) {
75
+ return providerRegistry.list(category);
76
+ }
77
+ /**
78
+ * Helper to define a provider with type safety
79
+ */
80
+ export function defineProvider(info, factory) {
81
+ return {
82
+ info,
83
+ factory,
84
+ register: () => registerProvider(info, factory),
85
+ };
86
+ }