@elevasis/core 0.20.0 → 0.21.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 (30) hide show
  1. package/dist/index.d.ts +108 -0
  2. package/dist/index.js +177 -27
  3. package/dist/knowledge/index.d.ts +54 -0
  4. package/dist/organization-model/index.d.ts +108 -0
  5. package/dist/organization-model/index.js +177 -27
  6. package/dist/test-utils/index.d.ts +54 -0
  7. package/dist/test-utils/index.js +177 -27
  8. package/package.json +3 -3
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +6 -1
  10. package/src/business/acquisition/api-schemas.test.ts +25 -0
  11. package/src/business/acquisition/api-schemas.ts +125 -2
  12. package/src/business/acquisition/build-templates.test.ts +28 -0
  13. package/src/business/acquisition/build-templates.ts +20 -8
  14. package/src/business/acquisition/types.ts +6 -1
  15. package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -0
  16. package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -41
  17. package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -0
  18. package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -0
  19. package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -0
  20. package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -0
  21. package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -0
  22. package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -0
  23. package/src/integrations/credentials/api-schemas.ts +21 -2
  24. package/src/integrations/credentials/schemas.ts +200 -164
  25. package/src/organization-model/__tests__/prospecting-ssot.test.ts +7 -4
  26. package/src/organization-model/domains/prospecting.ts +182 -25
  27. package/src/organization-model/domains/sales.ts +24 -3
  28. package/src/platform/constants/versions.ts +1 -1
  29. package/src/reference/_generated/contracts.md +6 -1
  30. package/src/server.ts +2 -0
@@ -4,7 +4,36 @@ import { DescriptionSchema, DisplayMetadataSchema, ModelIdSchema } from './share
4
4
 
5
5
  export const ProspectingLifecycleStageSchema = DisplayMetadataSchema.extend({
6
6
  id: ModelIdSchema,
7
- order: z.number().int().min(0)
7
+ order: z.number().min(0)
8
+ })
9
+
10
+ export const RecordColumnConfigSchema = z.object({
11
+ key: ModelIdSchema,
12
+ label: z.string().trim().min(1).max(120),
13
+ path: z.string().trim().min(1).max(500),
14
+ width: z.union([z.number().positive(), z.string().trim().min(1).max(100)]).optional(),
15
+ renderType: z.enum(['text', 'badge', 'datetime', 'count', 'json']).optional(),
16
+ badgeColor: z.string().trim().min(1).max(40).optional()
17
+ })
18
+
19
+ export const RecordColumnsConfigSchema = z
20
+ .object({
21
+ company: z.array(RecordColumnConfigSchema).optional(),
22
+ contact: z.array(RecordColumnConfigSchema).optional()
23
+ })
24
+ .refine((columns) => Boolean(columns.company?.length || columns.contact?.length), {
25
+ message: 'recordColumns must include at least one entity column set'
26
+ })
27
+
28
+ export const CredentialRequirementSchema = z.object({
29
+ key: ModelIdSchema,
30
+ provider: ModelIdSchema,
31
+ credentialType: z.enum(['api-key', 'api-key-secret', 'oauth', 'webhook-secret']),
32
+ label: z.string().trim().min(1).max(120),
33
+ required: z.boolean(),
34
+ selectionMode: z.enum(['single', 'multiple']).optional(),
35
+ inputPath: z.string().trim().min(1).max(500),
36
+ verifyOnRun: z.boolean().optional()
8
37
  })
9
38
 
10
39
  export const ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
@@ -12,11 +41,16 @@ export const ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
12
41
  primaryEntity: z.enum(['company', 'contact']),
13
42
  outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
14
43
  stageKey: ModelIdSchema,
44
+ recordEntity: z.enum(['company', 'contact']).optional(),
45
+ recordsStageKey: ModelIdSchema.optional(),
46
+ recordSourceStageKey: ModelIdSchema.optional(),
15
47
  dependsOn: z.array(ModelIdSchema).optional(),
16
48
  dependencyMode: z.literal('per-record-eligibility'),
17
49
  capabilityKey: ModelIdSchema,
18
50
  defaultBatchSize: z.number().int().positive(),
19
- maxBatchSize: z.number().int().positive()
51
+ maxBatchSize: z.number().int().positive(),
52
+ recordColumns: RecordColumnsConfigSchema.optional(),
53
+ credentialRequirements: z.array(CredentialRequirementSchema).optional()
20
54
  }).refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
21
55
  message: 'defaultBatchSize must be less than or equal to maxBatchSize',
22
56
  path: ['defaultBatchSize']
@@ -28,9 +62,68 @@ export const ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
28
62
  })
29
63
 
30
64
  export type ListBuilderStep = z.infer<typeof ProspectingBuildTemplateStepSchema>
65
+ export type RecordColumnConfig = z.infer<typeof RecordColumnConfigSchema>
66
+ export type CredentialRequirement = z.infer<typeof CredentialRequirementSchema>
31
67
  export type TemplateName = 'localServices' | 'dtcApolloClickup'
32
68
  export type StepName = string
33
69
 
70
+ const DTC_RECORD_COLUMNS = {
71
+ populated: {
72
+ company: [
73
+ { key: 'name', label: 'Company', path: 'company.name' },
74
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
75
+ { key: 'employee-count', label: 'Employees', path: 'company.numEmployees', renderType: 'count' },
76
+ { key: 'apollo-industry', label: 'Apollo industry', path: 'company.category' },
77
+ { key: 'location', label: 'Location', path: 'company.locationState' }
78
+ ]
79
+ },
80
+ crawled: {
81
+ company: [
82
+ { key: 'name', label: 'Company', path: 'company.name' },
83
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
84
+ { key: 'page-count', label: 'Pages', path: 'company.enrichmentData.websiteCrawl.pageCount', renderType: 'count' },
85
+ { key: 'crawl-status', label: 'Crawl status', path: 'processingState.crawled.status', renderType: 'badge' }
86
+ ]
87
+ },
88
+ extracted: {
89
+ company: [
90
+ { key: 'name', label: 'Company', path: 'company.name' },
91
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
92
+ { key: 'description', label: 'Description', path: 'company.enrichmentData.websiteCrawl.companyDescription' },
93
+ { key: 'services', label: 'Services', path: 'company.enrichmentData.websiteCrawl.services', renderType: 'json' },
94
+ { key: 'automation-gaps', label: 'Automation gaps', path: 'company.enrichmentData.websiteCrawl.automationGaps', renderType: 'json' },
95
+ { key: 'contact-count', label: 'Contacts', path: 'company.enrichmentData.websiteCrawl.emailCount', renderType: 'count' }
96
+ ]
97
+ },
98
+ qualified: {
99
+ company: [
100
+ { key: 'name', label: 'Company', path: 'company.name' },
101
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
102
+ { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
103
+ { key: 'signals', label: 'Signals', path: 'company.qualificationSignals', renderType: 'json' },
104
+ { key: 'disqualified-reason', label: 'Disqualified reason', path: 'processingState.qualified.data.disqualifiedReason' }
105
+ ]
106
+ },
107
+ decisionMakers: {
108
+ contact: [
109
+ { key: 'name', label: 'Name', path: 'contact.name' },
110
+ { key: 'title', label: 'Title', path: 'contact.title' },
111
+ { key: 'email', label: 'Email', path: 'contact.email' },
112
+ { key: 'linkedin', label: 'LinkedIn', path: 'contact.linkedinUrl' },
113
+ { key: 'priority-score', label: 'Priority', path: 'contact.enrichmentData.apollo.priorityScore', renderType: 'badge' }
114
+ ]
115
+ },
116
+ uploaded: {
117
+ company: [
118
+ { key: 'name', label: 'Company', path: 'company.name' },
119
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
120
+ { key: 'contacts', label: 'Contacts', path: 'company.enrichmentData.approvedLeadListExport.contacts', renderType: 'json' },
121
+ { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
122
+ { key: 'approval', label: 'Approval', path: 'company.enrichmentData.approvedLeadListExport.approvalStatus', renderType: 'badge' }
123
+ ]
124
+ }
125
+ } as const satisfies Record<string, z.infer<typeof RecordColumnsConfigSchema>>
126
+
34
127
  export const CapabilitySchema = z.object({
35
128
  id: ModelIdSchema,
36
129
  label: z.string(),
@@ -66,6 +159,13 @@ export const CAPABILITY_REGISTRY: CapabilityRegistry = [
66
159
  description: 'Check email deliverability before outreach.',
67
160
  resourceId: 'lgn-05-email-verification-workflow'
68
161
  },
162
+ {
163
+ id: 'lead-gen.company.apify-crawl',
164
+ label: 'Crawl websites',
165
+ description:
166
+ 'Crawl company websites via Apify and store raw page markdown in enrichmentData.websiteCrawl.pages for downstream LLM analysis.',
167
+ resourceId: 'lgn-02a-apify-website-crawl-workflow'
168
+ },
69
169
  {
70
170
  id: 'lead-gen.company.website-extract',
71
171
  label: 'Extract website signals',
@@ -217,7 +317,46 @@ export const PROSPECTING_STEPS = {
217
317
  dependencyMode: 'per-record-eligibility',
218
318
  capabilityKey: 'lead-gen.company.apollo-import',
219
319
  defaultBatchSize: 250,
220
- maxBatchSize: 1000
320
+ maxBatchSize: 1000,
321
+ recordColumns: DTC_RECORD_COLUMNS.populated,
322
+ credentialRequirements: [
323
+ {
324
+ key: 'apollo',
325
+ provider: 'apollo',
326
+ credentialType: 'api-key-secret',
327
+ label: 'Apollo API key',
328
+ required: true,
329
+ selectionMode: 'single',
330
+ inputPath: 'credential'
331
+ }
332
+ ]
333
+ },
334
+ apifyCrawl: {
335
+ id: 'apify-crawl',
336
+ label: 'Websites crawled',
337
+ description:
338
+ 'Crawl company websites via Apify and store raw page markdown in enrichmentData.websiteCrawl.pages for downstream LLM analysis. Overwrites the synthetic seed Apollo Import wrote with real page content.',
339
+ primaryEntity: 'company',
340
+ outputs: ['company'],
341
+ stageKey: 'crawled',
342
+ dependsOn: ['import-apollo-search'],
343
+ dependencyMode: 'per-record-eligibility',
344
+ capabilityKey: 'lead-gen.company.apify-crawl',
345
+ defaultBatchSize: 50,
346
+ maxBatchSize: 100,
347
+ recordColumns: DTC_RECORD_COLUMNS.crawled,
348
+ credentialRequirements: [
349
+ {
350
+ key: 'apify',
351
+ provider: 'apify',
352
+ credentialType: 'api-key-secret',
353
+ label: 'Apify API token',
354
+ required: true,
355
+ selectionMode: 'single',
356
+ inputPath: 'credential',
357
+ verifyOnRun: true
358
+ }
359
+ ]
221
360
  },
222
361
  analyzeWebsites: {
223
362
  id: 'analyze-websites',
@@ -226,11 +365,12 @@ export const PROSPECTING_STEPS = {
226
365
  primaryEntity: 'company',
227
366
  outputs: ['company'],
228
367
  stageKey: 'extracted',
229
- dependsOn: ['import-apollo-search'],
368
+ dependsOn: ['apify-crawl'],
230
369
  dependencyMode: 'per-record-eligibility',
231
370
  capabilityKey: 'lead-gen.company.website-extract',
232
371
  defaultBatchSize: 50,
233
- maxBatchSize: 100
372
+ maxBatchSize: 100,
373
+ recordColumns: DTC_RECORD_COLUMNS.extracted
234
374
  },
235
375
  scoreDtcFit: {
236
376
  id: 'score-dtc-fit',
@@ -243,7 +383,8 @@ export const PROSPECTING_STEPS = {
243
383
  dependencyMode: 'per-record-eligibility',
244
384
  capabilityKey: 'lead-gen.company.dtc-subscription-qualify',
245
385
  defaultBatchSize: 100,
246
- maxBatchSize: 250
386
+ maxBatchSize: 250,
387
+ recordColumns: DTC_RECORD_COLUMNS.qualified
247
388
  },
248
389
  enrichDecisionMakers: {
249
390
  id: 'enrich-decision-makers',
@@ -253,37 +394,53 @@ export const PROSPECTING_STEPS = {
253
394
  primaryEntity: 'company',
254
395
  outputs: ['contact'],
255
396
  stageKey: 'decision-makers-enriched',
397
+ recordEntity: 'contact',
256
398
  dependsOn: ['score-dtc-fit'],
257
399
  dependencyMode: 'per-record-eligibility',
258
400
  capabilityKey: 'lead-gen.contact.apollo-decision-maker-enrich',
259
401
  defaultBatchSize: 100,
260
- maxBatchSize: 250
261
- },
262
- verifyEmails: {
263
- id: 'verify-emails',
264
- label: 'Emails verified',
265
- description: 'Verify deliverability before the QC and handoff step.',
266
- primaryEntity: 'contact',
267
- outputs: ['contact'],
268
- stageKey: 'verified',
269
- dependsOn: ['enrich-decision-makers'],
270
- dependencyMode: 'per-record-eligibility',
271
- capabilityKey: 'lead-gen.contact.verify-email',
272
- defaultBatchSize: 250,
273
- maxBatchSize: 500
402
+ maxBatchSize: 250,
403
+ recordColumns: DTC_RECORD_COLUMNS.decisionMakers,
404
+ credentialRequirements: [
405
+ {
406
+ key: 'apollo',
407
+ provider: 'apollo',
408
+ credentialType: 'api-key-secret',
409
+ label: 'Apollo API key',
410
+ required: true,
411
+ selectionMode: 'single',
412
+ inputPath: 'credential'
413
+ }
414
+ ]
274
415
  },
275
416
  reviewAndExport: {
276
417
  id: 'review-and-export',
277
418
  label: 'Reviewed and exported',
278
- description: 'Operator QC approves or rejects leads, then approved records are exported as a lead list.',
419
+ description:
420
+ 'Operator QC approves or rejects qualified companies, then approved records are exported as a lead list with unverified emails.',
279
421
  primaryEntity: 'company',
280
422
  outputs: ['export'],
281
423
  stageKey: 'uploaded',
282
- dependsOn: ['verify-emails'],
424
+ recordsStageKey: 'uploaded',
425
+ recordSourceStageKey: 'qualified',
426
+ dependsOn: ['enrich-decision-makers'],
283
427
  dependencyMode: 'per-record-eligibility',
284
428
  capabilityKey: 'lead-gen.export.list',
285
429
  defaultBatchSize: 100,
286
- maxBatchSize: 250
430
+ maxBatchSize: 250,
431
+ recordColumns: DTC_RECORD_COLUMNS.uploaded,
432
+ credentialRequirements: [
433
+ {
434
+ key: 'clickup',
435
+ provider: 'clickup',
436
+ credentialType: 'api-key-secret',
437
+ label: 'ClickUp API token',
438
+ required: true,
439
+ selectionMode: 'single',
440
+ inputPath: 'clickupCredential',
441
+ verifyOnRun: true
442
+ }
443
+ ]
287
444
  }
288
445
  }
289
446
  } as const satisfies Record<TemplateName, Record<StepName, ListBuilderStep>>
@@ -311,7 +468,7 @@ function leadGenStagesForEntity(
311
468
  entity: LeadGenStageCatalogEntry['entity']
312
469
  ): z.infer<typeof ProspectingLifecycleStageSchema>[] {
313
470
  return Object.values(LEAD_GEN_STAGE_CATALOG)
314
- .filter((stage) => stage.entity === entity)
471
+ .filter((stage) => stage.entity === entity || stage.additionalEntities?.includes(entity))
315
472
  .sort((a, b) => a.order - b.order)
316
473
  .map(toProspectingLifecycleStage)
317
474
  }
@@ -346,10 +503,10 @@ export const DEFAULT_ORGANIZATION_MODEL_PROSPECTING: z.infer<typeof Organization
346
503
  'Prospecting pipeline for DTC subscription or subscription-ready brands where Apollo is the source and contact-enrichment layer, Elevasis handles company research and fit scoring, and approved leads export as an approved lead list.',
347
504
  steps: [
348
505
  PROSPECTING_STEPS.dtcApolloClickup.importApolloSearch,
506
+ PROSPECTING_STEPS.dtcApolloClickup.apifyCrawl,
349
507
  PROSPECTING_STEPS.dtcApolloClickup.analyzeWebsites,
350
508
  PROSPECTING_STEPS.dtcApolloClickup.scoreDtcFit,
351
509
  PROSPECTING_STEPS.dtcApolloClickup.enrichDecisionMakers,
352
- PROSPECTING_STEPS.dtcApolloClickup.verifyEmails,
353
510
  PROSPECTING_STEPS.dtcApolloClickup.reviewAndExport
354
511
  ]
355
512
  }
@@ -113,6 +113,7 @@ export const DEFAULT_ORGANIZATION_MODEL_SALES: z.infer<typeof OrganizationModelS
113
113
  // - interested (instantly-reply-handler.ts → contacts, initial reply transition)
114
114
  // prospecting/:
115
115
  // - populated (apify-acquire.ts, apify-scrape.ts → companies)
116
+ // - crawled (apify-website-crawl.ts → companies)
116
117
  // - extracted (website-extract.ts → companies)
117
118
  // - discovered (email-discovery.ts, anymailfinder-enrich.ts → contacts)
118
119
  // - verified (email-verification.ts → contacts)
@@ -414,7 +415,7 @@ export const ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE: StatefulPipelineDefinition =
414
415
  {
415
416
  stageKey: 'outreach',
416
417
  label: 'Outreach',
417
- states: [PENDING_STATE]
418
+ states: [PENDING_STATE, { stateKey: 'uploaded', label: 'Uploaded' }]
418
419
  },
419
420
  {
420
421
  stageKey: 'prospecting',
@@ -472,6 +473,15 @@ export interface LeadGenStageCatalogEntry {
472
473
  order: number
473
474
  /** Which entity's processing_state jsonb carries this stage status. */
474
475
  entity: 'company' | 'contact'
476
+ /** Additional entities allowed to write/read this processing_state key. */
477
+ additionalEntities?: Array<'company' | 'contact'>
478
+ /**
479
+ * Optional read-side override for Records views when a company-scoped step
480
+ * produces records on a different entity.
481
+ */
482
+ recordEntity?: 'company' | 'contact'
483
+ /** Stage key to read from recordEntity.processing_state for Records views. */
484
+ recordStageKey?: string
475
485
  }
476
486
 
477
487
  /**
@@ -496,6 +506,14 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
496
506
  order: 2,
497
507
  entity: 'company'
498
508
  },
509
+ crawled: {
510
+ key: 'crawled',
511
+ label: 'Websites crawled',
512
+ description:
513
+ 'Company websites have been crawled (e.g. via Apify) and raw page content stored for downstream LLM analysis.',
514
+ order: 2.5,
515
+ entity: 'company'
516
+ },
499
517
  extracted: {
500
518
  key: 'extracted',
501
519
  label: 'Websites analyzed',
@@ -515,7 +533,9 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
515
533
  label: 'Decision-makers found',
516
534
  description: 'Decision-maker contacts discovered and attached to a qualified company.',
517
535
  order: 6,
518
- entity: 'company'
536
+ entity: 'company',
537
+ recordEntity: 'contact',
538
+ recordStageKey: 'discovered'
519
539
  },
520
540
 
521
541
  // Prospecting — contact discovery
@@ -556,7 +576,8 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
556
576
  label: 'Reviewed and exported',
557
577
  description: 'Approved records have been reviewed and exported for handoff.',
558
578
  order: 10,
559
- entity: 'contact'
579
+ entity: 'company',
580
+ additionalEntities: ['contact']
560
581
  },
561
582
  interested: {
562
583
  key: 'interested',
@@ -1,3 +1,3 @@
1
1
  export const VERSION = {
2
- CURRENT: '1.8.6'
2
+ CURRENT: '1.8.7'
3
3
  }
@@ -2549,9 +2549,14 @@ export const AcqSubstrateSchemas = {
2549
2549
 
2550
2550
  // List members
2551
2551
  ListMembersQuery: ListMembersQuerySchema,
2552
+ ListRecordsQuery: ListRecordsQuerySchema,
2552
2553
  MemberIdParams: MemberIdParamsSchema,
2553
2554
  AcqListMemberResponse: AcqListMemberResponseSchema,
2554
2555
  AcqListMembersResponse: AcqListMembersResponseSchema,
2556
+ AcqListCompanyRecordRow: AcqListCompanyRecordRowSchema,
2557
+ AcqListContactRecordRow: AcqListContactRecordRowSchema,
2558
+ ListRecordRow: ListRecordRowSchema,
2559
+ ListRecordsResponse: ListRecordsResponseSchema,
2555
2560
 
2556
2561
  // List companies
2557
2562
  ListCompanyIdParams: ListCompanyIdParamsSchema,
@@ -2709,7 +2714,7 @@ export const ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE: StatefulPipelineDefinition =
2709
2714
  {
2710
2715
  stageKey: 'outreach',
2711
2716
  label: 'Outreach',
2712
- states: [PENDING_STATE]
2717
+ states: [PENDING_STATE, { stateKey: 'uploaded', label: 'Uploaded' }]
2713
2718
  },
2714
2719
  {
2715
2720
  stageKey: 'prospecting',
package/src/server.ts CHANGED
@@ -60,6 +60,7 @@ export { GmailAdapter } from './execution/engine/tools/integration/server/adapte
60
60
  export { AttioAdapter } from './execution/engine/tools/integration/server/adapters/attio'
61
61
  export { GoogleSheetsAdapter } from './execution/engine/tools/integration/server/adapters/google-sheets'
62
62
  export { ApifyAdapter } from './execution/engine/tools/integration/server/adapters/apify'
63
+ export { ApolloAdapter } from './execution/engine/tools/integration/server/adapters/apollo'
63
64
  export { InstantlyAdapter } from './execution/engine/tools/integration/server/adapters/instantly'
64
65
  export { ResendAdapter } from './execution/engine/tools/integration/server/adapters/resend'
65
66
  export { AnymailfinderAdapter } from './execution/engine/tools/integration/server/adapters/anymailfinder'
@@ -68,6 +69,7 @@ export { MillionVerifierAdapter } from './execution/engine/tools/integration/ser
68
69
  export { StripeAdapter } from './execution/engine/tools/integration/server/adapters/stripe'
69
70
  export { SignatureApiAdapter } from './execution/engine/tools/integration/server/adapters/signature-api'
70
71
  export { DropboxAdapter } from './execution/engine/tools/integration/server/adapters/dropbox'
72
+ export { ClickUpAdapter } from './execution/engine/tools/integration/server/adapters/clickup'
71
73
  export {
72
74
  GoogleCalendarAdapter,
73
75
  type CalendarEvent,