From 3a015d7b4af934e9cfe54441f16e3f1922154e82 Mon Sep 17 00:00:00 2001 From: Lazare-42 Date: Wed, 8 Apr 2026 11:21:46 +0200 Subject: [PATCH 1/5] [WIP] feat: add meeting-baas-recorder Twenty app with V1 features Recording custom object linked to CalendarEvent and WorkspaceMember. Calendar event triggers for automatic bot scheduling based on per-user recording preferences (all/organizer-only/none). Settings front component with API key status, calendar connection banner, and preference selector. Webhook handler stores completed recordings via REST API, using the bot's extra field to link back to calendar event and workspace member. Co-Authored-By: Claude Opus 4.6 --- .../meeting-baas-recorder/.env.example | 8 + .../meeting-baas-recorder/.gitignore | 2 + .../application.config.ts | 24 + .../meeting-baas-recorder/jest.config.mjs | 23 + .../meeting-baas-recorder/package.json | 29 + .../meeting-baas-recorder/project.json | 46 + .../src/__tests__/setup.ts | 13 + .../calendar-event-on-recording.field.ts | 28 + ...ng-preference-on-workspace-member.field.ts | 42 + .../recordings-on-calendar-event.field.ts | 26 + .../recordings-on-workspace-member.field.ts | 26 + .../workspace-member-on-recording.field.ts | 28 + .../meeting-baas-settings.front-component.tsx | 332 + .../meeting-baas-recorder/src/index.ts | 51 + .../meeting-baas-recorder/src/logger.ts | 37 + .../on-calendar-event-created.ts | 59 + .../on-calendar-event-updated.ts | 86 + .../src/logic-functions/schedule-bot.ts | 104 + .../src/meeting-baas-api-client.ts | 79 + .../recordings.navigation-menu-item.ts | 9 + .../src/objects/index.ts | 13 + .../src/objects/recording.ts | 104 + .../src/receive-recording-webhook.ts | 23 + .../src/roles/default.role.ts | 28 + .../src/twenty-sync-service.ts | 226 + .../meeting-baas-recorder/src/types.ts | 78 + .../meeting-baas-recorder/src/utils.ts | 26 + .../src/views/all-recordings.view.ts | 33 + .../src/views/by-platform.view.ts | 24 + .../src/views/completed-recordings.view.ts | 38 + .../src/webhook-handler.ts | 171 + .../src/webhook-validator.ts | 63 + .../meeting-baas-recorder/tsconfig.json | 27 + .../community/meeting-baas-recorder/yarn.lock | 5413 +++++++++++++++++ 34 files changed, 7319 insertions(+) create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/.env.example create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/.gitignore create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/application.config.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/jest.config.mjs create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/package.json create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/project.json create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/fields/recordings-on-calendar-event.field.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/fields/recordings-on-workspace-member.field.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/index.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/logger.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/objects/index.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/types.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/utils.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/tsconfig.json create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/yarn.lock diff --git a/packages/twenty-apps/community/meeting-baas-recorder/.env.example b/packages/twenty-apps/community/meeting-baas-recorder/.env.example new file mode 100644 index 00000000000..09df550d99c --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/.env.example @@ -0,0 +1,8 @@ +# Meeting BaaS API key (get yours at https://meetingbaas.com) +MEETING_BAAS_API_KEY= + +# Twenty CRM API key +TWENTY_API_KEY= + +# Twenty server URL (default: http://localhost:3000) +SERVER_URL=http://localhost:3000 diff --git a/packages/twenty-apps/community/meeting-baas-recorder/.gitignore b/packages/twenty-apps/community/meeting-baas-recorder/.gitignore new file mode 100644 index 00000000000..4f0e650be6c --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/.gitignore @@ -0,0 +1,2 @@ +.yarn/install-state.gz +.env diff --git a/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts b/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts new file mode 100644 index 00000000000..d96d7f7f82c --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts @@ -0,0 +1,24 @@ +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_ID } from './src/roles/default.role'; + +export default defineApplication({ + universalIdentifier: 'b7e3a1f2-8d4c-4e6a-9f2b-1c5d7e8a3b4f', + displayName: 'Meeting BaaS Recorder', + description: 'Record meetings via Meeting BaaS and sync recordings, transcripts, and participants into Twenty.', + icon: 'IconVideo', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_ID, + settingsCustomTabFrontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b', + applicationVariables: { + MEETING_BAAS_API_KEY: { + universalIdentifier: 'c1d2e3f4-5a6b-7c8d-9e0f-a1b2c3d4e5f6', + description: 'Meeting BaaS API key for authenticating requests and verifying webhooks', + isSecret: true, + value: '', + }, + AUTO_CREATE_CONTACTS: { + universalIdentifier: 'f4a5b6c7-8d9e-0f1a-2b3c-d4e5f6a7b8c9', + description: 'Whether to auto-create contacts for unknown participants (true/false)', + value: 'true', + }, + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/jest.config.mjs b/packages/twenty-apps/community/meeting-baas-recorder/jest.config.mjs new file mode 100644 index 00000000000..5acbe35f0d5 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/jest.config.mjs @@ -0,0 +1,23 @@ +const jestConfig = { + displayName: 'meeting-baas-recorder', + preset: 'ts-jest', + testEnvironment: 'node', + moduleFileExtensions: ['ts', 'js'], + transform: { + '^.+\\.ts$': 'ts-jest', + }, + testMatch: [ + '/src/**/__tests__/**/*.(test|spec).{js,ts}', + '/src/**/?(*.)(test|spec).{js,ts}', + ], + setupFilesAfterEnv: [ + '/src/__tests__/setup.ts' + ], + collectCoverageFrom: [ + 'src/**/*.{ts,js}', + '!src/**/*.d.ts', + ], + coverageDirectory: './coverage', +}; + +export default jestConfig; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/package.json b/packages/twenty-apps/community/meeting-baas-recorder/package.json new file mode 100644 index 00000000000..754bcc70af8 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/package.json @@ -0,0 +1,29 @@ +{ + "name": "meeting-baas-recorder", + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": "^24.5.0", + "npm": "please-use-yarn", + "yarn": ">=4.0.2" + }, + "packageManager": "yarn@4.9.2", + "scripts": { + "test": "jest" + }, + "dependencies": { + "@meeting-baas/sdk": "^6.0.5", + "axios": "^1.13.1", +"twenty-sdk": "0.1.3" + }, + "devDependencies": { + "@emotion/styled": "^11.14.0", + "@types/jest": "^29.5.5", + "@types/node": "^24.9.2", + "@types/react": "^18.2.0", + "jest": "^29.7.0", + "react": "^18.2.0", + "ts-jest": "^29.1.1", +"typescript": "^5.9.3" + } +} diff --git a/packages/twenty-apps/community/meeting-baas-recorder/project.json b/packages/twenty-apps/community/meeting-baas-recorder/project.json new file mode 100644 index 00000000000..4edbd267368 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/project.json @@ -0,0 +1,46 @@ +{ + "name": "meeting-baas-recorder", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/twenty-apps/community/meeting-baas-recorder/src", + "projectType": "application", + "tags": [ + "scope:apps" + ], + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": [ + "{workspaceRoot}/coverage/{projectRoot}" + ], + "options": { + "jestConfig": "packages/twenty-apps/community/meeting-baas-recorder/jest.config.mjs", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "coverageReporters": ["text"] + } + } + }, + "typecheck": { + "dependsOn": ["^build"] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/twenty-apps/community/meeting-baas-recorder/**/*.{ts,tsx,js,jsx}" + ] + }, + "configurations": { + "fix": { + "fix": true + } + } + } + } +} diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup.ts new file mode 100644 index 00000000000..c35674aea55 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup.ts @@ -0,0 +1,13 @@ +// Global test setup for Meeting BaaS Recorder + +global.fetch = jest.fn(); + +process.env.MEETING_BAAS_API_KEY = 'test-meeting-baas-api-key'; +process.env.TWENTY_API_KEY = 'test-twenty-api-key'; +process.env.SERVER_URL = 'http://localhost:3000'; +process.env.LOG_LEVEL = 'silent'; +process.env.NODE_ENV = 'test'; + +beforeEach(() => { + jest.clearAllMocks(); +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts new file mode 100644 index 00000000000..571f173a8fc --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts @@ -0,0 +1,28 @@ +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; +import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; + +export const CALENDAR_EVENT_ON_RECORDING_ID = '29fe48d1-7e7d-4253-9fea-0a876c2c116d'; +export const RECORDINGS_ON_CALENDAR_EVENT_ID = '131a78b1-f3c9-4b2e-9808-f9eb64bfb832'; + +export default defineField({ + universalIdentifier: CALENDAR_EVENT_ON_RECORDING_ID, + objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'calendarEvent', + label: 'Calendar Event', + icon: 'IconCalendarEvent', + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: RECORDINGS_ON_CALENDAR_EVENT_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'calendarEventId', + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts new file mode 100644 index 00000000000..c191105d6de --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts @@ -0,0 +1,42 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +export const RECORDING_PREFERENCE_FIELD_ID = 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a'; + +export default defineField({ + universalIdentifier: RECORDING_PREFERENCE_FIELD_ID, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier, + type: FieldType.SELECT, + name: 'recordingPreference', + label: 'Recording Preference', + icon: 'IconVideo', + description: 'Controls automatic meeting recording behavior for this workspace member', + defaultValue: "'RECORD_NONE'", + options: [ + { + id: 'e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b', + color: 'green', + label: 'Record All', + value: 'RECORD_ALL', + position: 1, + }, + { + id: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c', + color: 'yellow', + label: 'Organizer Only', + value: 'RECORD_ORGANIZED', + position: 2, + }, + { + id: 'a3b4c5d6-e7f8-4a9b-0c1d-2e3f4a5b6c7d', + color: 'gray', + label: 'None', + value: 'RECORD_NONE', + position: 3, + }, + ], +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recordings-on-calendar-event.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recordings-on-calendar-event.field.ts new file mode 100644 index 00000000000..5b150ba803b --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recordings-on-calendar-event.field.ts @@ -0,0 +1,26 @@ +import { + defineField, + FieldType, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; +import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; +import { + CALENDAR_EVENT_ON_RECORDING_ID, + RECORDINGS_ON_CALENDAR_EVENT_ID, +} from './calendar-event-on-recording.field'; + +export default defineField({ + universalIdentifier: RECORDINGS_ON_CALENDAR_EVENT_ID, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent.universalIdentifier, + type: FieldType.RELATION, + name: 'recordings', + label: 'Recordings', + icon: 'IconVideo', + relationTargetObjectMetadataUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: CALENDAR_EVENT_ON_RECORDING_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recordings-on-workspace-member.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recordings-on-workspace-member.field.ts new file mode 100644 index 00000000000..da9b6d6cefe --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recordings-on-workspace-member.field.ts @@ -0,0 +1,26 @@ +import { + defineField, + FieldType, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; +import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; +import { + WORKSPACE_MEMBER_ON_RECORDING_ID, + RECORDINGS_ON_WORKSPACE_MEMBER_ID, +} from './workspace-member-on-recording.field'; + +export default defineField({ + universalIdentifier: RECORDINGS_ON_WORKSPACE_MEMBER_ID, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier, + type: FieldType.RELATION, + name: 'recordings', + label: 'Recordings', + icon: 'IconVideo', + relationTargetObjectMetadataUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: WORKSPACE_MEMBER_ON_RECORDING_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts new file mode 100644 index 00000000000..8522ab7e6f3 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts @@ -0,0 +1,28 @@ +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; +import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; + +export const WORKSPACE_MEMBER_ON_RECORDING_ID = '3bb83966-51d6-41e1-9a33-5b53bc6313d9'; +export const RECORDINGS_ON_WORKSPACE_MEMBER_ID = '0fb0eaac-4e8d-4632-a6ad-f76df4fb93ad'; + +export default defineField({ + universalIdentifier: WORKSPACE_MEMBER_ON_RECORDING_ID, + objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'workspaceMember', + label: 'Owner', + icon: 'IconUser', + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: RECORDINGS_ON_WORKSPACE_MEMBER_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'workspaceMemberId', + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx b/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx new file mode 100644 index 00000000000..7fc1559b803 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx @@ -0,0 +1,332 @@ +import styled from '@emotion/styled'; +import { useEffect, useState } from 'react'; +import { defineFrontComponent } from 'twenty-sdk'; + +type RecordingPreference = 'RECORD_ALL' | 'RECORD_ORGANIZED' | 'RECORD_NONE'; + +type WorkspaceMember = { + id: string; + recordingPreference?: RecordingPreference; +}; + +type CalendarChannel = { + id: string; +}; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; +`; + +const StyledSectionTitle = styled.h3` + color: #333; + font-family: 'Inter', sans-serif; + font-size: 15px; + font-weight: 600; + margin: 0 0 4px 0; +`; + +const StyledSectionSubtitle = styled.p` + color: #818181; + font-family: 'Inter', sans-serif; + font-size: 13px; + font-weight: 400; + margin: 0 0 12px 0; +`; + +const StyledCard = styled.div` + align-items: center; + background: #fff; + border: 1px solid #ebebeb; + border-radius: 8px; + display: flex; + gap: 12px; + padding: 16px; +`; + +const StyledIconContainer = styled.div` + align-items: center; + background: #f5f5f5; + border-radius: 8px; + color: #666; + display: flex; + flex-shrink: 0; + height: 40px; + justify-content: center; + width: 40px; +`; + +const StyledTextContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + gap: 2px; + min-width: 0; +`; + +const StyledTitle = styled.span` + color: #333; + font-family: 'Inter', sans-serif; + font-size: 14px; + font-weight: 500; +`; + +const StyledDescription = styled.span` + color: #818181; + font-family: 'Inter', sans-serif; + font-size: 13px; +`; + +const StyledStatusBadge = styled.span<{ connected: boolean }>` + align-items: center; + background: ${({ connected }) => (connected ? '#10b981' : '#ef4444')}; + border-radius: 4px; + color: #fff; + display: inline-flex; + flex-shrink: 0; + font-family: 'Inter', sans-serif; + font-size: 13px; + font-weight: 500; + height: 32px; + padding: 0 12px; + white-space: nowrap; +`; + +const StyledRadioGroup = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const StyledRadioLabel = styled.label<{ selected: boolean }>` + align-items: center; + background: ${({ selected }) => (selected ? '#f0f0ff' : '#fff')}; + border: 1px solid ${({ selected }) => (selected ? '#5e5adb' : '#ebebeb')}; + border-radius: 8px; + cursor: pointer; + display: flex; + gap: 12px; + padding: 12px 16px; + transition: all 0.15s ease; + + &:hover { + border-color: #5e5adb; + } +`; + +const StyledRadioInput = styled.input` + accent-color: #5e5adb; + cursor: pointer; + height: 16px; + margin: 0; + width: 16px; +`; + +const StyledRadioTextContainer = styled.div` + display: flex; + flex-direction: column; + gap: 2px; +`; + +const StyledRadioTitle = styled.span` + color: #333; + font-family: 'Inter', sans-serif; + font-size: 14px; + font-weight: 500; +`; + +const StyledRadioDescription = styled.span` + color: #818181; + font-family: 'Inter', sans-serif; + font-size: 12px; +`; + +const StyledBanner = styled.div<{ variant: 'info' | 'warning' }>` + align-items: center; + background: ${({ variant }) => (variant === 'warning' ? '#fef3c7' : '#eff6ff')}; + border: 1px solid ${({ variant }) => (variant === 'warning' ? '#f59e0b' : '#3b82f6')}; + border-radius: 8px; + color: ${({ variant }) => (variant === 'warning' ? '#92400e' : '#1e40af')}; + display: flex; + font-family: 'Inter', sans-serif; + font-size: 13px; + gap: 8px; + padding: 12px 16px; +`; + +const PREFERENCE_OPTIONS: Array<{ + value: RecordingPreference; + title: string; + description: string; +}> = [ + { + value: 'RECORD_ALL', + title: 'Record all meetings', + description: 'Automatically record every meeting with a conference link', + }, + { + value: 'RECORD_ORGANIZED', + title: 'Organizer only', + description: 'Only record meetings you organized', + }, + { + value: 'RECORD_NONE', + title: 'Do not record', + description: 'No automatic recording — you can still record manually', + }, +]; + +const fetchCurrentWorkspaceMember = async (): Promise => { + const response = await fetch(`${process.env.TWENTY_API_URL}/rest/currentWorkspaceMember`, { + headers: { Authorization: `Bearer ${process.env.TWENTY_API_KEY}` }, + }); + const data = await response.json(); + return data?.data ?? data; +}; + +const updateWorkspaceMember = async ( + memberId: string, + recordingPreference: RecordingPreference, +): Promise => { + await fetch(`${process.env.TWENTY_API_URL}/rest/workspaceMembers/${memberId}`, { + method: 'PATCH', + headers: { + Authorization: `Bearer ${process.env.TWENTY_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ recordingPreference }), + }); +}; + +const fetchCalendarChannels = async (): Promise => { + const response = await fetch(`${process.env.TWENTY_API_URL}/rest/calendarChannels?limit=1`, { + headers: { Authorization: `Bearer ${process.env.TWENTY_API_KEY}` }, + }); + const data = await response.json(); + return data?.data?.calendarChannels ?? []; +}; + +const MeetingBaasSettings = () => { + const [member, setMember] = useState(null); + const [preference, setPreference] = useState('RECORD_NONE'); + const [hasCalendar, setHasCalendar] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSaving, setIsSaving] = useState(false); + + const apiKeyConfigured = Boolean(process.env.MEETING_BAAS_API_KEY); + + useEffect(() => { + Promise.all([fetchCurrentWorkspaceMember(), fetchCalendarChannels()]) + .then(([memberData, channels]) => { + setMember(memberData); + setPreference(memberData.recordingPreference ?? 'RECORD_NONE'); + setHasCalendar(channels.length > 0); + }) + .catch(() => { + // Non-fatal: show defaults + }) + .finally(() => setIsLoading(false)); + }, []); + + const handlePreferenceChange = async (newPreference: RecordingPreference) => { + if (!member || isSaving) return; + setIsSaving(true); + setPreference(newPreference); + try { + await updateWorkspaceMember(member.id, newPreference); + } catch { + setPreference(member.recordingPreference ?? 'RECORD_NONE'); + } finally { + setIsSaving(false); + } + }; + + if (isLoading) { + return ( + + Meeting Recording + Loading settings... + + ); + } + + return ( + + {/* API Key Status */} +
+ Meeting BaaS Connection + + Meeting BaaS records your meetings and syncs transcripts into Twenty + + + + 🎥 + + + API Key + + {apiKeyConfigured + ? 'Your Meeting BaaS API key is configured' + : 'No API key configured — add MEETING_BAAS_API_KEY in app variables'} + + + + {apiKeyConfigured ? '✓ Connected' : '✗ Not Set'} + + +
+ + {/* Calendar Connection Banner */} + {hasCalendar === false && ( + + No calendar connected — connect your Google or Microsoft calendar in Settings → Accounts + to enable automatic meeting recording. + + )} + + {/* Recording Preference */} +
+ Recording Preference + + Choose which meetings are automatically recorded when they have a conference link + + + {PREFERENCE_OPTIONS.map((option) => ( + + handlePreferenceChange(option.value)} + disabled={isSaving} + /> + + {option.title} + {option.description} + + + ))} + +
+ + {preference !== 'RECORD_NONE' && !apiKeyConfigured && ( + + Recording is enabled but no API key is set. Add MEETING_BAAS_API_KEY in the app's + variables to start recording. + + )} +
+ ); +}; + +export const SETTINGS_FRONT_COMPONENT_ID = 'e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b'; + +export default defineFrontComponent({ + universalIdentifier: SETTINGS_FRONT_COMPONENT_ID, + name: 'meeting-baas-settings', + description: 'Settings panel for Meeting BaaS recording preferences and connection status', + component: MeetingBaasSettings, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts new file mode 100644 index 00000000000..3750c4114e3 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts @@ -0,0 +1,51 @@ +// SDK entity definitions (discovered automatically by twenty app build) +export { default as RecordingObject, RECORDING_UNIVERSAL_IDENTIFIER } from './objects/recording'; + +// Field on standard object +export { default as RecordingPreferenceField } from './fields/recording-preference-on-workspace-member.field'; + +// Front component +export { default as MeetingBaasSettingsComponent } from './front-components/meeting-baas-settings.front-component'; + +// Logic functions (database event triggers) +export { default as OnCalendarEventCreated } from './logic-functions/on-calendar-event-created'; +export { default as OnCalendarEventUpdated } from './logic-functions/on-calendar-event-updated'; + +// Types +export { WebhookEvent } from './types'; +export type { + BotWebhookCompleted, + BotWebhookCompletedData, + BotWebhookFailed, + BotWebhookFailedData, + BotWebhookStatusChange, + CalendarEventOwnership, + MeetingBaasWebhookPayload, + MeetingPlatform, + ProcessResult, + RecordingData, + RecordingUpsertInput, + SyncResult, +} from './types'; + +// Services +export { MeetingBaasApiClient } from './meeting-baas-api-client'; +export { WebhookHandler } from './webhook-handler'; + +// Sync service +export { + checkIfRecordingExists, + detectPlatform, + resolveCalendarEventOwner, + syncBotRecording, + upsertRecording, +} from './twenty-sync-service'; + +// Utilities +export { createLogger } from './logger'; +export { getApiUrl, getRestApiUrl, restHeaders } from './utils'; +export { + getApiKeyFingerprint, + isValidMeetingBaasPayload, + verifyWebhookApiKey, +} from './webhook-validator'; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logger.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logger.ts new file mode 100644 index 00000000000..84a693d1f5d --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logger.ts @@ -0,0 +1,37 @@ +export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; + +const LOG_LEVELS: Record = { + debug: 0, + info: 1, + warn: 2, + error: 3, + silent: 4, +}; + +const parseLogLevel = (level: string): LogLevel => { + const normalized = level.toLowerCase() as LogLevel; + return Object.keys(LOG_LEVELS).includes(normalized) ? normalized : 'error'; +}; + +const currentLevel = parseLogLevel(process.env.LOG_LEVEL || 'error'); + +const shouldLog = (level: LogLevel): boolean => + LOG_LEVELS[level] >= LOG_LEVELS[currentLevel]; + +export const createLogger = (context: string) => ({ + debug: (message: string, ...args: unknown[]) => { + if (shouldLog('debug')) console.log(`[${context}] ${message}`, ...args); + }, + info: (message: string, ...args: unknown[]) => { + if (shouldLog('info')) console.log(`[${context}] ${message}`, ...args); + }, + warn: (message: string, ...args: unknown[]) => { + if (shouldLog('warn')) console.warn(`[${context}] ${message}`, ...args); + }, + error: (message: string, ...args: unknown[]) => { + if (shouldLog('error')) console.error(`[${context}] ${message}`, ...args); + }, + critical: (message: string, ...args: unknown[]) => { + console.error(`[${context}] CRITICAL: ${message}`, ...args); + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts new file mode 100644 index 00000000000..8c04b029260 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts @@ -0,0 +1,59 @@ +import { + defineLogicFunction, + type DatabaseEventPayload, + type ObjectRecordCreateEvent, +} from 'twenty-sdk'; +import { createLogger } from '../logger'; +import { scheduleBot } from './schedule-bot'; + +const logger = createLogger('on-calendar-event-created'); + +type CalendarEvent = { + conferenceLink?: { + primaryLinkUrl?: string; + }; + startsAt?: string; +}; + +type CalendarEventCreatedEvent = DatabaseEventPayload< + ObjectRecordCreateEvent +>; + +const handler = async ( + event: CalendarEventCreatedEvent, +): Promise => { + const { properties, recordId } = event; + const conferenceLink = properties.after?.conferenceLink?.primaryLinkUrl; + const startsAt = properties.after?.startsAt; + + if (!conferenceLink) { + return { skipped: true, reason: 'no conference link' }; + } + + if (!startsAt) { + return { skipped: true, reason: 'no start time' }; + } + + try { + const botId = await scheduleBot(recordId, conferenceLink, startsAt); + if (!botId) { + return { skipped: true, reason: 'bot not scheduled (preference or config)' }; + } + return { scheduled: true, botId, calendarEventId: recordId }; + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + logger.warn(`Failed to schedule bot for calendar event ${recordId}: ${msg}`); + return { error: msg }; + } +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', + name: 'on-calendar-event-created', + description: 'Schedules a Meeting BaaS recording bot when a calendar event with a conference link is created', + timeoutSeconds: 15, + handler, + databaseEventTriggerSettings: { + eventName: 'calendarEvent.created', + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts new file mode 100644 index 00000000000..675a55100e7 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts @@ -0,0 +1,86 @@ +import axios from 'axios'; +import { + defineLogicFunction, + type DatabaseEventPayload, + type ObjectRecordUpdateEvent, +} from 'twenty-sdk'; +import { createLogger } from '../logger'; +import { getRestApiUrl } from '../utils'; +import { scheduleBot } from './schedule-bot'; + +const logger = createLogger('on-calendar-event-updated'); + +type CalendarEvent = { + conferenceLink?: { + primaryLinkUrl?: string; + }; + startsAt?: string; +}; + +type CalendarEventUpdatedEvent = DatabaseEventPayload< + ObjectRecordUpdateEvent +>; + +const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? ''; + +// Check if a recording is already linked to this calendar event +const hasExistingRecording = async (calendarEventId: string): Promise => { + try { + const response = await axios({ + method: 'GET', + headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, + url: `${getRestApiUrl()}/recordings?filter=calendarEventId%5Beq%5D%3A%22${encodeURIComponent(calendarEventId)}%22&limit=1`, + }); + const recordings: Record[] = + response.data?.data?.recordings ?? []; + return recordings.length > 0; + } catch { + return false; + } +}; + +const handler = async ( + event: CalendarEventUpdatedEvent, +): Promise => { + const { properties, recordId } = event; + const conferenceLink = properties.after?.conferenceLink?.primaryLinkUrl; + const startsAt = properties.after?.startsAt; + + if (!conferenceLink) { + return { skipped: true, reason: 'no conference link after update' }; + } + + if (!startsAt) { + return { skipped: true, reason: 'no start time' }; + } + + // Check if a recording or bot is already associated with this event + const alreadyRecorded = await hasExistingRecording(recordId); + if (alreadyRecorded) { + return { skipped: true, reason: 'recording already exists for this calendar event' }; + } + + try { + const botId = await scheduleBot(recordId, conferenceLink, startsAt); + if (!botId) { + return { skipped: true, reason: 'bot not scheduled (preference or config)' }; + } + return { scheduled: true, botId, calendarEventId: recordId }; + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + logger.warn(`Failed to schedule bot for updated calendar event ${recordId}: ${msg}`); + return { error: msg }; + } +}; + +export default defineLogicFunction({ + universalIdentifier: 'b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e', + name: 'on-calendar-event-updated', + description: 'Schedules a Meeting BaaS recording bot when a calendar event gains a conference link', + timeoutSeconds: 15, + handler, + databaseEventTriggerSettings: { + eventName: 'calendarEvent.updated', + updatedFields: ['conferenceLink'], + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts new file mode 100644 index 00000000000..1e283dc6c11 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts @@ -0,0 +1,104 @@ +import axios from 'axios'; +import { MeetingBaasApiClient } from '../meeting-baas-api-client'; +import { resolveCalendarEventOwner } from '../twenty-sync-service'; +import { createLogger } from '../logger'; +import { getRestApiUrl, restHeaders } from '../utils'; + +const logger = createLogger('schedule-bot'); + +type RecordingPreference = 'RECORD_ALL' | 'RECORD_ORGANIZED' | 'RECORD_NONE'; + +const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? ''; + +const fetchWorkspaceMemberPreference = async ( + workspaceMemberId: string, +): Promise => { + try { + const response = await axios({ + method: 'GET', + headers: restHeaders(), + url: `${getRestApiUrl()}/workspaceMembers/${workspaceMemberId}`, + }); + const memberData = response.data?.data ?? response.data; + return (memberData?.recordingPreference as RecordingPreference) ?? 'RECORD_NONE'; + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + logger.warn(`Failed to fetch workspace member preference: ${msg}`); + return 'RECORD_NONE'; + } +}; + +const isOrganizer = async ( + calendarEventId: string, + workspaceMemberId: string, +): Promise => { + try { + const response = await axios({ + method: 'GET', + headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, + url: `${getRestApiUrl()}/calendarEventParticipants?filter=calendarEventId%5Beq%5D%3A%22${encodeURIComponent(calendarEventId)}%22&limit=50`, + }); + const participants: Record[] = + response.data?.data?.calendarEventParticipants ?? []; + + return participants.some( + (p) => + p.isOrganizer === true && + p.workspaceMemberId === workspaceMemberId, + ); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + logger.warn(`Failed to check organizer status: ${msg}`); + return false; + } +}; + +// Shared handler: resolve owner → check preference → check organizer → schedule bot +export const scheduleBot = async ( + calendarEventId: string, + conferenceUrl: string, + startsAt: string, +): Promise => { + const apiKey = process.env.MEETING_BAAS_API_KEY; + if (!apiKey) { + logger.debug('MEETING_BAAS_API_KEY not set, skipping bot scheduling'); + return null; + } + + // Resolve calendar event owner + const ownership = await resolveCalendarEventOwner(calendarEventId); + if (!ownership.workspaceMemberId) { + logger.debug(`No workspace member found for calendar event ${calendarEventId}`); + return null; + } + + // Check recording preference + const preference = await fetchWorkspaceMemberPreference(ownership.workspaceMemberId); + if (preference === 'RECORD_NONE') { + logger.debug(`Workspace member ${ownership.workspaceMemberName ?? ownership.workspaceMemberId} has recording disabled`); + return null; + } + + // If organizer-only, check if the member is the organizer + if (preference === 'RECORD_ORGANIZED') { + const memberIsOrganizer = await isOrganizer(calendarEventId, ownership.workspaceMemberId); + if (!memberIsOrganizer) { + logger.debug(`Workspace member ${ownership.workspaceMemberName ?? ownership.workspaceMemberId} is not the organizer, skipping`); + return null; + } + } + + // Schedule the bot + const client = new MeetingBaasApiClient(apiKey); + const botId = await client.createScheduledBot({ + meetingUrl: conferenceUrl, + joinAt: startsAt, + extra: { + calendarEventId, + workspaceMemberId: ownership.workspaceMemberId, + }, + }); + + logger.debug(`Scheduled bot ${botId} for calendar event ${calendarEventId} (${conferenceUrl})`); + return botId; +}; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts new file mode 100644 index 00000000000..3e8936c9efa --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts @@ -0,0 +1,79 @@ +import { createBaasClient, type BaasClient, type V2 } from '@meeting-baas/sdk'; +import { createLogger } from './logger'; +import type { MeetingPlatform, RecordingData } from './types'; +import { detectPlatform } from './twenty-sync-service'; + +const logger = createLogger('meeting-baas-api'); + +export class MeetingBaasApiClient { + private client: BaasClient<'v2'>; + + constructor(apiKey: string) { + if (!apiKey) { + logger.critical('MEETING_BAAS_API_KEY is required but not provided'); + throw new Error('MEETING_BAAS_API_KEY is required'); + } + this.client = createBaasClient({ + api_key: apiKey, + api_version: 'v2', + timeout: 60000, + }) as BaasClient<'v2'>; + } + + // Create a scheduled bot to join a meeting at a specific time + async createScheduledBot(options: { + meetingUrl: string; + joinAt: string; + botName?: string; + recordingMode?: 'speaker_view' | 'gallery_view' | 'audio_only'; + extra?: Record; + callbackUrl?: string; + }): Promise { + logger.debug(`scheduling bot for meeting: ${options.meetingUrl} at ${options.joinAt}`); + + const result = await this.client.createScheduledBot({ + meeting_url: options.meetingUrl, + join_at: options.joinAt, + bot_name: options.botName || 'Twenty CRM Recorder', + ...(options.recordingMode && { recording_mode: options.recordingMode }), + ...(options.extra && { extra: options.extra }), + ...(options.callbackUrl && { + callback_enabled: true, + callback_config: { url: options.callbackUrl }, + }), + }); + + if (!result.success) { + const errorInfo = 'code' in result ? ` (${result.code})` : ''; + throw new Error(`Meeting BaaS API error${errorInfo}: ${result.error}`); + } + + const botId = result.data.bot_id; + logger.debug(`scheduled bot created with id: ${botId}`); + return botId; + } + + // Transform V2 bot.completed webhook data into normalized RecordingData + transformWebhookData( + data: V2.BotWebhookCompletedData, + extra?: Record | null, + ): RecordingData { + const duration = data.duration_seconds ?? 0; + const extraData = extra ?? {}; + const meetingUrl = (extraData.meeting_url as string) || ''; + const title = (extraData.meeting_title as string) || `Recording ${new Date().toLocaleDateString()}`; + const platform: MeetingPlatform = detectPlatform(meetingUrl); + + return { + botId: data.bot_id, + title, + date: data.joined_at || new Date().toISOString(), + duration, + transcript: '', + mp4Url: data.video || '', + meetingUrl, + platform, + extra: extraData, + }; + } +} diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts new file mode 100644 index 00000000000..300b31d72bb --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts @@ -0,0 +1,9 @@ +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; + +export default defineNavigationMenuItem({ + universalIdentifier: 'a569ca64-99b6-4334-a51e-d5fc59e8da57', + position: 0, + type: NavigationMenuItemType.OBJECT, + targetObjectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/objects/index.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/objects/index.ts new file mode 100644 index 00000000000..ed9d9b6acb2 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/objects/index.ts @@ -0,0 +1,13 @@ +export { + default as RecordingObject, + RECORDING_UNIVERSAL_IDENTIFIER, + NAME_FIELD_ID, + BOT_ID_FIELD_ID, + DATE_FIELD_ID, + DURATION_FIELD_ID, + TRANSCRIPT_FIELD_ID, + MEETING_URL_FIELD_ID, + MP4_URL_FIELD_ID, + PLATFORM_FIELD_ID, + STATUS_FIELD_ID, +} from './recording'; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts new file mode 100644 index 00000000000..15de141c1ce --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts @@ -0,0 +1,104 @@ +import { defineObject, FieldType } from 'twenty-sdk'; + +export const RECORDING_UNIVERSAL_IDENTIFIER = + 'a8b9c0d1-2e3f-4a5b-6c7d-8e9f0a1b2c3d'; + +// Field UUIDs — exported for use in views, relations, and page layouts +export const NAME_FIELD_ID = 'b6209c31-8f6a-453e-8291-41191c0394ae'; +export const BOT_ID_FIELD_ID = '68967b34-482f-47c2-8d81-7fa1c824bd82'; +export const DATE_FIELD_ID = '74bd2089-d5e2-4488-97fb-017ea8e0d14a'; +export const DURATION_FIELD_ID = '2ee873e5-60eb-40b1-9a0f-b10d7d48f297'; +export const TRANSCRIPT_FIELD_ID = '06e9adf7-392d-44a0-abc9-ef5c1e81bdd7'; +export const MEETING_URL_FIELD_ID = 'ad56795c-49ac-4075-b099-42e938d9e36e'; +export const MP4_URL_FIELD_ID = '00ecab81-954c-47c4-a263-96fd5ccbc39d'; +export const PLATFORM_FIELD_ID = 'eaa24930-508b-4e63-80c3-cc607d8e2845'; +export const STATUS_FIELD_ID = '6fce2fe2-169e-4780-9fc4-d330d3cd39bc'; + +export default defineObject({ + universalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + nameSingular: 'recording', + namePlural: 'recordings', + labelSingular: 'Recording', + labelPlural: 'Recordings', + description: 'Meeting recordings from Meeting BaaS with transcripts and media.', + icon: 'IconVideo', + labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_ID, + fields: [ + { + universalIdentifier: NAME_FIELD_ID, + type: FieldType.TEXT, + label: 'Name', + name: 'name', + icon: 'IconAbc', + }, + { + universalIdentifier: BOT_ID_FIELD_ID, + type: FieldType.TEXT, + label: 'Bot ID', + name: 'botId', + icon: 'IconKey', + }, + { + universalIdentifier: DATE_FIELD_ID, + type: FieldType.DATE_TIME, + label: 'Date', + name: 'date', + icon: 'IconCalendar', + isNullable: true, + defaultValue: null, + }, + { + universalIdentifier: DURATION_FIELD_ID, + type: FieldType.NUMBER, + label: 'Duration (min)', + name: 'duration', + icon: 'IconClock', + }, + { + universalIdentifier: TRANSCRIPT_FIELD_ID, + type: FieldType.TEXT, + label: 'Transcript', + name: 'transcript', + icon: 'IconFileText', + }, + { + universalIdentifier: MEETING_URL_FIELD_ID, + type: FieldType.LINKS, + label: 'Meeting URL', + name: 'meetingUrl', + icon: 'IconLink', + }, + { + universalIdentifier: MP4_URL_FIELD_ID, + type: FieldType.LINKS, + label: 'Video Recording', + name: 'mp4Url', + icon: 'IconVideo', + }, + { + universalIdentifier: PLATFORM_FIELD_ID, + type: FieldType.SELECT, + label: 'Platform', + name: 'platform', + icon: 'IconDevices', + options: [ + { id: 'b3521d94-c929-45b0-8573-26567e6dfc85', value: 'GOOGLE_MEET', label: 'Google Meet', position: 0, color: 'blue' }, + { id: 'd517462f-8737-4a71-b1bd-bca56a82a1fa', value: 'ZOOM', label: 'Zoom', position: 1, color: 'sky' }, + { id: '3e8c1061-6b12-4f2d-8e52-3bf4667dae01', value: 'MICROSOFT_TEAMS', label: 'Microsoft Teams', position: 2, color: 'purple' }, + { id: 'd0d8114f-811c-4d14-8c6d-fea5283e2b61', value: 'UNKNOWN', label: 'Unknown', position: 3, color: 'gray' }, + ], + }, + { + universalIdentifier: STATUS_FIELD_ID, + type: FieldType.SELECT, + label: 'Status', + name: 'status', + icon: 'IconStatusChange', + options: [ + { id: 'e127d867-4931-4e15-a5be-971caa8678a3', value: 'COMPLETED', label: 'Completed', position: 0, color: 'green' }, + { id: '776885a7-8a42-415c-92fe-e08f5a1274ea', value: 'FAILED', label: 'Failed', position: 1, color: 'red' }, + { id: '12374b43-a6fe-48d6-8a71-437242e59df2', value: 'IN_PROGRESS', label: 'In Progress', position: 2, color: 'orange' }, + ], + }, + ], +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts new file mode 100644 index 00000000000..5f9c57af5fb --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts @@ -0,0 +1,23 @@ +import { defineLogicFunction } from 'twenty-sdk'; +import type { ProcessResult } from './types'; +import { WebhookHandler } from './webhook-handler'; + +export default defineLogicFunction({ + universalIdentifier: '3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d', + name: 'receive-recording-webhook', + description: + 'Receives Meeting BaaS webhooks when recordings complete, and stores them in Twenty.', + timeoutSeconds: 30, + httpRouteTriggerSettings: { + path: '/webhook/meeting-baas', + httpMethod: 'POST', + isAuthRequired: false, + }, + handler: async ( + params: unknown, + headers?: Record, + ): Promise => { + const handler = new WebhookHandler(); + return handler.handle(params, headers); + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts new file mode 100644 index 00000000000..1aff14dcbcc --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts @@ -0,0 +1,28 @@ +import { defineRole, PermissionFlag } from 'twenty-sdk'; +import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; + +export const DEFAULT_ROLE_ID = '3231cf40-5b90-4c2b-ae41-fcb5606299b4'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_ID, + label: 'Meeting BaaS Recorder role', + description: 'Default role for the Meeting BaaS Recorder app', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: true, + canDestroyObjectRecords: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts new file mode 100644 index 00000000000..31cc6ecce79 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts @@ -0,0 +1,226 @@ +import axios from 'axios'; +import type { + CalendarEventOwnership, + MeetingPlatform, + RecordingUpsertInput, + SyncResult, +} from './types'; +import { getRestApiUrl, restHeaders } from './utils'; + +const TWENTY_API_KEY: string = process.env.TWENTY_API_KEY ?? ''; + +// --- Platform Detection --- + +export const detectPlatform = (meetingUrl: string): MeetingPlatform => { + if (!meetingUrl) return 'UNKNOWN'; + const url = meetingUrl.toLowerCase(); + if (url.includes('meet.google.com')) return 'GOOGLE_MEET'; + if (url.includes('zoom.us') || url.includes('zoom.com')) return 'ZOOM'; + if (url.includes('teams.microsoft.com') || url.includes('teams.live.com')) + return 'MICROSOFT_TEAMS'; + return 'UNKNOWN'; +}; + +// --- Calendar event ownership resolution --- +// Walk CalendarEvent -> CalendarChannelEventAssociation -> CalendarChannel -> ConnectedAccount -> WorkspaceMember + +const ownershipCache = new Map(); + +export const resolveCalendarEventOwner = async ( + calendarEventId: string, +): Promise => { + const cached = ownershipCache.get(calendarEventId); + if (cached) return cached; + + const result: CalendarEventOwnership = {}; + + try { + // 1. CalendarEvent -> CalendarChannelEventAssociation -> calendarChannelId + const assocResponse = await axios({ + method: 'GET', + headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, + url: `${getRestApiUrl()}/calendarChannelEventAssociations?filter=calendarEventId%5Beq%5D%3A%22${encodeURIComponent(calendarEventId)}%22&limit=1`, + }); + + const associations: Record[] = + assocResponse.data?.data?.calendarChannelEventAssociations ?? []; + if (associations.length === 0) { + ownershipCache.set(calendarEventId, result); + return result; + } + + const calendarChannelId = associations[0].calendarChannelId as string | undefined; + if (!calendarChannelId) { + ownershipCache.set(calendarEventId, result); + return result; + } + + // 2. CalendarChannel -> connectedAccountId + const channelResponse = await axios({ + method: 'GET', + headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, + url: `${getRestApiUrl()}/calendarChannels/${calendarChannelId}`, + }); + + const channelData = channelResponse.data?.data ?? channelResponse.data; + const connectedAccountId = channelData?.connectedAccountId as string | undefined; + if (!connectedAccountId) { + ownershipCache.set(calendarEventId, result); + return result; + } + + // 3. ConnectedAccount -> accountOwnerId (= workspaceMemberId) + const accountResponse = await axios({ + method: 'GET', + headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, + url: `${getRestApiUrl()}/connectedAccounts/${connectedAccountId}`, + }); + + const accountData = accountResponse.data?.data ?? accountResponse.data; + const workspaceMemberId = accountData?.accountOwnerId as string | undefined; + if (!workspaceMemberId) { + ownershipCache.set(calendarEventId, result); + return result; + } + result.workspaceMemberId = workspaceMemberId; + + // 4. WorkspaceMember -> display name + try { + const memberResponse = await axios({ + method: 'GET', + headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, + url: `${getRestApiUrl()}/workspaceMembers/${workspaceMemberId}`, + }); + + const memberData = memberResponse.data?.data ?? memberResponse.data; + const firstName = (memberData?.name?.firstName as string) ?? ''; + const lastName = (memberData?.name?.lastName as string) ?? ''; + const fullName = [firstName, lastName].filter(Boolean).join(' '); + if (fullName) result.workspaceMemberName = fullName; + } catch { + // Non-fatal: we still have the workspaceMemberId + } + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + console.warn(`Ownership resolution failed for calendarEvent ${calendarEventId}: ${msg}`); + } + + ownershipCache.set(calendarEventId, result); + return result; +}; + +// --- Recording CRUD --- + +export const checkIfRecordingExists = async ( + botId: string, +): Promise => { + try { + const response = await axios({ + method: 'GET', + headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, + url: `${getRestApiUrl()}/recordings?filter=botId%5Beq%5D%3A%22${encodeURIComponent(botId)}%22`, + }); + const recording = response.data?.data?.recordings?.[0]; + return recording?.id ?? null; + } catch { + return null; + } +}; + +export const upsertRecording = async ( + opts: RecordingUpsertInput, +): Promise => { + const existingId = await checkIfRecordingExists(opts.botId); + + const data: Record = { + name: opts.name, + botId: opts.botId, + date: opts.date, + duration: opts.duration, + platform: opts.platform, + status: opts.status, + transcript: opts.transcript, + }; + if (opts.meetingUrl) data.meetingUrl = opts.meetingUrl; + if (opts.mp4Url) data.mp4Url = opts.mp4Url; + if (opts.calendarEventId) data.calendarEventId = opts.calendarEventId; + if (opts.workspaceMemberId) data.workspaceMemberId = opts.workspaceMemberId; + + if (existingId) { + await axios({ + method: 'PATCH', + headers: restHeaders(), + url: `${getRestApiUrl()}/recordings/${existingId}`, + data, + }); + return existingId; + } + + const response = await axios({ + method: 'POST', + headers: restHeaders(), + url: `${getRestApiUrl()}/recordings`, + data, + }); + const id = response.data?.data?.id ?? response.data?.id; + return id ? (id as string) : null; +}; + +// --- Sync orchestration --- +// calendarEventId and workspaceMemberId come from the bot's `extra` field, +// set by schedule-bot.ts when creating the scheduled bot. + +export const syncBotRecording = async ( + recordingData: { + botId: string; + title: string; + date: string; + duration: number; + transcript: string; + mp4Url: string; + meetingUrl: string; + platform: MeetingPlatform; + calendarEventId?: string; + workspaceMemberId?: string; + }, + result: SyncResult, +): Promise => { + try { + const durationMinutes = Math.round(recordingData.duration / 60); + + const recordingId = await upsertRecording({ + botId: recordingData.botId, + name: recordingData.title, + date: recordingData.date, + duration: durationMinutes, + platform: recordingData.platform, + status: 'COMPLETED', + meetingUrl: recordingData.meetingUrl + ? { primaryLinkLabel: 'Join Meeting', primaryLinkUrl: recordingData.meetingUrl, secondaryLinks: null } + : null, + mp4Url: recordingData.mp4Url + ? { primaryLinkLabel: 'Watch Recording', primaryLinkUrl: recordingData.mp4Url, secondaryLinks: null } + : null, + transcript: recordingData.transcript, + calendarEventId: recordingData.calendarEventId, + workspaceMemberId: recordingData.workspaceMemberId, + }); + + if (recordingId) { + result.recordingsCreated++; + } + result.recordingsProcessed++; + + return recordingId; + } catch (error) { + let errorMessage: string; + if (axios.isAxiosError(error) && error.response) { + errorMessage = `${error.response.status}: ${JSON.stringify(error.response.data)}`; + } else { + errorMessage = error instanceof Error ? error.message : String(error); + } + result.errors.push({ botId: recordingData.botId, error: errorMessage }); + console.error(`Error syncing recording ${recordingData.botId}: ${errorMessage}`); + return null; + } +}; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/types.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/types.ts new file mode 100644 index 00000000000..e5f82fdcbcd --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/types.ts @@ -0,0 +1,78 @@ +// SDK V2 types and runtime constants +import type { V2 } from '@meeting-baas/sdk'; + +export type BotWebhookCompleted = V2.BotWebhookCompleted; +export type BotWebhookCompletedData = V2.BotWebhookCompletedData; +export type BotWebhookFailed = V2.BotWebhookFailed; +export type BotWebhookFailedData = V2.BotWebhookFailedData; +export type BotWebhookStatusChange = V2.BotWebhookStatusChange; + +// Union of all webhook payloads we handle +export type MeetingBaasWebhookPayload = + | BotWebhookCompleted + | BotWebhookFailed + | BotWebhookStatusChange; + +// Webhook event discriminants derived from SDK types +export const WebhookEvent = { + COMPLETED: 'bot.completed' as BotWebhookCompleted['event'], + FAILED: 'bot.failed' as BotWebhookFailed['event'], + STATUS_CHANGE: 'bot.status_change' as BotWebhookStatusChange['event'], +} as const; + +// Meeting platform types +export type MeetingPlatform = + | 'GOOGLE_MEET' + | 'ZOOM' + | 'MICROSOFT_TEAMS' + | 'UNKNOWN'; + +// Normalized recording data from Meeting BaaS +export type RecordingData = { + botId: string; + title: string; + date: string; + duration: number; + transcript: string; + mp4Url: string; + meetingUrl: string; + platform: MeetingPlatform; + extra: Record; +}; + +// Webhook handler result +export type ProcessResult = { + success: boolean; + recordingId?: string; + errors?: string[]; + durationMinutes?: number; +}; + +// Recording upsert input for the REST API +export type RecordingUpsertInput = { + botId: string; + name: string; + date: string; + duration: number; + platform: MeetingPlatform; + status: 'COMPLETED' | 'FAILED' | 'IN_PROGRESS'; + meetingUrl: { primaryLinkLabel: string; primaryLinkUrl: string; secondaryLinks: null } | null; + mp4Url: { primaryLinkLabel: string; primaryLinkUrl: string; secondaryLinks: null } | null; + transcript: string; + calendarEventId?: string; + workspaceMemberId?: string; +}; + +// Sync result +export type SyncResult = { + recordingsProcessed: number; + recordingsCreated: number; + recordingsUpdated: number; + errors: { botId: string; error: string }[]; +}; + +// Ownership chain resolution result +export type CalendarEventOwnership = { + workspaceMemberId?: string; + workspaceMemberName?: string; +}; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/utils.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/utils.ts new file mode 100644 index 00000000000..84ea92a0232 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/utils.ts @@ -0,0 +1,26 @@ +export const getApiUrl = (): string => { + return process.env.TWENTY_API_URL || process.env.SERVER_URL || 'https://api.twenty.com'; +}; + +export const getRestApiUrl = (): string => { + return `${getApiUrl()}/rest`; +}; + +export const restHeaders = (): Record => ({ + Authorization: `Bearer ${process.env.TWENTY_API_KEY ?? ''}`, + 'Content-Type': 'application/json', +}); + +export const formatDuration = (seconds: number): string => { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = Math.floor(seconds % 60); + + if (hours > 0) { + return `${hours}h ${minutes}m ${secs}s`; + } + if (minutes > 0) { + return `${minutes}m ${secs}s`; + } + return `${secs}s`; +}; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts new file mode 100644 index 00000000000..220c42746d4 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts @@ -0,0 +1,33 @@ +import { defineView } from 'twenty-sdk'; +import { ViewType } from 'twenty-shared/types'; +import { + RECORDING_UNIVERSAL_IDENTIFIER, + NAME_FIELD_ID, + DATE_FIELD_ID, + DURATION_FIELD_ID, + PLATFORM_FIELD_ID, + STATUS_FIELD_ID, + MEETING_URL_FIELD_ID, + MP4_URL_FIELD_ID, +} from '../objects/recording'; + +export const ALL_RECORDINGS_VIEW_ID = 'bd6347b3-3b8a-4095-a0bb-24220b5040bd'; + +export default defineView({ + universalIdentifier: ALL_RECORDINGS_VIEW_ID, + name: 'All Recordings', + objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + type: ViewType.TABLE, + key: 'INDEX', + icon: 'IconVideo', + position: 0, + fields: [ + { universalIdentifier: 'e75cd4e3-e42e-43c7-bd94-532f2766ea11', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, + { universalIdentifier: '30411458-e7bc-463f-8308-a9c0c947122c', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, + { universalIdentifier: '74be7ee1-10f5-49a1-9af8-86174a2d037f', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, + { universalIdentifier: '83fc4647-d08a-400e-840e-01f3b9cbe51e', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, + { universalIdentifier: '59f601ca-c442-4feb-a803-476eff07d534', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, + { universalIdentifier: '3becc98e-ee5f-479a-a947-9e7db0412e8b', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, + { universalIdentifier: 'f72a4775-4443-467f-ad0b-8e31323bc322', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, + ], +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts new file mode 100644 index 00000000000..ddc03da598b --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts @@ -0,0 +1,24 @@ +import { defineView } from 'twenty-sdk'; +import { ViewType } from 'twenty-shared/types'; +import { + RECORDING_UNIVERSAL_IDENTIFIER, + PLATFORM_FIELD_ID, +} from '../objects/recording'; + +export default defineView({ + universalIdentifier: '9d3114e6-3ba9-4e31-8197-45e510b69c4c', + name: 'By Platform', + objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + type: ViewType.KANBAN, + icon: 'IconLayoutKanban', + position: 2, + groups: [ + { universalIdentifier: '7a0144b1-6d0e-453d-8cda-cf1fa63d3b77', fieldValue: 'GOOGLE_MEET', isVisible: true, position: 0 }, + { universalIdentifier: '8ed53363-962d-4136-b34c-fae2eae7d3f9', fieldValue: 'ZOOM', isVisible: true, position: 1 }, + { universalIdentifier: '7ab9b893-6f97-4973-a4a9-2295f69f5d6d', fieldValue: 'MICROSOFT_TEAMS', isVisible: true, position: 2 }, + { universalIdentifier: '3e82c80b-66db-4be2-8043-552ee309d26c', fieldValue: 'UNKNOWN', isVisible: true, position: 3 }, + ], + fields: [ + { universalIdentifier: 'a578e600-97ec-4cd3-84fe-b254c3f89130', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 0, isVisible: true, size: 150 }, + ], +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts new file mode 100644 index 00000000000..800a5f523b7 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts @@ -0,0 +1,38 @@ +import { defineView } from 'twenty-sdk'; +import { ViewType, ViewFilterOperand } from 'twenty-shared/types'; +import { + RECORDING_UNIVERSAL_IDENTIFIER, + NAME_FIELD_ID, + DATE_FIELD_ID, + DURATION_FIELD_ID, + PLATFORM_FIELD_ID, + STATUS_FIELD_ID, + MEETING_URL_FIELD_ID, + MP4_URL_FIELD_ID, +} from '../objects/recording'; + +export default defineView({ + universalIdentifier: 'd6325047-8ba7-4976-a732-4e280db36dfa', + name: 'Completed Recordings', + objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, + type: ViewType.TABLE, + icon: 'IconCheck', + position: 1, + fields: [ + { universalIdentifier: 'b953f475-0eb7-4864-b3e8-d2c4e8b02a3b', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, + { universalIdentifier: 'b3bd42e6-05cd-4c3a-bfad-726ef75a0c15', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, + { universalIdentifier: 'e15d29e8-468a-4b8f-93cb-a71d89f01d45', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, + { universalIdentifier: 'c2b37ddf-f16f-4b5f-b443-c6b5214eb4b4', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, + { universalIdentifier: '489703b0-1cd1-4422-98d8-6d8d32063821', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, + { universalIdentifier: '76d8a2c2-259f-49cc-8921-6ebad06ac276', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, + { universalIdentifier: '4d31d04c-f90e-413a-9c67-38cb6a3594a3', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, + ], + filters: [ + { + universalIdentifier: 'ee754547-ed53-4d9e-8155-fbf58d8ed016', + fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, + operand: ViewFilterOperand.IS, + value: 'COMPLETED', + }, + ], +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts new file mode 100644 index 00000000000..8cce9d18d76 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts @@ -0,0 +1,171 @@ +import { createLogger } from './logger'; +import { MeetingBaasApiClient } from './meeting-baas-api-client'; +import { WebhookEvent, type MeetingBaasWebhookPayload, type ProcessResult, type SyncResult } from './types'; +import { + getApiKeyFingerprint, + isValidMeetingBaasPayload, + verifyWebhookApiKey +} from './webhook-validator'; +import { syncBotRecording } from './twenty-sync-service'; + +declare const process: { env: Record }; + +export class WebhookHandler { + private logger: ReturnType; + + constructor() { + this.logger = createLogger('meeting-baas'); + } + + async handle(params: unknown, headers?: Record): Promise { + const result: ProcessResult = { + success: false, + errors: [], + }; + + try { + this.logger.debug('invoked'); + + const meetingBaasApiKey = process.env.MEETING_BAAS_API_KEY || ''; + if (!meetingBaasApiKey) { + this.logger.critical('MEETING_BAAS_API_KEY not configured'); + throw new Error('MEETING_BAAS_API_KEY environment variable is required'); + } + + const { payload, extractedHeaders } = this.parsePayload(params); + const finalHeaders = extractedHeaders || headers; + + this.logger.debug(`payload event=${payload.event} bot_id=${payload.data.bot_id}`); + + this.logger.debug(`API key fingerprint=${getApiKeyFingerprint(meetingBaasApiKey)}`); + this.verifyApiKey(finalHeaders, meetingBaasApiKey); + this.logger.debug('API key verification: ok'); + + if (payload.event === WebhookEvent.FAILED) { + const failedData = payload.data; + this.logger.error(`bot failed: ${failedData.error_message} (${failedData.error_code})`); + throw new Error(`Meeting BaaS bot failed: ${failedData.error_message}`); + } + + if (payload.event === WebhookEvent.STATUS_CHANGE) { + this.logger.debug(`${WebhookEvent.STATUS_CHANGE} event received - no action needed`); + result.success = true; + return result; + } + + // Transform webhook data + const completedData = payload.data; + const meetingBaasClient = new MeetingBaasApiClient(meetingBaasApiKey); + const recordingData = meetingBaasClient.transformWebhookData( + completedData, + payload.extra ?? undefined, + ); + + result.durationMinutes = Math.round(recordingData.duration / 60); + + // calendarEventId and workspaceMemberId are passed via extra + // when schedule-bot.ts creates the scheduled bot + const extra = recordingData.extra; + const calendarEventId = extra.calendarEventId as string | undefined; + const workspaceMemberId = extra.workspaceMemberId as string | undefined; + + const syncResult: SyncResult = { + recordingsProcessed: 0, + recordingsCreated: 0, + recordingsUpdated: 0, + errors: [], + }; + + const recordingId = await syncBotRecording( + { + botId: recordingData.botId, + title: recordingData.title, + date: recordingData.date, + duration: recordingData.duration, + transcript: recordingData.transcript, + mp4Url: recordingData.mp4Url, + meetingUrl: recordingData.meetingUrl, + platform: recordingData.platform, + calendarEventId, + workspaceMemberId, + }, + syncResult, + ); + + if (recordingId) { + result.recordingId = recordingId; + this.logger.debug(`upserted recording id=${recordingId}`); + } + + result.success = true; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + this.logger.error(`error: ${message}`); + result.errors?.push(message); + } + + return result; + } + + private parsePayload(params: unknown): { payload: MeetingBaasWebhookPayload; extractedHeaders?: Record } { + let normalizedParams = params; + let extractedHeaders: Record | undefined; + + if (typeof normalizedParams === 'string') { + try { + normalizedParams = JSON.parse(normalizedParams); + } catch { + throw new Error('Invalid or missing webhook payload'); + } + } + + let payload: MeetingBaasWebhookPayload | undefined; + if (isValidMeetingBaasPayload(normalizedParams)) { + payload = normalizedParams as MeetingBaasWebhookPayload; + } else if (normalizedParams && typeof normalizedParams === 'object') { + const wrapper = normalizedParams as Record; + + if (wrapper.headers && typeof wrapper.headers === 'object' && !Array.isArray(wrapper.headers)) { + extractedHeaders = wrapper.headers as Record; + } + + for (const key of ['params', 'payload', 'body', 'data', 'event']) { + const candidate = wrapper[key]; + if (isValidMeetingBaasPayload(candidate)) { + payload = candidate as MeetingBaasWebhookPayload; + break; + } + } + + if (!payload && typeof wrapper['event'] === 'string' && wrapper['data']) { + const reconstructed = { event: wrapper['event'], data: wrapper['data'] }; + if (isValidMeetingBaasPayload(reconstructed)) { + payload = reconstructed as MeetingBaasWebhookPayload; + } + } + } + + if (!payload) { + throw new Error('Invalid or missing webhook payload'); + } + + return { payload, extractedHeaders }; + } + + private verifyApiKey( + headers: Record | undefined, + expectedApiKey: string + ): void { + const normalizedHeaders = headers || {}; + + const providedKey = Object.entries(normalizedHeaders).find( + ([key]) => key.toLowerCase() === 'x-meeting-baas-api-key', + )?.[1]; + + const verification = verifyWebhookApiKey(providedKey, expectedApiKey); + if (!verification.isValid) { + this.logger.critical('Invalid webhook API key - potential security threat'); + throw new Error('Invalid webhook API key'); + } + } +} diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.ts new file mode 100644 index 00000000000..8e53afb6c0e --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.ts @@ -0,0 +1,63 @@ +import { createHash } from 'crypto'; +import { WebhookEvent, type MeetingBaasWebhookPayload } from './types'; + +const VALID_WEBHOOK_EVENTS: MeetingBaasWebhookPayload['event'][] = [ + WebhookEvent.COMPLETED, + WebhookEvent.FAILED, + WebhookEvent.STATUS_CHANGE, +]; + +export type SignatureVerificationResult = { + isValid: boolean; + reason?: string; +}; + +// Meeting BaaS authenticates webhooks by including the API key +// in the x-meeting-baas-api-key header. We verify it matches our configured key. +export const verifyWebhookApiKey = ( + providedKey: string | undefined, + expectedKey: string +): SignatureVerificationResult => { + if (!providedKey) { + return { isValid: false, reason: 'missing x-meeting-baas-api-key header' }; + } + + if (!expectedKey) { + return { isValid: false, reason: 'MEETING_BAAS_API_KEY not configured' }; + } + + const isValid = providedKey === expectedKey; + + return { isValid, reason: isValid ? undefined : 'API key mismatch' }; +}; + +export const getApiKeyFingerprint = (apiKey: string): string => { + return createHash('sha256').update(apiKey).digest('hex').substring(0, 8); +}; + +export const isValidMeetingBaasPayload = ( + params: unknown +): params is MeetingBaasWebhookPayload => { + if (!params || typeof params !== 'object') { + return false; + } + + const payload = params as Record; + + // Must have 'event' field with valid V2 event type + if (typeof payload['event'] !== 'string' || payload['event'].length === 0) { + return false; + } + + if (!VALID_WEBHOOK_EVENTS.includes(payload['event'] as MeetingBaasWebhookPayload['event'])) { + return false; + } + + // Must have 'data' object with bot_id + if (!payload['data'] || typeof payload['data'] !== 'object') { + return false; + } + + const data = payload['data'] as Record; + return typeof data['bot_id'] === 'string' && data['bot_id'].length > 0; +}; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/tsconfig.json b/packages/twenty-apps/community/meeting-baas-recorder/tsconfig.json new file mode 100644 index 00000000000..f948a3187fb --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "sourceMap": true, + "declaration": true, + "outDir": "./dist", + "rootDir": ".", + "jsx": "react-jsx", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "strict": true, + "target": "es2018", + "module": "esnext", + "lib": ["es2020", "dom"], + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "resolveJsonModule": true + }, + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts", + "**/*.spec.ts" + ] +} diff --git a/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock b/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock new file mode 100644 index 00000000000..9a19f0ceada --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock @@ -0,0 +1,5413 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.28.5" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.28.6": + version: 7.29.0 + resolution: "@babel/compat-data@npm:7.29.0" + checksum: 10c0/08f348554989d23aa801bf1405aa34b15e841c0d52d79da7e524285c77a5f9d298e70e11d91cc578d8e2c9542efc586d50c5f5cf8e1915b254a9dcf786913a94 + languageName: node + linkType: hard + +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9": + version: 7.29.0 + resolution: "@babel/core@npm:7.29.0" + dependencies: + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helpers": "npm:^7.28.6" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/5127d2e8e842ae409e11bcbb5c2dff9874abf5415e8026925af7308e903f4f43397341467a130490d1a39884f461bc2b67f3063bce0be44340db89687fd852aa + languageName: node + linkType: hard + +"@babel/generator@npm:^7.29.0, @babel/generator@npm:^7.7.2": + version: 7.29.1 + resolution: "@babel/generator@npm:7.29.1" + dependencies: + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-compilation-targets@npm:7.28.6" + dependencies: + "@babel/compat-data": "npm:^7.28.6" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/3fcdf3b1b857a1578e99d20508859dbd3f22f3c87b8a0f3dc540627b4be539bae7f6e61e49d931542fe5b557545347272bbdacd7f58a5c77025a18b745593a50 + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-imports@npm:7.28.6" + dependencies: + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/b49d8d8f204d9dbfd5ac70c54e533e5269afb3cea966a9d976722b13e9922cc773a653405f53c89acb247d5aebdae4681d631a3ae3df77ec046b58da76eda2ac + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-transforms@npm:7.28.6" + dependencies: + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/traverse": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/6f03e14fc30b287ce0b839474b5f271e72837d0cafe6b172d759184d998fbee3903a035e81e07c2c596449e504f453463d58baa65b6f40a37ded5bec74620b2b + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.28.6, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.28.6 + resolution: "@babel/helper-plugin-utils@npm:7.28.6" + checksum: 10c0/3f5f8acc152fdbb69a84b8624145ff4f9b9f6e776cb989f9f968f8606eb7185c5c3cfcf3ba08534e37e1e0e1c118ac67080610333f56baa4f7376c99b5f1143d + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helpers@npm:7.28.6" + dependencies: + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/c4a779c66396bb0cf619402d92f1610601ff3832db2d3b86b9c9dd10983bf79502270e97ac6d5280cea1b1a37de2f06ecbac561bd2271545270407fbe64027cb + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/parser@npm:7.29.0" + dependencies: + "@babel/types": "npm:^7.29.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/333b2aa761264b91577a74bee86141ef733f9f9f6d4fc52548e4847dc35dfbf821f58c46832c637bfa761a6d9909d6a68f7d1ed59e17e4ffbb958dc510c17b62 + languageName: node + linkType: hard + +"@babel/plugin-syntax-async-generators@npm:^7.8.4": + version: 7.8.4 + resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 + languageName: node + linkType: hard + +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.12.13": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.12.13" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-static-block@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-attributes@npm:^7.24.7": + version: 7.28.6 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/1be160e2c426faa74e5be2e30e39e8d0d8c543063bd5d06cd804f8751b8fbcb82ce824ca7f9ce4b09c003693f6c06a11ce503b7e34d85e1a259631e4c3f72ad2 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee + languageName: node + linkType: hard + +"@babel/plugin-syntax-json-strings@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.7.2": + version: 7.28.6 + resolution: "@babel/plugin-syntax-jsx@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/b98fc3cd75e4ca3d5ca1162f610c286e14ede1486e0d297c13a5eb0ac85680ac9656d17d348bddd9160a54d797a08cea5eaac02b9330ddebb7b26732b7b99fb5 + languageName: node + linkType: hard + +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b + languageName: node + linkType: hard + +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce + languageName: node + linkType: hard + +"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 + languageName: node + linkType: hard + +"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 + languageName: node + linkType: hard + +"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3 + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.7.2": + version: 7.28.6 + resolution: "@babel/plugin-syntax-typescript@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/b0c392a35624883ac480277401ac7d92d8646b66e33639f5d350de7a6723924265985ae11ab9ebd551740ded261c443eaa9a87ea19def9763ca1e0d78c97dea8 + languageName: node + linkType: hard + +"@babel/template@npm:^7.28.6, @babel/template@npm:^7.3.3": + version: 7.28.6 + resolution: "@babel/template@npm:7.28.6" + dependencies: + "@babel/code-frame": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/66d87225ed0bc77f888181ae2d97845021838c619944877f7c4398c6748bcf611f216dfd6be74d39016af502bca876e6ce6873db3c49e4ac354c56d34d57e9f5 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/traverse@npm:7.29.0" + dependencies: + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" + debug: "npm:^4.3.1" + checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0, @babel/types@npm:^7.3.3": + version: 7.29.0 + resolution: "@babel/types@npm:7.29.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/aix-ppc64@npm:0.27.3" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm64@npm:0.27.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm@npm:0.27.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-x64@npm:0.27.3" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-arm64@npm:0.27.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-x64@npm:0.27.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-arm64@npm:0.27.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-x64@npm:0.27.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm64@npm:0.27.3" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm@npm:0.27.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ia32@npm:0.27.3" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-loong64@npm:0.27.3" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-mips64el@npm:0.27.3" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ppc64@npm:0.27.3" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-riscv64@npm:0.27.3" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-s390x@npm:0.27.3" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-x64@npm:0.27.3" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-arm64@npm:0.27.3" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-x64@npm:0.27.3" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-arm64@npm:0.27.3" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-x64@npm:0.27.3" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openharmony-arm64@npm:0.27.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/sunos-x64@npm:0.27.3" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-arm64@npm:0.27.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-ia32@npm:0.27.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-x64@npm:0.27.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@fastify/busboy@npm:^2.0.0": + version: 2.1.1 + resolution: "@fastify/busboy@npm:2.1.1" + checksum: 10c0/6f8027a8cba7f8f7b736718b013f5a38c0476eea67034c94a0d3c375e2b114366ad4419e6a6fa7ffc2ef9c6d3e0435d76dd584a7a1cbac23962fda7650b579e3 + languageName: node + linkType: hard + +"@gar/promise-retry@npm:^1.0.0": + version: 1.0.2 + resolution: "@gar/promise-retry@npm:1.0.2" + dependencies: + retry: "npm:^0.13.1" + checksum: 10c0/748a84fb0ab962f7867966f21dc24d1872c53c1656dd3352320fe69ad3b2043f2dfdb3be024c7636ce4904c5ba1da22d0f3558e489c3de578f5bb520f062d0fd + languageName: node + linkType: hard + +"@genql/cli@npm:^3.0.3": + version: 3.0.5 + resolution: "@genql/cli@npm:3.0.5" + dependencies: + "@graphql-tools/graphql-file-loader": "npm:^7.5.11" + "@graphql-tools/load": "npm:^7.8.6" + fs-extra: "npm:^10.1.0" + graphql: "npm:^16.6.0" + kleur: "npm:^4.1.5" + listr: "npm:^0.14.3" + lodash: "npm:^4.17.21" + mkdirp: "npm:^0.5.1" + native-fetch: "npm:^4.0.2" + prettier: "npm:^2.8.0" + qs: "npm:^6.11.0" + rimraf: "npm:^2.6.3" + undici: "npm:^5.18.0" + yargs: "npm:^15.3.1" + bin: + genql: dist/cli.js + checksum: 10c0/646d4da8986741a8f1b610bd8cfb5bc26cd9a856de671461e4ca87fb05443f37dd0edfb3b30ceaa34cd049077873afa07febfd4714dd2e0681fb07484e19a080 + languageName: node + linkType: hard + +"@graphql-tools/graphql-file-loader@npm:^7.5.11": + version: 7.5.17 + resolution: "@graphql-tools/graphql-file-loader@npm:7.5.17" + dependencies: + "@graphql-tools/import": "npm:6.7.18" + "@graphql-tools/utils": "npm:^9.2.1" + globby: "npm:^11.0.3" + tslib: "npm:^2.4.0" + unixify: "npm:^1.0.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/f737f14357731ad01da57755e1cf26ce375b475209d6ab7e4b656b56191a8979d2ab7dd5d1c54a1f11e04374f7a373fa95ea5ec6a001d0cef913ea208fadc65b + languageName: node + linkType: hard + +"@graphql-tools/import@npm:6.7.18": + version: 6.7.18 + resolution: "@graphql-tools/import@npm:6.7.18" + dependencies: + "@graphql-tools/utils": "npm:^9.2.1" + resolve-from: "npm:5.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/d33e37a1879dd43ac2851c9bac2f2873c58bb3687f1c06e159760dbb5e540ef074d688df70cc6dbd3ee5de48d437878df8f65a7c65ae80bd025bf98f2d615732 + languageName: node + linkType: hard + +"@graphql-tools/load@npm:^7.8.6": + version: 7.8.14 + resolution: "@graphql-tools/load@npm:7.8.14" + dependencies: + "@graphql-tools/schema": "npm:^9.0.18" + "@graphql-tools/utils": "npm:^9.2.1" + p-limit: "npm:3.1.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/1fa036ac596ccf48f350aa545d108c173184d9b53247f9e21c0d4ba96c5cba4a0b44281f9154f122e1e8e9d9d6eab93a5b2618ca8a797969bde1e75c1d45e786 + languageName: node + linkType: hard + +"@graphql-tools/merge@npm:^8.4.1": + version: 8.4.2 + resolution: "@graphql-tools/merge@npm:8.4.2" + dependencies: + "@graphql-tools/utils": "npm:^9.2.1" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/2df55222b48e010e683572f456cf265aabae5748c59f7c1260c66dec9794b7a076d3706f04da969b77f0a32c7ccb4551fee30125931d3fe9c98a8806aae9a3f4 + languageName: node + linkType: hard + +"@graphql-tools/schema@npm:^9.0.18": + version: 9.0.19 + resolution: "@graphql-tools/schema@npm:9.0.19" + dependencies: + "@graphql-tools/merge": "npm:^8.4.1" + "@graphql-tools/utils": "npm:^9.2.1" + tslib: "npm:^2.4.0" + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/42fd8ca8d3c8d60b583077c201980518482ff0cd5ed0c1f14bd9b835a2689ad41d02cbd3478f7d7dea7aec1227f7639fd5deb5e6360852a2e542b96b44bfb7a4 + languageName: node + linkType: hard + +"@graphql-tools/utils@npm:^9.2.1": + version: 9.2.1 + resolution: "@graphql-tools/utils@npm:9.2.1" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/37a7bd7e14d28ff1bacc007dca84bc6cef2d7d7af9a547b5dbe52fcd134afddd6d4a7b2148cfbaff5ddba91a868453d597da77bd0457fb0be15928f916901606 + languageName: node + linkType: hard + +"@graphql-typed-document-node/core@npm:^3.1.1": + version: 3.2.0 + resolution: "@graphql-typed-document-node/core@npm:3.2.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/94e9d75c1f178bbae8d874f5a9361708a3350c8def7eaeb6920f2c820e82403b7d4f55b3735856d68e145e86c85cbfe2adc444fdc25519cd51f108697e99346c + languageName: node + linkType: hard + +"@inquirer/checkbox@npm:^2.5.0": + version: 2.5.0 + resolution: "@inquirer/checkbox@npm:2.5.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/figures": "npm:^1.0.5" + "@inquirer/type": "npm:^1.5.3" + ansi-escapes: "npm:^4.3.2" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/679d17ffe3aef0825593f3bc8d193b6c37b860c6cf6e0e9a10d4e60cc254a2dfc5da4a982bf5b9b5147018e456fffcb0b0dadf93ee1914b9d600b0c814284e22 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^3.2.0": + version: 3.2.0 + resolution: "@inquirer/confirm@npm:3.2.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + checksum: 10c0/a2cbfc8ae9c880bba4cce1993f5c399fb0d12741fdd574917c87fceb40ece62ffa60e35aaadf4e62d7c114f54008e45aee5d6d90497bb62d493996c02725d243 + languageName: node + linkType: hard + +"@inquirer/core@npm:^9.1.0": + version: 9.2.1 + resolution: "@inquirer/core@npm:9.2.1" + dependencies: + "@inquirer/figures": "npm:^1.0.6" + "@inquirer/type": "npm:^2.0.0" + "@types/mute-stream": "npm:^0.0.4" + "@types/node": "npm:^22.5.5" + "@types/wrap-ansi": "npm:^3.0.0" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^1.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/11c14be77a9fa85831de799a585721b0a49ab2f3b7d8fd1780c48ea2b29229c6bdc94e7892419086d0f7734136c2ba87b6a32e0782571eae5bbd655b1afad453 + languageName: node + linkType: hard + +"@inquirer/editor@npm:^2.2.0": + version: 2.2.0 + resolution: "@inquirer/editor@npm:2.2.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + external-editor: "npm:^3.1.0" + checksum: 10c0/b8afc0790a7a5d82998bdfe469cbaa83b0cd0700be432cf95256c548e2a6a494997b5e93d65cbf94979c17b510758cf8494d85559f6b9508eb15d239a7f22aee + languageName: node + linkType: hard + +"@inquirer/expand@npm:^2.3.0": + version: 2.3.0 + resolution: "@inquirer/expand@npm:2.3.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/f2030cb482a715e4d5153c19b3f0fd8bf47c16cdc16e1c669e90985386edf4f7b0f3b0e97e2990bb228878b93716228eb067d94fc557c25d3c5ee58747c0a995 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.5, @inquirer/figures@npm:^1.0.6": + version: 1.0.15 + resolution: "@inquirer/figures@npm:1.0.15" + checksum: 10c0/6e39a040d260ae234ae220180b7994ff852673e20be925f8aa95e78c7934d732b018cbb4d0ec39e600a410461bcb93dca771e7de23caa10630d255692e440f69 + languageName: node + linkType: hard + +"@inquirer/input@npm:^2.3.0": + version: 2.3.0 + resolution: "@inquirer/input@npm:2.3.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + checksum: 10c0/44c8cea38c9192f528cae556f38709135a00230132deab3b9bb9a925375fce0513fecf4e8c1df7c4319e1ed7aa31fb4dd2c4956c8bc9dd39af087aafff5b6f1f + languageName: node + linkType: hard + +"@inquirer/number@npm:^1.1.0": + version: 1.1.0 + resolution: "@inquirer/number@npm:1.1.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + checksum: 10c0/db472dab57c951c4a083b2a749ce58262b1efd9889e7603de6e9c3f9af7d8dce8fbdfa3859f65402d3587470e0397a076e5fb4ed775db33310f17a42c9faeb20 + languageName: node + linkType: hard + +"@inquirer/password@npm:^2.2.0": + version: 2.2.0 + resolution: "@inquirer/password@npm:2.2.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + ansi-escapes: "npm:^4.3.2" + checksum: 10c0/fa4b335164b2c9c3304d29a7214ef93bac8d3da6788146603ea3d0485b8d811151e49bf66cb0dcc729a9dc21406c3a8c2718c5beec572a91d07026d22842c13f + languageName: node + linkType: hard + +"@inquirer/prompts@npm:^5.5.0": + version: 5.5.0 + resolution: "@inquirer/prompts@npm:5.5.0" + dependencies: + "@inquirer/checkbox": "npm:^2.5.0" + "@inquirer/confirm": "npm:^3.2.0" + "@inquirer/editor": "npm:^2.2.0" + "@inquirer/expand": "npm:^2.3.0" + "@inquirer/input": "npm:^2.3.0" + "@inquirer/number": "npm:^1.1.0" + "@inquirer/password": "npm:^2.2.0" + "@inquirer/rawlist": "npm:^2.3.0" + "@inquirer/search": "npm:^1.1.0" + "@inquirer/select": "npm:^2.5.0" + checksum: 10c0/2d62b50ca761b2bd2d5759f48c03758f1af0665ac602c26ae1ae257ac512cf5c27fd82cde108ee0c6371ec39187adc6f45637f31ca79adf5bf96579f23e77143 + languageName: node + linkType: hard + +"@inquirer/rawlist@npm:^2.3.0": + version: 2.3.0 + resolution: "@inquirer/rawlist@npm:2.3.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/d49d5e12b7a54394c140b27c8d8748ba1ab855c67c01fa72b5a63810f12865df3bf4d5ae929f54fad77b5fc2f7431a332ae1e5fe4babb335380c28917002f364 + languageName: node + linkType: hard + +"@inquirer/search@npm:^1.1.0": + version: 1.1.0 + resolution: "@inquirer/search@npm:1.1.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/figures": "npm:^1.0.5" + "@inquirer/type": "npm:^1.5.3" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/20d7e910266b9e3f0dc8eef8f3007f487e6149fa8421d293eaf7c11a1e35c3d82aa30af118b3a6e35eed1048a27d7d806f45722abb10005db5d099ea64b00b17 + languageName: node + linkType: hard + +"@inquirer/select@npm:^2.5.0": + version: 2.5.0 + resolution: "@inquirer/select@npm:2.5.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/figures": "npm:^1.0.5" + "@inquirer/type": "npm:^1.5.3" + ansi-escapes: "npm:^4.3.2" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/280fa700187ff29da0ad4bf32aa11db776261584ddf5cc1ceac5caebb242a4ac0c5944af522a2579d78b6ec7d6e8b1b9f6564872101abd8dcc69929b4e33fc4c + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.5.3": + version: 1.5.5 + resolution: "@inquirer/type@npm:1.5.5" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10c0/4c41736c09ba9426b5a9e44993bdd54e8f532e791518802e33866f233a2a6126a25c1c82c19d1abbf1df627e57b1b957dd3f8318ea96073d8bfc32193943bcb3 + languageName: node + linkType: hard + +"@inquirer/type@npm:^2.0.0": + version: 2.0.0 + resolution: "@inquirer/type@npm:2.0.0" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10c0/8c663d52beb2b89a896d3c3d5cc3d6d024fa149e565555bcb42fa640cbe23fba7ff2c51445342cef1fe6e46305e2d16c1590fa1d11ad0ddf93a67b655ef41f0a + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c + languageName: node + linkType: hard + +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/reporters": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-changed-files: "npm:^29.7.0" + jest-config: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-resolve-dependencies: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-ansi: "npm:^6.0.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 + languageName: node + linkType: hard + +"@jest/environment@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/environment@npm:29.7.0" + dependencies: + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + checksum: 10c0/c7b1b40c618f8baf4d00609022d2afa086d9c6acc706f303a70bb4b67275868f620ad2e1a9efc5edd418906157337cce50589a627a6400bbdf117d351b91ef86 + languageName: node + linkType: hard + +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a + languageName: node + linkType: hard + +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" + dependencies: + expect: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e + languageName: node + linkType: hard + +"@jest/fake-timers@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/fake-timers@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@sinonjs/fake-timers": "npm:^10.0.2" + "@types/node": "npm:*" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/cf0a8bcda801b28dc2e2b2ba36302200ee8104a45ad7a21e6c234148932f826cb3bc57c8df3b7b815aeea0861d7b6ca6f0d4778f93b9219398ef28749e03595c + languageName: node + linkType: hard + +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + jest-mock: "npm:^29.7.0" + checksum: 10c0/a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea + languageName: node + linkType: hard + +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + collect-v8-coverage: "npm:^1.0.0" + exit: "npm:^0.1.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.1" + strip-ansi: "npm:^6.0.0" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be + languageName: node + linkType: hard + +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.18" + callsites: "npm:^3.0.0" + graceful-fs: "npm:^4.2.9" + checksum: 10c0/a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 + languageName: node + linkType: hard + +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + collect-v8-coverage: "npm:^1.0.0" + checksum: 10c0/7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b + languageName: node + linkType: hard + +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + babel-plugin-istanbul: "npm:^6.1.1" + chalk: "npm:^4.0.0" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pirates: "npm:^4.0.4" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^4.0.2" + checksum: 10c0/7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 + languageName: node + linkType: hard + +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.8" + chalk: "npm:^4.0.0" + checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@meeting-baas/sdk@npm:^6.0.5": + version: 6.0.5 + resolution: "@meeting-baas/sdk@npm:6.0.5" + dependencies: + axios: "npm:1.8.3" + zod: "npm:3.24.2" + checksum: 10c0/6de1fe321e20a9f7f95dff38168f8d9be6f0d537ada6a800225d6d032b0cfd8c24813e56a96ed1eb13d9feb4067be39d70d60fd26903bf76514ae0fd707fab9f + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b + languageName: node + linkType: hard + +"@samverschueren/stream-to-observable@npm:^0.3.0": + version: 0.3.1 + resolution: "@samverschueren/stream-to-observable@npm:0.3.1" + dependencies: + any-observable: "npm:^0.3.0" + peerDependenciesMeta: + rxjs: + optional: true + zen-observable: + optional: true + checksum: 10c0/0d874453f6bc2460d71783292291f52feb36c2a75314b1072a6ffe6206562f33e9d664a554348d565a6b54da9041d75070371052545bc329caaa52f64216987f + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.10 + resolution: "@sinclair/typebox@npm:0.27.10" + checksum: 10c0/ca42a02817656dbdae464ed4bb8aca6ad4718d7618e270760fea84a834ad0ecc1a22eba51421f09e5047174571131356ff3b5d80d609ced775d631df7b404b0d + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" + dependencies: + type-detect: "npm:4.0.8" + checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^10.0.2": + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10c0/2e2fb6cc57f227912814085b7b01fede050cd4746ea8d49a1e44d5a0e56a804663b0340ae2f11af7559ea9bf4d087a11f2f646197a660ea3cb04e19efc04aa63 + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.1.14": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.27.0 + resolution: "@types/babel__generator@npm:7.27.0" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.28.0 + resolution: "@types/babel__traverse@npm:7.28.0" + dependencies: + "@babel/types": "npm:^7.28.2" + checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 + languageName: node + linkType: hard + +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.9 + resolution: "@types/graceful-fs@npm:4.1.9" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/235d2fc69741448e853333b7c3d1180a966dd2b8972c8cbcd6b2a0c6cd7f8d582ab2b8e58219dbc62cce8f1b40aa317ff78ea2201cdd8249da5025adebed6f0b + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.3 + resolution: "@types/istanbul-lib-report@npm:3.0.3" + dependencies: + "@types/istanbul-lib-coverage": "npm:*" + checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/istanbul-reports@npm:3.0.4" + dependencies: + "@types/istanbul-lib-report": "npm:*" + checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee + languageName: node + linkType: hard + +"@types/jest@npm:^29.5.5": + version: 29.5.14 + resolution: "@types/jest@npm:29.5.14" + dependencies: + expect: "npm:^29.0.0" + pretty-format: "npm:^29.0.0" + checksum: 10c0/18e0712d818890db8a8dab3d91e9ea9f7f19e3f83c2e50b312f557017dc81466207a71f3ed79cf4428e813ba939954fa26ffa0a9a7f153181ba174581b1c2aed + languageName: node + linkType: hard + +"@types/mute-stream@npm:^0.0.4": + version: 0.0.4 + resolution: "@types/mute-stream@npm:0.0.4" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/944730fd7b398c5078de3c3d4d0afeec8584283bc694da1803fdfca14149ea385e18b1b774326f1601baf53898ce6d121a952c51eb62d188ef6fcc41f725c0dc + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 25.3.3 + resolution: "@types/node@npm:25.3.3" + dependencies: + undici-types: "npm:~7.18.0" + checksum: 10c0/63e1d3816a9f4a706ab5d588d18cb98aa824b97748ff585537d327528e9438f58f69f45c7762e7cd3a1ab32c1619f551aabe8075d13172f9273cf10f6d83ab91 + languageName: node + linkType: hard + +"@types/node@npm:^22.5.5": + version: 22.19.13 + resolution: "@types/node@npm:22.19.13" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/ad8a0b6a69dd8ef663701573eca94ef6d20ce093f54f05bed823c826374eb1ea8b06e6f2c5d7f6cfc203b82dfd60293296bf80ea987ddb77775840bc2ef9f803 + languageName: node + linkType: hard + +"@types/node@npm:^24.9.2": + version: 24.11.0 + resolution: "@types/node@npm:24.11.0" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10c0/4fb7390259e3b158d25dbecf52de8ce70fa18a4ed0949c9444bb6384517c361fa19781e6821ca8c18dc5f6af43eab72e9e159e07000e6b1286d082e8585d8c41 + languageName: node + linkType: hard + +"@types/stack-utils@npm:^2.0.0": + version: 2.0.3 + resolution: "@types/stack-utils@npm:2.0.3" + checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c + languageName: node + linkType: hard + +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 10c0/8d8f53363f360f38135301a06b596c295433ad01debd082078c33c6ed98b05a5c8fe8853a88265432126096084f4a135ec1564e3daad631b83296905509f90b3 + languageName: node + linkType: hard + +"@types/yargs-parser@npm:*": + version: 21.0.3 + resolution: "@types/yargs-parser@npm:21.0.3" + checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.8": + version: 17.0.35 + resolution: "@types/yargs@npm:17.0.35" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10c0/609557826a6b85e73ccf587923f6429850d6dc70e420b455bab4601b670bfadf684b09ae288bccedab042c48ba65f1666133cf375814204b544009f57d6eef63 + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"ansi-escapes@npm:^3.0.0": + version: 3.2.0 + resolution: "ansi-escapes@npm:3.2.0" + checksum: 10c0/084e1ce38139ad2406f18a8e7efe2b850ddd06ce3c00f633392d1ce67756dab44fe290e573d09ef3c9a0cb13c12881e0e35a8f77a017d39a0a4ab85ae2fae04f + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-regex@npm:^2.0.0": + version: 2.1.1 + resolution: "ansi-regex@npm:2.1.1" + checksum: 10c0/78cebaf50bce2cb96341a7230adf28d804611da3ce6bf338efa7b72f06cc6ff648e29f80cd95e582617ba58d5fdbec38abfeed3500a98bce8381a9daec7c548b + languageName: node + linkType: hard + +"ansi-regex@npm:^3.0.0": + version: 3.0.1 + resolution: "ansi-regex@npm:3.0.1" + checksum: 10c0/d108a7498b8568caf4a46eea4f1784ab4e0dfb2e3f3938c697dee21443d622d765c958f2b7e2b9f6b9e55e2e2af0584eaa9915d51782b89a841c28e744e7a167 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-styles@npm:^2.2.1": + version: 2.2.1 + resolution: "ansi-styles@npm:2.2.1" + checksum: 10c0/7c68aed4f1857389e7a12f85537ea5b40d832656babbf511cc7ecd9efc52889b9c3e5653a71a6aade783c3c5e0aa223ad4ff8e83c27ac8a666514e6c79068cab + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"any-observable@npm:^0.3.0": + version: 0.3.0 + resolution: "any-observable@npm:0.3.0" + checksum: 10c0/104c2b79c2ac7e6c75b35f8fd62babf73015668f22bd25336c6f848350d91f9e7daf2fddbf1c1b76fe795e89fbc91b49f70a2aec5c69f1acf0562c344f36042b + languageName: node + linkType: hard + +"anymatch@npm:^3.0.3": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"axios@npm:1.8.3": + version: 1.8.3 + resolution: "axios@npm:1.8.3" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/de75da9859adf0a6481d4af2b687db357a054d20f0d69b99d502b71dae3578326b1fdc0951dabaef769827484941cda93d3f89150bf9e04f05f6615fb8316780 + languageName: node + linkType: hard + +"axios@npm:^1.13.1, axios@npm:^1.6.0": + version: 1.13.6 + resolution: "axios@npm:1.13.6" + dependencies: + follow-redirects: "npm:^1.15.11" + form-data: "npm:^4.0.5" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/51fb5af055c3b85662fa97df17d986ae2c37d13bf86d50b6bb36b6b3a2dec6966a1d3a14ab3774b71707b155ae3597ed9b7babdf1a1a863d1a31840cb8e7ec71 + languageName: node + linkType: hard + +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" + dependencies: + "@jest/transform": "npm:^29.7.0" + "@types/babel__core": "npm:^7.1.14" + babel-plugin-istanbul: "npm:^6.1.1" + babel-preset-jest: "npm:^29.6.3" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + slash: "npm:^3.0.0" + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 10c0/2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-instrument: "npm:^5.0.4" + test-exclude: "npm:^6.0.0" + checksum: 10c0/1075657feb705e00fd9463b329921856d3775d9867c5054b449317d39153f8fbcebd3e02ebf00432824e647faff3683a9ca0a941325ef1afe9b3c4dd51b24beb + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": "npm:^7.3.3" + "@babel/types": "npm:^7.3.3" + "@types/babel__core": "npm:^7.1.14" + "@types/babel__traverse": "npm:^7.0.6" + checksum: 10c0/7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.2.0 + resolution: "babel-preset-current-node-syntax@npm:1.2.0" + dependencies: + "@babel/plugin-syntax-async-generators": "npm:^7.8.4" + "@babel/plugin-syntax-bigint": "npm:^7.8.3" + "@babel/plugin-syntax-class-properties": "npm:^7.12.13" + "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" + "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" + "@babel/plugin-syntax-import-meta": "npm:^7.10.4" + "@babel/plugin-syntax-json-strings": "npm:^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" + "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" + "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" + "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0 || ^8.0.0-0 + checksum: 10c0/94a4f81cddf9b051045d08489e4fff7336292016301664c138cfa3d9ffe3fe2ba10a24ad6ae589fd95af1ac72ba0216e1653555c187e694d7b17be0c002bea10 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.9.0": + version: 2.10.0 + resolution: "baseline-browser-mapping@npm:2.10.0" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/da9c3ec0fcd7f325226a47d2142794d41706b6e0a405718a2c15410bbdb72aacadd65738bedef558c6f1b106ed19458cb25b06f63b66df2c284799905dbbd003 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73 + languageName: node + linkType: hard + +"brace-expansion@npm:^5.0.2": + version: 5.0.4 + resolution: "brace-expansion@npm:5.0.4" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/359cbcfa80b2eb914ca1f3440e92313fbfe7919ee6b274c35db55bec555aded69dac5ee78f102cec90c35f98c20fa43d10936d0cd9978158823c249257e1643a + languageName: node + linkType: hard + +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" + dependencies: + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" + bin: + browserslist: cli.js + checksum: 10c0/545a5fa9d7234e3777a7177ec1e9134bb2ba60a69e6b95683f6982b1473aad347c77c1264ccf2ac5dea609a9731fbfbda6b85782bdca70f80f86e28a402504bd + languageName: node + linkType: hard + +"bs-logger@npm:^0.2.6": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: "npm:2.x" + checksum: 10c0/80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0 + languageName: node + linkType: hard + +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: "npm:^0.4.0" + checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + unique-filename: "npm:^5.0.0" + checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001759": + version: 1.0.30001775 + resolution: "caniuse-lite@npm:1.0.30001775" + checksum: 10c0/96e59a83abd171c729db80a93d7a50becebc145102e3c2d2ea4b3749385cae1e6e09155bada486f662fa070009989d043c4c60f1ee64a19eb50139c7e6dbe574 + languageName: node + linkType: hard + +"chalk@npm:^1.0.0, chalk@npm:^1.1.3": + version: 1.1.3 + resolution: "chalk@npm:1.1.3" + dependencies: + ansi-styles: "npm:^2.2.1" + escape-string-regexp: "npm:^1.0.2" + has-ansi: "npm:^2.0.0" + strip-ansi: "npm:^3.0.0" + supports-color: "npm:^2.0.0" + checksum: 10c0/28c3e399ec286bb3a7111fd4225ebedb0d7b813aef38a37bca7c498d032459c265ef43404201d5fbb8d888d29090899c95335b4c0cda13e8b126ff15c541cef8 + languageName: node + linkType: hard + +"chalk@npm:^2.4.1": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"chalk@npm:^5.3.0": + version: 5.6.2 + resolution: "chalk@npm:5.6.2" + checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 + languageName: node + linkType: hard + +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e + languageName: node + linkType: hard + +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 10c0/96e4731b9ec8050cbb56ab684e8c48d6c33f7826b755802d14e3ebfdc51c57afeece3ea39bc6b09acc359e4363525388b915e16640c1378053820f5e70d0f27d + languageName: node + linkType: hard + +"chokidar@npm:^4.0.0": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.0.0": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10c0/076b3af85adc4d65dbdab1b5b240fe5b45d44fcf0ef9d429044dd94d19be5589376805c44fb2d4b3e684e5fe6a9b7cf3e426476a6507c45283c5fc6ff95240be + languageName: node + linkType: hard + +"cli-cursor@npm:^2.0.0, cli-cursor@npm:^2.1.0": + version: 2.1.0 + resolution: "cli-cursor@npm:2.1.0" + dependencies: + restore-cursor: "npm:^2.0.0" + checksum: 10c0/09ee6d8b5b818d840bf80ec9561eaf696672197d3a02a7daee2def96d5f52ce6e0bbe7afca754ccf14f04830b5a1b4556273e983507d5029f95bba3016618eda + languageName: node + linkType: hard + +"cli-truncate@npm:^0.2.1": + version: 0.2.1 + resolution: "cli-truncate@npm:0.2.1" + dependencies: + slice-ansi: "npm:0.0.4" + string-width: "npm:^1.0.1" + checksum: 10c0/c6caa5e2b70d841c42f4a2270d6fc7129df915f8911e4afa90c79231ccc857cd819a2c90e0707fde04e51ce56b4d71646b843f6cbaff4f7cdcb3b91ed51f6e89 + languageName: node + linkType: hard + +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + +"cliui@npm:^6.0.0": + version: 6.0.0 + resolution: "cliui@npm:6.0.0" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10c0/35229b1bb48647e882104cac374c9a18e34bbf0bace0e2cf03000326b6ca3050d6b59545d91e17bfe3705f4a0e2988787aa5cde6331bf5cbbf0164732cef6492 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 + languageName: node + linkType: hard + +"code-point-at@npm:^1.0.0": + version: 1.1.0 + resolution: "code-point-at@npm:1.1.0" + checksum: 10c0/33f6b234084e46e6e369b6f0b07949392651b4dde70fc6a592a8d3dafa08d5bb32e3981a02f31f6fc323a26bc03a4c063a9d56834848695bda7611c2417ea2e6 + languageName: node + linkType: hard + +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.3 + resolution: "collect-v8-coverage@npm:1.0.3" + checksum: 10c0/bc62ba251bcce5e3354a8f88fa6442bee56e3e612fec08d4dfcf66179b41ea0bf544b0f78c4ebc0f8050871220af95bb5c5578a6aef346feea155640582f09dc + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"commander@npm:^12.0.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + prompts: "npm:^2.0.1" + bin: + create-jest: bin/create-jest.js + checksum: 10c0/e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.3": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"date-fns@npm:^1.27.2": + version: 1.30.1 + resolution: "date-fns@npm:1.30.1" + checksum: 10c0/bad6ad7c15180121e15d61ad62a4a214c108d66f35b35f5eeb6ade837a3c29aa4444b9528a93a5374b95ba11231c142276351bf52f4d168676f9a1e17ce3726a + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.4": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"decamelize@npm:^1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: 10c0/85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 + languageName: node + linkType: hard + +"dedent@npm:^1.0.0": + version: 1.7.2 + resolution: "dedent@npm:1.7.2" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10c0/acaff07cac355b93f17b1b17ebbb84d3cc55af6ab4b7814c3f505e061903e168bc6bf9ddce331552d64dee1525f0b4c549c9ade46aebfac6f69caaed74e90751 + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d + languageName: node + linkType: hard + +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"dotenv@npm:^16.4.0": + version: 16.6.1 + resolution: "dotenv@npm:16.6.1" + checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc + languageName: node + linkType: hard + +"dotenv@npm:^17.2.3": + version: 17.3.1 + resolution: "dotenv@npm:17.3.1" + checksum: 10c0/c78e0c2d5a549c751e544cc60e2b95e7cb67e0c551f42e094d161c6b297aa44b630a3c2dcacf5569e529a6c2a6b84e2ab9be8d37b299d425df5a18b81ce4a35f + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.263": + version: 1.5.302 + resolution: "electron-to-chromium@npm:1.5.302" + checksum: 10c0/014413f2b4ec3a0e043c68f6c07a760d230b14e36b8549c5b124f386a6318d9641e69be2aa0df1877395dd99922745c1722c8ecb3d72205f0f3b3b3dc44c8442 + languageName: node + linkType: hard + +"elegant-spinner@npm:^1.0.1": + version: 1.0.1 + resolution: "elegant-spinner@npm:1.0.1" + checksum: 10c0/df607c83c20fc3ce56c514175dd5d1ee7f667da00cee13d04d32c70d55e76555091fa236689e691cf7dedba17b0020fec635e499cdde84dbea2ef8639314e5f8 + languageName: node + linkType: hard + +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"esbuild@npm:~0.27.0": + version: 0.27.3 + resolution: "esbuild@npm:0.27.3" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.3" + "@esbuild/android-arm": "npm:0.27.3" + "@esbuild/android-arm64": "npm:0.27.3" + "@esbuild/android-x64": "npm:0.27.3" + "@esbuild/darwin-arm64": "npm:0.27.3" + "@esbuild/darwin-x64": "npm:0.27.3" + "@esbuild/freebsd-arm64": "npm:0.27.3" + "@esbuild/freebsd-x64": "npm:0.27.3" + "@esbuild/linux-arm": "npm:0.27.3" + "@esbuild/linux-arm64": "npm:0.27.3" + "@esbuild/linux-ia32": "npm:0.27.3" + "@esbuild/linux-loong64": "npm:0.27.3" + "@esbuild/linux-mips64el": "npm:0.27.3" + "@esbuild/linux-ppc64": "npm:0.27.3" + "@esbuild/linux-riscv64": "npm:0.27.3" + "@esbuild/linux-s390x": "npm:0.27.3" + "@esbuild/linux-x64": "npm:0.27.3" + "@esbuild/netbsd-arm64": "npm:0.27.3" + "@esbuild/netbsd-x64": "npm:0.27.3" + "@esbuild/openbsd-arm64": "npm:0.27.3" + "@esbuild/openbsd-x64": "npm:0.27.3" + "@esbuild/openharmony-arm64": "npm:0.27.3" + "@esbuild/sunos-x64": "npm:0.27.3" + "@esbuild/win32-arm64": "npm:0.27.3" + "@esbuild/win32-ia32": "npm:0.27.3" + "@esbuild/win32-x64": "npm:0.27.3" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: 10c0/71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 + languageName: node + linkType: hard + +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"external-editor@npm:^3.1.0": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: "npm:^0.7.0" + iconv-lite: "npm:^0.4.24" + tmp: "npm:^0.0.33" + checksum: 10c0/c98f1ba3efdfa3c561db4447ff366a6adb5c1e2581462522c56a18bf90dfe4da382f9cd1feee3e330108c3595a854b218272539f311ba1b3298f841eb0fbf339 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.20.1 + resolution: "fastq@npm:1.20.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/e5dd725884decb1f11e5c822221d76136f239d0236f176fab80b7b8f9e7619ae57e6b4e5b73defc21e6b9ef99437ee7b545cff8e6c2c337819633712fa9d352e + languageName: node + linkType: hard + +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: "npm:2.1.1" + checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"figures@npm:^1.7.0": + version: 1.7.0 + resolution: "figures@npm:1.7.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + object-assign: "npm:^4.1.0" + checksum: 10c0/a10942b0eec3372bf61822ab130d2bbecdf527d551b0b013fbe7175b7a0238ead644ee8930a1a3cb872fb9ab2ec27df30e303765a3b70b97852e2e9ee43bdff3 + languageName: node + linkType: hard + +"figures@npm:^2.0.0": + version: 2.0.0 + resolution: "figures@npm:2.0.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + checksum: 10c0/5dc5a75fec3e7e04ae65d6ce51d28b3e70d4656c51b06996b6fdb2cb5b542df512e3b3c04482f5193a964edddafa5521479ff948fa84e12ff556e53e094ab4ce + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.11, follow-redirects@npm:^1.15.6": + version: 1.15.11 + resolution: "follow-redirects@npm:1.15.11" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/d301f430542520a54058d4aeeb453233c564aaccac835d29d15e050beb33f339ad67d9bddbce01739c5dc46a6716dbe3d9d0d5134b1ca203effa11a7ef092343 + languageName: node + linkType: hard + +"form-data@npm:^4.0.0, form-data@npm:^4.0.5": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" + mime-types: "npm:^2.1.12" + checksum: 10c0/dd6b767ee0bbd6d84039db12a0fa5a2028160ffbfaba1800695713b46ae974a5f6e08b3356c3195137f8530dcd9dfcb5d5ae1eeff53d0db1e5aad863b619ce3b + languageName: node + linkType: hard + +"fs-extra@npm:^10.1.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/5f579466e7109719d162a9249abbeffe7f426eb133ea486e020b89bc6d67a741134076bf439983f2eb79276ceaf6bd7b7c1e43c3fd67fe889863e69072fb0a5e + languageName: node + linkType: hard + +"fs-extra@npm:^11.2.0": + version: 11.3.3 + resolution: "fs-extra@npm:11.3.3" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/984924ff4104e3e9f351b658a864bf3b354b2c90429f57aec0acd12d92c4e6b762cbacacdffb4e745b280adce882e1f980c485d9f02c453f769ab4e7fc646ce3 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.7.5": + version: 4.13.6 + resolution: "get-tsconfig@npm:4.13.6" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/bab6937302f542f97217cbe7cbbdfa7e85a56a377bc7a73e69224c1f0b7c9ae8365918e55752ae8648265903f506c1705f63c0de1d4bab1ec2830fef3e539a1a + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob@npm:^13.0.0": + version: 13.0.6 + resolution: "glob@npm:13.0.6" + dependencies: + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"globby@npm:^11.0.3": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graphql-sse@npm:^2.5.4": + version: 2.6.0 + resolution: "graphql-sse@npm:2.6.0" + peerDependencies: + graphql: ">=0.11 <=16" + checksum: 10c0/e05f0b5c8539d61e5ce34af8e0bb418c02bf922d6a7f9232a9abd53c77df5654684ca14f674ac771645c8fba9c37ce666c5d06f46eef54ea07e63653468065b0 + languageName: node + linkType: hard + +"graphql@npm:^16.6.0, graphql@npm:^16.8.1": + version: 16.13.0 + resolution: "graphql@npm:16.13.0" + checksum: 10c0/c20e7909c6c8d405a30f8caeae5aaf604b9f949bdcdf80bdb3d3561123e6099181c39eae697cf4fcdc5dd301ab215363b8def0aa0ff51f03cc7e4b48a8c2c2b1 + languageName: node + linkType: hard + +"handlebars@npm:^4.7.8": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" + dependencies: + minimist: "npm:^1.2.5" + neo-async: "npm:^2.6.2" + source-map: "npm:^0.6.1" + uglify-js: "npm:^3.1.4" + wordwrap: "npm:^1.0.0" + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 10c0/7aff423ea38a14bb379316f3857fe0df3c5d66119270944247f155ba1f08e07a92b340c58edaa00cfe985c21508870ee5183e0634dcb53dd405f35c93ef7f10d + languageName: node + linkType: hard + +"has-ansi@npm:^2.0.0": + version: 2.0.0 + resolution: "has-ansi@npm:2.0.0" + dependencies: + ansi-regex: "npm:^2.0.0" + checksum: 10c0/f54e4887b9f8f3c4bfefd649c48825b3c093987c92c27880ee9898539e6f01aed261e82e73153c3f920fde0db5bf6ebd58deb498ed1debabcb4bc40113ccdf05 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"iconv-lite@npm:^0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.7.2": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/3c228920f3bd307f56bf8363706a776f4a060eb042f131cd23855ceca962951b264d0997ab38a1ad340e1c5df8499ed26e1f4f0db6b2a2ad9befaff22f14b722 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"import-local@npm:^3.0.2": + version: 3.2.0 + resolution: "import-local@npm:3.2.0" + dependencies: + pkg-dir: "npm:^4.2.0" + resolve-cwd: "npm:^3.0.0" + bin: + import-local-fixture: fixtures/cli.js + checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"indent-string@npm:^3.0.0": + version: 3.2.0 + resolution: "indent-string@npm:3.2.0" + checksum: 10c0/91b6d61621d24944c5c4d365d6f1ff4a490264ccaf1162a602faa0d323e69231db2180ad4ccc092c2f49cf8888cdb3da7b73e904cc0fdeec40d0bfb41ceb9478 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"inquirer@npm:^10.0.0": + version: 10.2.2 + resolution: "inquirer@npm:10.2.2" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/prompts": "npm:^5.5.0" + "@inquirer/type": "npm:^1.5.3" + "@types/mute-stream": "npm:^0.0.4" + ansi-escapes: "npm:^4.3.2" + mute-stream: "npm:^1.0.0" + run-async: "npm:^3.0.0" + rxjs: "npm:^7.8.1" + checksum: 10c0/09bcff887a968ce29d3a8cba749ef35b6531b7fb7bf5b28aaf3e10aebae07af2494dd3ec67ae6f1dabe76da1064cfe4514098d4c7658fcaf3fd480b2975d7163 + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.1": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^1.0.0": + version: 1.0.0 + resolution: "is-fullwidth-code-point@npm:1.0.0" + dependencies: + number-is-nan: "npm:^1.0.0" + checksum: 10c0/12acfcf16142f2d431bf6af25d68569d3198e81b9799b4ae41058247aafcc666b0127d64384ea28e67a746372611fcbe9b802f69175287aba466da3eddd5ba0f + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^2.0.0": + version: 2.0.0 + resolution: "is-fullwidth-code-point@npm:2.0.0" + checksum: 10c0/e58f3e4a601fc0500d8b2677e26e9fe0cd450980e66adb29d85b6addf7969731e38f8e43ed2ec868a09c101a55ac3d8b78902209269f38c5286bc98f5bc1b4d9 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d + languageName: node + linkType: hard + +"is-glob@npm:^4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-observable@npm:^1.1.0": + version: 1.1.0 + resolution: "is-observable@npm:1.1.0" + dependencies: + symbol-observable: "npm:^1.1.0" + checksum: 10c0/cf3166b0822f70ad06e7851e09430166ce658349d54aaa64c93a03320420b9285735821b23164bdce741ff83a86730ac3e53035ce4e2511ed843dbff4105bfa2 + languageName: node + linkType: hard + +"is-promise@npm:^2.1.0": + version: 2.2.2 + resolution: "is-promise@npm:2.2.2" + checksum: 10c0/2dba959812380e45b3df0fb12e7cb4d4528c989c7abb03ececb1d1fd6ab1cbfee956ca9daa587b9db1d8ac3c1e5738cf217bdb3dfd99df8c691be4c00ae09069 + languageName: node + linkType: hard + +"is-stream@npm:^1.1.0": + version: 1.1.0 + resolution: "is-stream@npm:1.1.0" + checksum: 10c0/b8ae7971e78d2e8488d15f804229c6eed7ed36a28f8807a1815938771f4adff0e705218b7dab968270433f67103e4fef98062a0beea55d64835f705ee72c7002 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": "npm:^7.12.3" + "@babel/parser": "npm:^7.14.7" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^6.3.0" + checksum: 10c0/8a1bdf3e377dcc0d33ec32fe2b6ecacdb1e4358fd0eb923d4326bb11c67622c0ceb99600a680f3dad5d29c66fc1991306081e339b4d43d0b8a2ab2e1d910a6ee + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3": + version: 3.2.0 + resolution: "istanbul-reports@npm:3.2.0" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc + languageName: node + linkType: hard + +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" + dependencies: + execa: "npm:^5.0.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b + languageName: node + linkType: hard + +"jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + co: "npm:^4.6.0" + dedent: "npm:^1.0.0" + is-generator-fn: "npm:^2.0.0" + jest-each: "npm:^29.7.0" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + pure-rand: "npm:^6.0.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e + languageName: node + linkType: hard + +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + create-jest: "npm:^29.7.0" + exit: "npm:^0.1.2" + import-local: "npm:^3.0.2" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + yargs: "npm:^17.3.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a + languageName: node + linkType: hard + +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 + languageName: node + linkType: hard + +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" + dependencies: + detect-newline: "npm:^3.0.0" + checksum: 10c0/d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 + languageName: node + linkType: hard + +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 + languageName: node + linkType: hard + +"jest-environment-node@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-node@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/61f04fec077f8b1b5c1a633e3612fc0c9aa79a0ab7b05600683428f1e01a4d35346c474bde6f439f9fcc1a4aa9a2861ff852d079a43ab64b02105d1004b2592b + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b + languageName: node + linkType: hard + +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/graceful-fs": "npm:^4.1.3" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.3.2" + graceful-fs: "npm:^4.2.9" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c + languageName: node + linkType: hard + +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e + languageName: node + linkType: hard + +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" + dependencies: + "@babel/code-frame": "npm:^7.12.13" + "@jest/types": "npm:^29.6.3" + "@types/stack-utils": "npm:^2.0.0" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 + languageName: node + linkType: hard + +"jest-mock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-mock@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + checksum: 10c0/7b9f8349ee87695a309fe15c46a74ab04c853369e5c40952d68061d9dc3159a0f0ed73e215f81b07ee97a9faaf10aebe5877a9d6255068a0977eae6a9ff1d5ac + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac + languageName: node + linkType: hard + +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 10c0/4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b + languageName: node + linkType: hard + +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" + dependencies: + jest-regex-util: "npm:^29.6.3" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d + languageName: node + linkType: hard + +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-pnp-resolver: "npm:^1.2.2" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + resolve: "npm:^1.20.0" + resolve.exports: "npm:^2.0.0" + slash: "npm:^3.0.0" + checksum: 10c0/59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 + languageName: node + linkType: hard + +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/environment": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + graceful-fs: "npm:^4.2.9" + jest-docblock: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-leak-detector: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-resolve: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10c0/2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 + languageName: node + linkType: hard + +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/globals": "npm:^29.7.0" + "@jest/source-map": "npm:^29.6.3" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + cjs-module-lexer: "npm:^1.0.0" + collect-v8-coverage: "npm:^1.0.0" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10c0/7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 + languageName: node + linkType: hard + +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@babel/generator": "npm:^7.7.2" + "@babel/plugin-syntax-jsx": "npm:^7.7.2" + "@babel/plugin-syntax-typescript": "npm:^7.7.2" + "@babel/types": "npm:^7.3.3" + "@jest/expect-utils": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + chalk: "npm:^4.0.0" + expect: "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + natural-compare: "npm:^1.4.0" + pretty-format: "npm:^29.7.0" + semver: "npm:^7.5.3" + checksum: 10c0/6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 + languageName: node + linkType: hard + +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + graceful-fs: "npm:^4.2.9" + picomatch: "npm:^2.2.3" + checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 + languageName: node + linkType: hard + +"jest-validate@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-validate@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + leven: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/a20b930480c1ed68778c739f4739dce39423131bc070cd2505ddede762a5570a256212e9c2401b7ae9ba4d7b7c0803f03c5b8f1561c62348213aba18d9dbece2 + languageName: node + linkType: hard + +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + jest-util: "npm:^29.7.0" + string-length: "npm:^4.0.1" + checksum: 10c0/ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 + languageName: node + linkType: hard + +"jest-worker@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10c0/5570a3a005b16f46c131968b8a5b56d291f9bbb85ff4217e31c80bd8a02e7de799e59a54b95ca28d5c302f248b54cbffde2d177c2f0f52ffcee7504c6eabf660 + languageName: node + linkType: hard + +"jest@npm:^29.7.0": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.7.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/3261f25912f5dd76605e5993d0a126c2b6c346311885d3c483706cd722efe34f697ea0331f654ce27c00a42b426e524518ec89d65ed02ea47df8ad26dcc8ce69 + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonc-parser@npm:^3.2.0": + version: 3.3.1 + resolution: "jsonc-parser@npm:3.3.1" + checksum: 10c0/269c3ae0a0e4f907a914bf334306c384aabb9929bd8c99f909275ebd5c2d3bc70b9bcd119ad794f339dec9f24b6a4ee9cd5a8ab2e6435e730ad4075388fc2ab6 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.2.0 + resolution: "jsonfile@npm:6.2.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7f4f43b08d1869ded8a6822213d13ae3b99d651151d77efd1557ced0889c466296a7d9684e397bd126acf5eb2cfcb605808c3e681d0fdccd2fe5a04b47e76c0d + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b + languageName: node + linkType: hard + +"kleur@npm:^4.1.5": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 10c0/e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a + languageName: node + linkType: hard + +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"listr-silent-renderer@npm:^1.1.1": + version: 1.1.1 + resolution: "listr-silent-renderer@npm:1.1.1" + checksum: 10c0/a13e08ebf863516a757bce4887f05290070772113d89095e9f51a07cf0b11a43a7563a67ff3b287c752c08f6d781fdb2123b02957534e3e0675fb564f2a42e1b + languageName: node + linkType: hard + +"listr-update-renderer@npm:^0.5.0": + version: 0.5.0 + resolution: "listr-update-renderer@npm:0.5.0" + dependencies: + chalk: "npm:^1.1.3" + cli-truncate: "npm:^0.2.1" + elegant-spinner: "npm:^1.0.1" + figures: "npm:^1.7.0" + indent-string: "npm:^3.0.0" + log-symbols: "npm:^1.0.2" + log-update: "npm:^2.3.0" + strip-ansi: "npm:^3.0.1" + peerDependencies: + listr: ^0.14.2 + checksum: 10c0/8ade44bf3dc6146c8e0178000619439e8889792c4689b66be6ce82bd459f5fe462ecb34b05147fb206a8ad60e6d4e6f34c9f48038e18366f867fd972688b8edc + languageName: node + linkType: hard + +"listr-verbose-renderer@npm:^0.5.0": + version: 0.5.0 + resolution: "listr-verbose-renderer@npm:0.5.0" + dependencies: + chalk: "npm:^2.4.1" + cli-cursor: "npm:^2.1.0" + date-fns: "npm:^1.27.2" + figures: "npm:^2.0.0" + checksum: 10c0/041cd1e82da7054f27ae0a914e98b40d15faf9f950ef850578fc6241d3fff3c2d7158a4f6226006e566b4c47bf445be2d254dd1ce5c16569a3a5dcd575bec656 + languageName: node + linkType: hard + +"listr@npm:^0.14.3": + version: 0.14.3 + resolution: "listr@npm:0.14.3" + dependencies: + "@samverschueren/stream-to-observable": "npm:^0.3.0" + is-observable: "npm:^1.1.0" + is-promise: "npm:^2.1.0" + is-stream: "npm:^1.1.0" + listr-silent-renderer: "npm:^1.1.1" + listr-update-renderer: "npm:^0.5.0" + listr-verbose-renderer: "npm:^0.5.0" + p-map: "npm:^2.0.0" + rxjs: "npm:^6.3.3" + checksum: 10c0/753d518218c423f46bee8eeacccecadfd2e414ba9c0f602e7f85fe3f6fa18404dfab0812433aeda4683ee2548358488f597ac1a3d321196baec5d3149b200b10 + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"lodash.camelcase@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.camelcase@npm:4.3.0" + checksum: 10c0/fcba15d21a458076dd309fce6b1b4bf611d84a0ec252cb92447c948c533ac250b95d2e00955801ebc367e5af5ed288b996d75d37d2035260a937008e14eaf432 + languageName: node + linkType: hard + +"lodash.capitalize@npm:^4.2.1": + version: 4.2.1 + resolution: "lodash.capitalize@npm:4.2.1" + checksum: 10c0/b289326497c2e24d6b8afa2af2ca4e068ef6ef007ade36bfb6f70af77ce10ea3f090eeee947d5fdcf2db4bcfa4703c8c10a5857a2b39e308bddfd1d11ad35970 + languageName: node + linkType: hard + +"lodash.kebabcase@npm:^4.1.1": + version: 4.1.1 + resolution: "lodash.kebabcase@npm:4.1.1" + checksum: 10c0/da5d8f41dbb5bc723d4bf9203d5096ca8da804d6aec3d2b56457156ba6c8d999ff448d347ebd97490da853cb36696ea4da09a431499f1ee8deb17b094ecf4e33 + languageName: node + linkType: hard + +"lodash.memoize@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 + languageName: node + linkType: hard + +"lodash.startcase@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.startcase@npm:4.4.0" + checksum: 10c0/bd82aa87a45de8080e1c5ee61128c7aee77bf7f1d86f4ff94f4a6d7438fc9e15e5f03374b947be577a93804c8ad6241f0251beaf1452bf716064eeb657b3a9f0 + languageName: node + linkType: hard + +"lodash@npm:^4.17.21": + version: 4.17.23 + resolution: "lodash@npm:4.17.23" + checksum: 10c0/1264a90469f5bb95d4739c43eb6277d15b6d9e186df4ac68c3620443160fc669e2f14c11e7d8b2ccf078b81d06147c01a8ccced9aab9f9f63d50dcf8cace6bf6 + languageName: node + linkType: hard + +"log-symbols@npm:^1.0.2": + version: 1.0.2 + resolution: "log-symbols@npm:1.0.2" + dependencies: + chalk: "npm:^1.0.0" + checksum: 10c0/c64e1fe41d0d043840f8b592d043b8607a836b846506f525a53d99d578561f02f97b2cba1d2b3c30bae5311d64b308d5a392a9930d252b906a9042fc2877da7a + languageName: node + linkType: hard + +"log-update@npm:^2.3.0": + version: 2.3.0 + resolution: "log-update@npm:2.3.0" + dependencies: + ansi-escapes: "npm:^3.0.0" + cli-cursor: "npm:^2.0.0" + wrap-ansi: "npm:^3.0.1" + checksum: 10c0/9bf21b138801ab4770a2bfa735161cf005b869360eaf5003a84ba64ddc5f5c3ce7217f4f1fa79d9c1f510d792213b2c9800327228e94df05859d19b716215d90 + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.6 + resolution: "lru-cache@npm:11.2.6" + checksum: 10c0/73bbffb298760e71b2bfe8ebc16a311c6a60ceddbba919cfedfd8635c2d125fbfb5a39b71818200e67973b11f8d59c5a9e31d6f90722e340e90393663a66e5cd + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-error@npm:^1.3.6": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.4 + resolution: "make-fetch-happen@npm:15.0.4" + dependencies: + "@gar/promise-retry": "npm:^1.0.0" + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + ssri: "npm:^13.0.0" + checksum: 10c0/b874bf6879fc0b8ef3a3cafdddadea4d956acf94790f8dede1a9d3c74c7886b6cd3eb992616b8e5935e6fd550016a465f10ba51bf6723a0c6f4d98883ae2926b + languageName: node + linkType: hard + +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: "npm:1.0.5" + checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"meeting-baas-recorder@workspace:.": + version: 0.0.0-use.local + resolution: "meeting-baas-recorder@workspace:." + dependencies: + "@meeting-baas/sdk": "npm:^6.0.5" + "@types/jest": "npm:^29.5.5" + "@types/node": "npm:^24.9.2" + axios: "npm:^1.13.1" + dotenv: "npm:^17.2.3" + jest: "npm:^29.7.0" + ts-jest: "npm:^29.1.1" + tsx: "npm:^4.19.3" + twenty-sdk: "npm:0.1.3" + typescript: "npm:^5.9.3" + languageName: unknown + linkType: soft + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mimic-fn@npm:^1.0.0": + version: 1.2.0 + resolution: "mimic-fn@npm:1.2.0" + checksum: 10c0/ad55214aec6094c0af4c0beec1a13787556f8116ed88807cf3f05828500f21f93a9482326bcd5a077ae91e3e8795b4e76b5b4c8bb12237ff0e4043a365516cba + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"minimatch@npm:^10.2.2": + version: 10.2.4 + resolution: "minimatch@npm:10.2.4" + dependencies: + brace-expansion: "npm:^5.0.2" + checksum: 10c0/35f3dfb7b99b51efd46afd378486889f590e7efb10e0f6a10ba6800428cf65c9a8dedb74427d0570b318d749b543dc4e85f06d46d2858bc8cac7e1eb49a95945 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70 + languageName: node + linkType: hard + +"minimist@npm:^1.2.5, minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.2 + resolution: "minipass-fetch@npm:5.0.2" + dependencies: + iconv-lite: "npm:^0.7.2" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^2.0.0" + minizlib: "npm:^3.0.1" + dependenciesMeta: + iconv-lite: + optional: true + checksum: 10c0/ce4ab9f21cfabaead2097d95dd33f485af8072fbc6b19611bce694965393453a1639d641c2bcf1c48f2ea7d41ea7fab8278373f1d0bee4e63b0a5b2cdd0ef649 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^2.0.0": + version: 2.0.0 + resolution: "minipass-sized@npm:2.0.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/f9201696a6f6d68610d04c9c83e3d2e5cb9c026aae1c8cbf7e17f386105cb79c1bb088dbc21bf0b1eb4f3fb5df384fd1e7aa3bf1f33868c416ae8c8a92679db8 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mkdirp@npm:^0.5.1": + version: 0.5.6 + resolution: "mkdirp@npm:0.5.6" + dependencies: + minimist: "npm:^1.2.6" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/e2e2be789218807b58abced04e7b49851d9e46e88a2f9539242cc8a92c9b5c3a0b9bab360bd3014e02a140fc4fbc58e31176c408b493f8a2a6f4986bd7527b01 + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"mute-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 10c0/dce2a9ccda171ec979a3b4f869a102b1343dee35e920146776780de182f16eae459644d187e38d59a3d37adf85685e1c17c38cf7bfda7e39a9880f7a1d10a74c + languageName: node + linkType: hard + +"native-fetch@npm:^4.0.2": + version: 4.0.2 + resolution: "native-fetch@npm:4.0.2" + peerDependencies: + undici: "*" + checksum: 10c0/e3b824721daaa628086d9dcd02e8eb12f0a6c5e13a1d182682bae238d80c9bbf3dfd6314a94692ebe20316aa354476804b4df148201d066b46fc552a5794cfab + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10c0/c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.2.0 + resolution: "node-gyp@npm:12.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/3ed046746a5a7d90950cd8b0547332b06598443f31fe213ef4332a7174c7b7d259e1704835feda79b87d3f02e59d7791842aac60642ede4396ab25fdf0f8f759 + languageName: node + linkType: hard + +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a + languageName: node + linkType: hard + +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10c0/f1e6583b7833ea81880627748d28a3a7ff5703d5409328c216ae57befbced10ce2c991bea86434e8ec39003bd017f70481e2e5f8c1f7e0a7663241f81d6e00e2 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"normalize-path@npm:^2.1.1": + version: 2.1.1 + resolution: "normalize-path@npm:2.1.1" + dependencies: + remove-trailing-separator: "npm:^1.0.1" + checksum: 10c0/db814326ff88057437233361b4c7e9cac7b54815b051b57f2d341ce89b1d8ec8cbd43e7fa95d7652b3b69ea8fcc294b89b8530d556a84d1bdace94229e1e9a8b + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"number-is-nan@npm:^1.0.0": + version: 1.0.1 + resolution: "number-is-nan@npm:1.0.1" + checksum: 10c0/cb97149006acc5cd512c13c1838223abdf202e76ddfa059c5e8e7507aff2c3a78cd19057516885a2f6f5b576543dc4f7b6f3c997cc7df53ae26c260855466df5 + languageName: node + linkType: hard + +"object-assign@npm:^4.1.0": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"onetime@npm:^2.0.0": + version: 2.0.1 + resolution: "onetime@npm:2.0.1" + dependencies: + mimic-fn: "npm:^1.0.0" + checksum: 10c0/b4e44a8c34e70e02251bfb578a6e26d6de6eedbed106cd78211d2fd64d28b6281d54924696554e4e966559644243753ac5df73c87f283b0927533d3315696215 + languageName: node + linkType: hard + +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 10c0/f438450224f8e2687605a8dd318f0db694b6293c5d835ae509a69e97c8de38b6994645337e5577f5001115470414638978cc49da1cdcc25106dad8738dc69990 + languageName: node + linkType: hard + +"p-limit@npm:3.1.0, p-limit@npm:^3.1.0": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-map@npm:^2.0.0": + version: 2.1.0 + resolution: "p-map@npm:2.1.0" + checksum: 10c0/735dae87badd4737a2dd582b6d8f93e49a1b79eabbc9815a4d63a528d5e3523e978e127a21d784cccb637010e32103a40d2aaa3ab23ae60250b1a820ca752043 + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/b35ad37cf6557a87fd057121ce2be7695380c9138d93e87ae928609da259ea0a170fac6f3ef1eb3ece8a068e8b7f2f3adf5bb2374cf4d4a57fe484954fcc9482 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"pirates@npm:^4.0.4": + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a + languageName: node + linkType: hard + +"pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: "npm:^4.0.0" + checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + +"prettier@npm:^2.8.0": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: 10c0/463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a + languageName: node + linkType: hard + +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"prompts@npm:^2.0.1": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: "npm:^3.0.3" + sisteransi: "npm:^1.0.5" + checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + +"pure-rand@npm:^6.0.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 + languageName: node + linkType: hard + +"qs@npm:^6.11.0": + version: 6.15.0 + resolution: "qs@npm:6.15.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10c0/ff341078a78a991d8a48b4524d52949211447b4b1ad907f489cac0770cbc346a28e47304455c0320e5fb000f8762d64b03331e3b71865f663bf351bcba8cdb4b + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"remove-trailing-separator@npm:^1.0.1": + version: 1.1.0 + resolution: "remove-trailing-separator@npm:1.1.0" + checksum: 10c0/3568f9f8f5af3737b4aee9e6e1e8ec4be65a92da9cb27f989e0893714d50aa95ed2ff02d40d1fa35e1b1a234dc9c2437050ef356704a3999feaca6667d9e9bfc + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: 10c0/db91467d9ead311b4111cbd73a4e67fa7820daed2989a32f7023785a2659008c6d119752d9c4ac011ae07e537eb86523adff99804c5fdb39cd3a017f9b401bb6 + languageName: node + linkType: hard + +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: "npm:^5.0.0" + checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 + languageName: node + linkType: hard + +"resolve-from@npm:5.0.0, resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + +"resolve.exports@npm:^2.0.0": + version: 2.0.3 + resolution: "resolve.exports@npm:2.0.3" + checksum: 10c0/1ade1493f4642a6267d0a5e68faeac20b3d220f18c28b140343feb83694d8fed7a286852aef43689d16042c61e2ddb270be6578ad4a13990769e12065191200d + languageName: node + linkType: hard + +"resolve@npm:^1.20.0": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 + languageName: node + linkType: hard + +"restore-cursor@npm:^2.0.0": + version: 2.0.0 + resolution: "restore-cursor@npm:2.0.0" + dependencies: + onetime: "npm:^2.0.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/f5b335bee06f440445e976a7031a3ef53691f9b7c4a9d42a469a0edaf8a5508158a0d561ff2b26a1f4f38783bcca2c0e5c3a44f927326f6694d5b44d7a4993e6 + languageName: node + linkType: hard + +"retry@npm:^0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa + languageName: node + linkType: hard + +"rimraf@npm:^2.6.3": + version: 2.7.1 + resolution: "rimraf@npm:2.7.1" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: ./bin.js + checksum: 10c0/4eef73d406c6940927479a3a9dee551e14a54faf54b31ef861250ac815172bade86cc6f7d64a4dc5e98b65e4b18a2e1c9ff3b68d296be0c748413f092bb0dd40 + languageName: node + linkType: hard + +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 10c0/b18b562ae37c3020083dcaae29642e4cc360c824fbfb6b7d50d809a9d5227bb986152d09310255842c8dce40526e82ca768f02f00806c91ba92a8dfa6159cb85 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"rxjs@npm:^6.3.3": + version: 6.6.7 + resolution: "rxjs@npm:6.6.7" + dependencies: + tslib: "npm:^1.9.0" + checksum: 10c0/e556a13a9aa89395e5c9d825eabcfa325568d9c9990af720f3f29f04a888a3b854f25845c2b55875d875381abcae2d8100af9cacdc57576e7ed6be030a01d2fe + languageName: node + linkType: hard + +"rxjs@npm:^7.8.1": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.3": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"slice-ansi@npm:0.0.4": + version: 0.0.4 + resolution: "slice-ansi@npm:0.0.4" + checksum: 10c0/997d4cc73e34aa8c0f60bdb71701b16c062cc4acd7a95e3b10e8c05d790eb5e735d9b470270dc6f443b1ba21492db7ceb849d5c93011d1256061bf7ed7216c7a + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.1 + resolution: "ssri@npm:13.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/cf6408a18676c57ff2ed06b8a20dc64bb3e748e5c7e095332e6aecaa2b8422b1e94a739a8453bf65156a8a47afe23757ba4ab52d3ea3b62322dc40875763e17a + languageName: node + linkType: hard + +"stack-utils@npm:^2.0.3": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: "npm:^2.0.0" + checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a + languageName: node + linkType: hard + +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: "npm:^1.0.2" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c + languageName: node + linkType: hard + +"string-width@npm:^1.0.1": + version: 1.0.2 + resolution: "string-width@npm:1.0.2" + dependencies: + code-point-at: "npm:^1.0.0" + is-fullwidth-code-point: "npm:^1.0.0" + strip-ansi: "npm:^3.0.0" + checksum: 10c0/c558438baed23a9ab9370bb6a939acbdb2b2ffc517838d651aad0f5b2b674fb85d460d9b1d0b6a4c210dffd09e3235222d89a5bd4c0c1587f78b2bb7bc00c65e + languageName: node + linkType: hard + +"string-width@npm:^2.1.1": + version: 2.1.1 + resolution: "string-width@npm:2.1.1" + dependencies: + is-fullwidth-code-point: "npm:^2.0.0" + strip-ansi: "npm:^4.0.0" + checksum: 10c0/e5f2b169fcf8a4257a399f95d069522f056e92ec97dbdcb9b0cdf14d688b7ca0b1b1439a1c7b9773cd79446cbafd582727279d6bfdd9f8edd306ea5e90e5b610 + languageName: node + linkType: hard + +"string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": + version: 3.0.1 + resolution: "strip-ansi@npm:3.0.1" + dependencies: + ansi-regex: "npm:^2.0.0" + checksum: 10c0/f6e7fbe8e700105dccf7102eae20e4f03477537c74b286fd22cfc970f139002ed6f0d9c10d0e21aa9ed9245e0fa3c9275930e8795c5b947da136e4ecb644a70f + languageName: node + linkType: hard + +"strip-ansi@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-ansi@npm:4.0.0" + dependencies: + ansi-regex: "npm:^3.0.0" + checksum: 10c0/d75d9681e0637ea316ddbd7d4d3be010b1895a17e885155e0ed6a39755ae0fd7ef46e14b22162e66a62db122d3a98ab7917794e255532ab461bb0a04feb03e7d + languageName: node + linkType: hard + +"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"supports-color@npm:^2.0.0": + version: 2.0.0 + resolution: "supports-color@npm:2.0.0" + checksum: 10c0/570e0b63be36cccdd25186350a6cb2eaad332a95ff162fa06d9499982315f2fe4217e69dd98e862fbcd9c81eaff300a825a1fe7bf5cc752e5b84dfed042b0dda + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"symbol-observable@npm:^1.1.0": + version: 1.2.0 + resolution: "symbol-observable@npm:1.2.0" + checksum: 10c0/009fee50798ef80ed4b8195048288f108b03de162db07493f2e1fd993b33fafa72d659e832b584da5a2427daa78e5a738fb2a9ab027ee9454252e0bedbcd1fdc + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.9 + resolution: "tar@npm:7.5.9" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/e870beb1b2477135ca2abe86b2d18f7b35d0a4e3a37bbc523d3b8f7adca268dfab543f26528a431d569897f8c53a7cac745cdfbc4411c2f89aeeacc652b81b0a + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^7.1.4" + minimatch: "npm:^3.0.4" + checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: "npm:~1.0.2" + checksum: 10c0/69863947b8c29cabad43fe0ce65cec5bb4b481d15d4b4b21e036b060b3edbf3bc7a5541de1bacb437bb3f7c4538f669752627fdf9b4aaf034cebd172ba373408 + languageName: node + linkType: hard + +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"ts-jest@npm:^29.1.1": + version: 29.4.6 + resolution: "ts-jest@npm:29.4.6" + dependencies: + bs-logger: "npm:^0.2.6" + fast-json-stable-stringify: "npm:^2.1.0" + handlebars: "npm:^4.7.8" + json5: "npm:^2.2.3" + lodash.memoize: "npm:^4.1.2" + make-error: "npm:^1.3.6" + semver: "npm:^7.7.3" + type-fest: "npm:^4.41.0" + yargs-parser: "npm:^21.1.1" + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 || ^30.0.0 + "@jest/types": ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: ">=4.3 <6" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + bin: + ts-jest: cli.js + checksum: 10c0/013dda99ac938cd4b94bae9323ed1b633cd295976c256d596d01776866188078fe7b82b8b3ebd05deb401b27b5618d9d76208eded2568661240ecf9694a5c933 + languageName: node + linkType: hard + +"tslib@npm:^1.9.0": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tslib@npm:^2.1.0, tslib@npm:^2.4.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"tsx@npm:^4.19.3": + version: 4.21.0 + resolution: "tsx@npm:4.21.0" + dependencies: + esbuild: "npm:~0.27.0" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/f5072923cd8459a1f9a26df87823a2ab5754641739d69df2a20b415f61814322b751fa6be85db7c6ec73cf68ba8fac2fd1cfc76bdb0aa86ded984d84d5d2126b + languageName: node + linkType: hard + +"twenty-sdk@npm:0.1.3": + version: 0.1.3 + resolution: "twenty-sdk@npm:0.1.3" + dependencies: + "@genql/cli": "npm:^3.0.3" + axios: "npm:^1.6.0" + chalk: "npm:^5.3.0" + chokidar: "npm:^4.0.0" + commander: "npm:^12.0.0" + dotenv: "npm:^16.4.0" + fs-extra: "npm:^11.2.0" + graphql: "npm:^16.8.1" + graphql-sse: "npm:^2.5.4" + inquirer: "npm:^10.0.0" + jsonc-parser: "npm:^3.2.0" + lodash.camelcase: "npm:^4.3.0" + lodash.capitalize: "npm:^4.2.1" + lodash.kebabcase: "npm:^4.1.1" + lodash.startcase: "npm:^4.4.0" + typescript: "npm:^5.9.2" + uuid: "npm:^13.0.0" + bin: + twenty: dist/cli.cjs + checksum: 10c0/bb7a43331c61d34e250c7850e1746e6aac09fa8c552e0e19598b8ad44f56e3a9d51dc985d07da4c205ed179f49ba1ede52c6a9ed8be002689f70b571d1ac9c6d + languageName: node + linkType: hard + +"type-detect@npm:4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-fest@npm:^4.41.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + +"typescript@npm:^5.9.2, typescript@npm:^5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.9.2#optional!builtin, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"uglify-js@npm:^3.1.4": + version: 3.19.3 + resolution: "uglify-js@npm:3.19.3" + bin: + uglifyjs: bin/uglifyjs + checksum: 10c0/83b0a90eca35f778e07cad9622b80c448b6aad457c9ff8e568afed978212b42930a95f9e1be943a1ffa4258a3340fbb899f41461131c05bb1d0a9c303aed8479 + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard + +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a + languageName: node + linkType: hard + +"undici-types@npm:~7.18.0": + version: 7.18.2 + resolution: "undici-types@npm:7.18.2" + checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d + languageName: node + linkType: hard + +"undici@npm:^5.18.0": + version: 5.29.0 + resolution: "undici@npm:5.29.0" + dependencies: + "@fastify/busboy": "npm:^2.0.0" + checksum: 10c0/e4e4d631ca54ee0ad82d2e90e7798fa00a106e27e6c880687e445cc2f13b4bc87c5eba2a88c266c3eecffb18f26e227b778412da74a23acc374fca7caccec49b + languageName: node + linkType: hard + +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: "npm:^6.0.0" + checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c + languageName: node + linkType: hard + +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a + languageName: node + linkType: hard + +"unixify@npm:^1.0.0": + version: 1.0.0 + resolution: "unixify@npm:1.0.0" + dependencies: + normalize-path: "npm:^2.1.1" + checksum: 10c0/8b89100619ebde9f0ab4024a4d402316fb7b1d4853723410fc828944e8d3d01480f210cddf94d9a1699559f8180d861eb6323da8011b7bcc1bbaf6a11a5b1f1e + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.2.0": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec + languageName: node + linkType: hard + +"uuid@npm:^13.0.0": + version: 13.0.0 + resolution: "uuid@npm:13.0.0" + bin: + uuid: dist-node/bin/uuid + checksum: 10c0/950e4c18d57fef6c69675344f5700a08af21e26b9eff2bf2180427564297368c538ea11ac9fb2e6528b17fc3966a9fd2c5049361b0b63c7d654f3c550c9b3d67 + languageName: node + linkType: hard + +"v8-to-istanbul@npm:^9.0.1": + version: 9.3.0 + resolution: "v8-to-istanbul@npm:9.3.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.12" + "@types/istanbul-lib-coverage": "npm:^2.0.1" + convert-source-map: "npm:^2.0.0" + checksum: 10c0/968bcf1c7c88c04df1ffb463c179558a2ec17aa49e49376120504958239d9e9dad5281aa05f2a78542b8557f2be0b0b4c325710262f3b838b40d703d5ed30c23 + languageName: node + linkType: hard + +"value-or-promise@npm:^1.0.12": + version: 1.0.12 + resolution: "value-or-promise@npm:1.0.12" + checksum: 10c0/b75657b74e4d17552bd88e0c2857020fbab34a4d091dc058db18c470e7da0336067e72c130b3358e3321ac0a6ff11c0b92b67a382318a3705ad5d57de7ff3262 + languageName: node + linkType: hard + +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: "npm:1.0.12" + checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e + languageName: node + linkType: hard + +"which-module@npm:^2.0.0": + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 10c0/087038e7992649eaffa6c7a4f3158d5b53b14cf5b6c1f0e043dccfacb1ba179d12f17545d5b85ebd94a42ce280a6fe65d0cbcab70f4fc6daad1dfae85e0e6a3e + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.1 + resolution: "which@npm:6.0.1" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/7e710e54ea36d2d6183bee2f9caa27a3b47b9baf8dee55a199b736fcf85eab3b9df7556fca3d02b50af7f3dfba5ea3a45644189836df06267df457e354da66d5 + languageName: node + linkType: hard + +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 10c0/7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 + languageName: node + linkType: hard + +"wrap-ansi@npm:^3.0.1": + version: 3.0.1 + resolution: "wrap-ansi@npm:3.0.1" + dependencies: + string-width: "npm:^2.1.1" + strip-ansi: "npm:^4.0.0" + checksum: 10c0/ad6fed8f242c26755badaf452da154122d0d862f8b7aab56e758466857f230efafdc5fbffca026650b947ac3fc0eb563df5c05b9e2190a52a4a68f4eef3d4555 + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^3.0.7" + checksum: 10c0/a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 + languageName: node + linkType: hard + +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 10c0/308a2efd7cc296ab2c0f3b9284fd4827be01cfeb647b3ba18230e3a416eb1bc887ac050de9f8c4fd9e7856b2e8246e05d190b53c96c5ad8d8cb56dffb6f81024 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yargs-parser@npm:^18.1.2": + version: 18.1.3 + resolution: "yargs-parser@npm:18.1.3" + dependencies: + camelcase: "npm:^5.0.0" + decamelize: "npm:^1.2.0" + checksum: 10c0/25df918833592a83f52e7e4f91ba7d7bfaa2b891ebf7fe901923c2ee797534f23a176913ff6ff7ebbc1cc1725a044cc6a6539fed8bfd4e13b5b16376875f9499 + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^15.3.1": + version: 15.4.1 + resolution: "yargs@npm:15.4.1" + dependencies: + cliui: "npm:^6.0.0" + decamelize: "npm:^1.2.0" + find-up: "npm:^4.1.0" + get-caller-file: "npm:^2.0.1" + require-directory: "npm:^2.1.1" + require-main-filename: "npm:^2.0.0" + set-blocking: "npm:^2.0.0" + string-width: "npm:^4.2.0" + which-module: "npm:^2.0.0" + y18n: "npm:^4.0.0" + yargs-parser: "npm:^18.1.2" + checksum: 10c0/f1ca680c974333a5822732825cca7e95306c5a1e7750eb7b973ce6dc4f97a6b0a8837203c8b194f461969bfe1fb1176d1d423036635285f6010b392fa498ab2d + languageName: node + linkType: hard + +"yargs@npm:^17.3.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"yoctocolors-cjs@npm:^2.1.2": + version: 2.1.3 + resolution: "yoctocolors-cjs@npm:2.1.3" + checksum: 10c0/584168ef98eb5d913473a4858dce128803c4a6cd87c0f09e954fa01126a59a33ab9e513b633ad9ab953786ed16efdd8c8700097a51635aafaeed3fef7712fa79 + languageName: node + linkType: hard + +"zod@npm:3.24.2": + version: 3.24.2 + resolution: "zod@npm:3.24.2" + checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565 + languageName: node + linkType: hard From 5af0c3a2c44be746ed4f9a96c9199f7674048102 Mon Sep 17 00:00:00 2001 From: Lazare-42 Date: Thu, 16 Apr 2026 23:16:22 +0200 Subject: [PATCH 2/5] Support application custom tab rendering and manifest migration --- .../tabs/SettingsApplicationCustomTab.tsx | 21 ++++++++++++++----- .../application-manifest-migration.service.ts | 6 +++++- ...ew-manifest-to-universal-flat-view.util.ts | 3 ++- .../src/application/viewManifestType.ts | 1 + 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationCustomTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationCustomTab.tsx index 122b30dce85..b5cd692cad4 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationCustomTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationCustomTab.tsx @@ -1,6 +1,9 @@ import { styled } from '@linaria/react'; import { Suspense, lazy } from 'react'; +import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { PageLayoutType } from '~/generated-metadata/graphql'; + const FrontComponentRenderer = lazy(() => import('@/front-components/components/FrontComponentRenderer').then( (module) => ({ default: module.FrontComponentRenderer }), @@ -22,11 +25,19 @@ export const SettingsApplicationCustomTab = ({ }: SettingsApplicationCustomTabProps) => { return ( - - - + + + + + ); }; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts index 7d10513a19c..e8e20a73541 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts @@ -329,11 +329,15 @@ export class ApplicationManifestMigrationService { } if (isDefined(role.permissionFlags) && role.permissionFlags.length > 0) { + const permissionFlagKeys = role.permissionFlags.map((flag) => + typeof flag === 'string' ? flag : (flag as { flag: string }).flag, + ); + await this.permissionFlagService.upsertPermissionFlags({ workspaceId, input: { roleId, - permissionFlagKeys: role.permissionFlags, + permissionFlagKeys, }, }); } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts index 497de38ab89..6ea60ceb1e8 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts @@ -33,7 +33,8 @@ export const fromViewManifestToUniversalFlatView = ({ kanbanAggregateOperationFieldMetadataUniversalIdentifier: null, calendarLayout: null, calendarFieldMetadataUniversalIdentifier: null, - mainGroupByFieldMetadataUniversalIdentifier: null, + mainGroupByFieldMetadataUniversalIdentifier: + viewManifest.mainGroupByFieldMetadataUniversalIdentifier ?? null, shouldHideEmptyGroups: false, anyFieldFilterValue: null, createdByUserWorkspaceId: null, diff --git a/packages/twenty-shared/src/application/viewManifestType.ts b/packages/twenty-shared/src/application/viewManifestType.ts index 479f4cddefc..288e5afee78 100644 --- a/packages/twenty-shared/src/application/viewManifestType.ts +++ b/packages/twenty-shared/src/application/viewManifestType.ts @@ -62,6 +62,7 @@ export type ViewManifest = SyncableEntityOptions & { isCompact?: boolean; visibility?: ViewVisibility; openRecordIn?: ViewOpenRecordIn; + mainGroupByFieldMetadataUniversalIdentifier?: string; fields?: ViewFieldManifest[]; filters?: ViewFilterManifest[]; filterGroups?: ViewFilterGroupManifest[]; From e4e064008a33cc82f18369446587017aaf8a3299 Mon Sep 17 00:00:00 2001 From: Lazare-42 Date: Thu, 16 Apr 2026 23:16:36 +0200 Subject: [PATCH 3/5] Extend meeting recorder settings and transcript sync --- .../application.config.ts | 5 - .../meeting-baas-recorder/package.json | 4 +- ...entry-message-on-workspace-member.field.ts | 19 + .../bot-name-on-workspace-member.field.ts | 19 + .../meeting-baas-settings.front-component.tsx | 284 +++++---- .../meeting-baas-recorder/src/index.ts | 4 +- .../src/logic-functions/schedule-bot.ts | 47 +- .../src/meeting-baas-api-client.ts | 69 ++- .../src/twenty-sync-service.ts | 3 +- .../src/webhook-handler.ts | 43 +- .../community/meeting-baas-recorder/yarn.lock | 541 ++++++++---------- 11 files changed, 631 insertions(+), 407 deletions(-) create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-name-on-workspace-member.field.ts diff --git a/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts b/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts index d96d7f7f82c..a65fab6c741 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts @@ -15,10 +15,5 @@ export default defineApplication({ isSecret: true, value: '', }, - AUTO_CREATE_CONTACTS: { - universalIdentifier: 'f4a5b6c7-8d9e-0f1a-2b3c-d4e5f6a7b8c9', - description: 'Whether to auto-create contacts for unknown participants (true/false)', - value: 'true', - }, }, }); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/package.json b/packages/twenty-apps/community/meeting-baas-recorder/package.json index 754bcc70af8..c12fd7e90de 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/package.json +++ b/packages/twenty-apps/community/meeting-baas-recorder/package.json @@ -14,7 +14,7 @@ "dependencies": { "@meeting-baas/sdk": "^6.0.5", "axios": "^1.13.1", -"twenty-sdk": "0.1.3" + "twenty-sdk": "0.1.3" }, "devDependencies": { "@emotion/styled": "^11.14.0", @@ -24,6 +24,6 @@ "jest": "^29.7.0", "react": "^18.2.0", "ts-jest": "^29.1.1", -"typescript": "^5.9.3" + "typescript": "^5.9.3" } } diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts new file mode 100644 index 00000000000..04fbf96cf5c --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts @@ -0,0 +1,19 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +export const BOT_ENTRY_MESSAGE_FIELD_ID = 'b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e'; + +export default defineField({ + universalIdentifier: BOT_ENTRY_MESSAGE_FIELD_ID, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier, + type: FieldType.TEXT, + name: 'botEntryMessage', + label: 'Bot Entry Message', + icon: 'IconMessage', + description: 'Message the bot posts in the meeting chat when it joins (max 500 characters)', + defaultValue: "''", +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-name-on-workspace-member.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-name-on-workspace-member.field.ts new file mode 100644 index 00000000000..1d545d2fc65 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-name-on-workspace-member.field.ts @@ -0,0 +1,19 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +export const BOT_NAME_FIELD_ID = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'; + +export default defineField({ + universalIdentifier: BOT_NAME_FIELD_ID, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier, + type: FieldType.TEXT, + name: 'botName', + label: 'Bot Name', + icon: 'IconRobot', + description: 'Name displayed for the recording bot when it joins meetings', + defaultValue: "'Twenty CRM Recorder'", +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx b/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx index 7fc1559b803..0d6c67f71c5 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx @@ -1,12 +1,15 @@ import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; import { defineFrontComponent } from 'twenty-sdk'; +import { Callout, H2Title, Status, themeCssVariables } from 'twenty-sdk/ui'; type RecordingPreference = 'RECORD_ALL' | 'RECORD_ORGANIZED' | 'RECORD_NONE'; type WorkspaceMember = { id: string; recordingPreference?: RecordingPreference; + botName?: string; + botEntryMessage?: string; }; type CalendarChannel = { @@ -16,41 +19,25 @@ type CalendarChannel = { const StyledContainer = styled.div` display: flex; flex-direction: column; - gap: 16px; + gap: ${themeCssVariables.spacing[4]}; width: 100%; `; -const StyledSectionTitle = styled.h3` - color: #333; - font-family: 'Inter', sans-serif; - font-size: 15px; - font-weight: 600; - margin: 0 0 4px 0; -`; - -const StyledSectionSubtitle = styled.p` - color: #818181; - font-family: 'Inter', sans-serif; - font-size: 13px; - font-weight: 400; - margin: 0 0 12px 0; -`; - const StyledCard = styled.div` align-items: center; - background: #fff; - border: 1px solid #ebebeb; - border-radius: 8px; + background: ${themeCssVariables.background.primary}; + border: 1px solid ${themeCssVariables.border.color.light}; + border-radius: ${themeCssVariables.border.radius.sm}; display: flex; - gap: 12px; - padding: 16px; + gap: ${themeCssVariables.spacing[3]}; + padding: ${themeCssVariables.spacing[4]}; `; const StyledIconContainer = styled.div` align-items: center; - background: #f5f5f5; - border-radius: 8px; - color: #666; + background: ${themeCssVariables.background.tertiary}; + border-radius: ${themeCssVariables.border.radius.sm}; + color: ${themeCssVariables.font.color.secondary}; display: flex; flex-shrink: 0; height: 40px; @@ -67,57 +54,45 @@ const StyledTextContainer = styled.div` `; const StyledTitle = styled.span` - color: #333; - font-family: 'Inter', sans-serif; - font-size: 14px; - font-weight: 500; + color: ${themeCssVariables.font.color.primary}; + font-family: ${themeCssVariables.font.family}; + font-size: ${themeCssVariables.font.size.md}; + font-weight: ${themeCssVariables.font.weight.medium}; `; const StyledDescription = styled.span` - color: #818181; - font-family: 'Inter', sans-serif; - font-size: 13px; -`; - -const StyledStatusBadge = styled.span<{ connected: boolean }>` - align-items: center; - background: ${({ connected }) => (connected ? '#10b981' : '#ef4444')}; - border-radius: 4px; - color: #fff; - display: inline-flex; - flex-shrink: 0; - font-family: 'Inter', sans-serif; - font-size: 13px; - font-weight: 500; - height: 32px; - padding: 0 12px; - white-space: nowrap; + color: ${themeCssVariables.font.color.tertiary}; + font-family: ${themeCssVariables.font.family}; + font-size: ${themeCssVariables.font.size.sm}; `; const StyledRadioGroup = styled.div` display: flex; flex-direction: column; - gap: 8px; + gap: ${themeCssVariables.spacing[2]}; `; const StyledRadioLabel = styled.label<{ selected: boolean }>` align-items: center; - background: ${({ selected }) => (selected ? '#f0f0ff' : '#fff')}; - border: 1px solid ${({ selected }) => (selected ? '#5e5adb' : '#ebebeb')}; - border-radius: 8px; + background: ${({ selected }) => + selected ? themeCssVariables.accent.accent1 : themeCssVariables.background.primary}; + border: 1px solid + ${({ selected }) => + selected ? themeCssVariables.accent.primary : themeCssVariables.border.color.light}; + border-radius: ${themeCssVariables.border.radius.sm}; cursor: pointer; display: flex; - gap: 12px; - padding: 12px 16px; + gap: ${themeCssVariables.spacing[3]}; + padding: ${themeCssVariables.spacing[3]} ${themeCssVariables.spacing[4]}; transition: all 0.15s ease; &:hover { - border-color: #5e5adb; + border-color: ${themeCssVariables.accent.primary}; } `; const StyledRadioInput = styled.input` - accent-color: #5e5adb; + accent-color: ${themeCssVariables.accent.primary}; cursor: pointer; height: 16px; margin: 0; @@ -131,29 +106,59 @@ const StyledRadioTextContainer = styled.div` `; const StyledRadioTitle = styled.span` - color: #333; - font-family: 'Inter', sans-serif; - font-size: 14px; - font-weight: 500; + color: ${themeCssVariables.font.color.primary}; + font-family: ${themeCssVariables.font.family}; + font-size: ${themeCssVariables.font.size.md}; + font-weight: ${themeCssVariables.font.weight.medium}; `; const StyledRadioDescription = styled.span` - color: #818181; - font-family: 'Inter', sans-serif; - font-size: 12px; + color: ${themeCssVariables.font.color.tertiary}; + font-family: ${themeCssVariables.font.family}; + font-size: ${themeCssVariables.font.size.xs}; `; -const StyledBanner = styled.div<{ variant: 'info' | 'warning' }>` - align-items: center; - background: ${({ variant }) => (variant === 'warning' ? '#fef3c7' : '#eff6ff')}; - border: 1px solid ${({ variant }) => (variant === 'warning' ? '#f59e0b' : '#3b82f6')}; - border-radius: 8px; - color: ${({ variant }) => (variant === 'warning' ? '#92400e' : '#1e40af')}; +const StyledFieldGroup = styled.div` display: flex; - font-family: 'Inter', sans-serif; - font-size: 13px; - gap: 8px; - padding: 12px 16px; + flex-direction: column; + gap: ${themeCssVariables.spacing[3]}; +`; + +const StyledFieldLabel = styled.label` + color: ${themeCssVariables.font.color.primary}; + font-family: ${themeCssVariables.font.family}; + font-size: ${themeCssVariables.font.size.sm}; + font-weight: ${themeCssVariables.font.weight.medium}; +`; + +const StyledTextInput = styled.input` + background: ${themeCssVariables.background.primary}; + border: 1px solid ${themeCssVariables.border.color.light}; + border-radius: ${themeCssVariables.border.radius.sm}; + color: ${themeCssVariables.font.color.primary}; + font-family: ${themeCssVariables.font.family}; + font-size: ${themeCssVariables.font.size.md}; + margin-top: 4px; + outline: none; + padding: 10px 12px; + width: 100%; + + &:focus { + border-color: ${themeCssVariables.accent.primary}; + } + + &::placeholder { + color: ${themeCssVariables.font.color.light}; + } +`; + +const StyledCharCount = styled.span<{ over: boolean }>` + color: ${({ over }) => + over ? themeCssVariables.color.red : themeCssVariables.font.color.tertiary}; + font-family: ${themeCssVariables.font.family}; + font-size: ${themeCssVariables.font.size.xxs}; + margin-top: 2px; + text-align: right; `; const PREFERENCE_OPTIONS: Array<{ @@ -188,16 +193,23 @@ const fetchCurrentWorkspaceMember = async (): Promise => { const updateWorkspaceMember = async ( memberId: string, - recordingPreference: RecordingPreference, + fields: { + recordingPreference?: RecordingPreference; + botName?: string | null; + botEntryMessage?: string | null; + }, ): Promise => { - await fetch(`${process.env.TWENTY_API_URL}/rest/workspaceMembers/${memberId}`, { + const response = await fetch(`${process.env.TWENTY_API_URL}/rest/workspaceMembers/${memberId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TWENTY_API_KEY}`, 'Content-Type': 'application/json', }, - body: JSON.stringify({ recordingPreference }), + body: JSON.stringify(fields), }); + if (!response.ok) { + throw new Error(`PATCH failed: ${response.status}`); + } }; const fetchCalendarChannels = async (): Promise => { @@ -211,6 +223,8 @@ const fetchCalendarChannels = async (): Promise => { const MeetingBaasSettings = () => { const [member, setMember] = useState(null); const [preference, setPreference] = useState('RECORD_NONE'); + const [botName, setBotName] = useState(''); + const [botEntryMessage, setBotEntryMessage] = useState(''); const [hasCalendar, setHasCalendar] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); @@ -222,6 +236,8 @@ const MeetingBaasSettings = () => { .then(([memberData, channels]) => { setMember(memberData); setPreference(memberData.recordingPreference ?? 'RECORD_NONE'); + setBotName(memberData.botName ?? ''); + setBotEntryMessage(memberData.botEntryMessage ?? ''); setHasCalendar(channels.length > 0); }) .catch(() => { @@ -232,12 +248,44 @@ const MeetingBaasSettings = () => { const handlePreferenceChange = async (newPreference: RecordingPreference) => { if (!member || isSaving) return; + const previousPreference = preference; setIsSaving(true); setPreference(newPreference); try { - await updateWorkspaceMember(member.id, newPreference); + await updateWorkspaceMember(member.id, { recordingPreference: newPreference }); + setMember({ ...member, recordingPreference: newPreference }); + } catch { + setPreference(previousPreference); + } finally { + setIsSaving(false); + } + }; + + const handleBotNameBlur = async () => { + if (!member || isSaving) return; + const trimmed = botName.trim(); + if (trimmed === (member.botName ?? '')) return; + setIsSaving(true); + try { + await updateWorkspaceMember(member.id, { botName: trimmed || null }); + setMember({ ...member, botName: trimmed || undefined }); + } catch { + setBotName(member.botName ?? ''); + } finally { + setIsSaving(false); + } + }; + + const handleEntryMessageBlur = async () => { + if (!member || isSaving) return; + const trimmed = botEntryMessage.trim().slice(0, 500); + if (trimmed === (member.botEntryMessage ?? '')) return; + setIsSaving(true); + try { + await updateWorkspaceMember(member.id, { botEntryMessage: trimmed || null }); + setMember({ ...member, botEntryMessage: trimmed || undefined }); } catch { - setPreference(member.recordingPreference ?? 'RECORD_NONE'); + setBotEntryMessage(member.botEntryMessage ?? ''); } finally { setIsSaving(false); } @@ -246,8 +294,7 @@ const MeetingBaasSettings = () => { if (isLoading) { return ( - Meeting Recording - Loading settings... + ); } @@ -256,10 +303,10 @@ const MeetingBaasSettings = () => { {/* API Key Status */}
- Meeting BaaS Connection - - Meeting BaaS records your meetings and syncs transcripts into Twenty - + 🎥 @@ -272,26 +319,28 @@ const MeetingBaasSettings = () => { : 'No API key configured — add MEETING_BAAS_API_KEY in app variables'} - - {apiKeyConfigured ? '✓ Connected' : '✗ Not Set'} - +
{/* Calendar Connection Banner */} {hasCalendar === false && ( - - No calendar connected — connect your Google or Microsoft calendar in Settings → Accounts - to enable automatic meeting recording. - + )} {/* Recording Preference */}
- Recording Preference - - Choose which meetings are automatically recorded when they have a conference link - + {PREFERENCE_OPTIONS.map((option) => ( @@ -312,11 +361,50 @@ const MeetingBaasSettings = () => {
+ {/* Bot Customization */} +
+ + +
+ Bot name + ) => setBotName(e.target.value)} + onBlur={handleBotNameBlur} + disabled={isSaving} + /> +
+
+ Entry message + ) => + setBotEntryMessage(e.target.value.slice(0, 500)) + } + onBlur={handleEntryMessageBlur} + disabled={isSaving} + maxLength={500} + /> + = 500}> + {botEntryMessage.length}/500 + +
+
+
+ {preference !== 'RECORD_NONE' && !apiKeyConfigured && ( - - Recording is enabled but no API key is set. Add MEETING_BAAS_API_KEY in the app's - variables to start recording. - + )}
); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts index 3750c4114e3..01e10094a00 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts @@ -1,8 +1,10 @@ // SDK entity definitions (discovered automatically by twenty app build) export { default as RecordingObject, RECORDING_UNIVERSAL_IDENTIFIER } from './objects/recording'; -// Field on standard object +// Fields on standard object export { default as RecordingPreferenceField } from './fields/recording-preference-on-workspace-member.field'; +export { default as BotNameField } from './fields/bot-name-on-workspace-member.field'; +export { default as BotEntryMessageField } from './fields/bot-entry-message-on-workspace-member.field'; // Front component export { default as MeetingBaasSettingsComponent } from './front-components/meeting-baas-settings.front-component'; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts index 1e283dc6c11..93c5f78a08d 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts @@ -8,11 +8,17 @@ const logger = createLogger('schedule-bot'); type RecordingPreference = 'RECORD_ALL' | 'RECORD_ORGANIZED' | 'RECORD_NONE'; +type BotSettings = { + preference: RecordingPreference; + botName: string; + botEntryMessage: string; +}; + const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? ''; -const fetchWorkspaceMemberPreference = async ( +const fetchWorkspaceMemberBotSettings = async ( workspaceMemberId: string, -): Promise => { +): Promise => { try { const response = await axios({ method: 'GET', @@ -20,11 +26,31 @@ const fetchWorkspaceMemberPreference = async ( url: `${getRestApiUrl()}/workspaceMembers/${workspaceMemberId}`, }); const memberData = response.data?.data ?? response.data; - return (memberData?.recordingPreference as RecordingPreference) ?? 'RECORD_NONE'; + return { + preference: (memberData?.recordingPreference as RecordingPreference) ?? 'RECORD_NONE', + botName: (memberData?.botName as string) || 'Twenty CRM Recorder', + botEntryMessage: (memberData?.botEntryMessage as string) || '', + }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); - logger.warn(`Failed to fetch workspace member preference: ${msg}`); - return 'RECORD_NONE'; + logger.warn(`Failed to fetch workspace member bot settings: ${msg}`); + return { preference: 'RECORD_NONE', botName: 'Twenty CRM Recorder', botEntryMessage: '' }; + } +}; + +const fetchCalendarEventTitle = async ( + calendarEventId: string, +): Promise => { + try { + const response = await axios({ + method: 'GET', + headers: restHeaders(), + url: `${getRestApiUrl()}/calendarEvents/${calendarEventId}`, + }); + const eventData = response.data?.data ?? response.data; + return (eventData?.title as string) || ''; + } catch { + return ''; } }; @@ -72,8 +98,8 @@ export const scheduleBot = async ( return null; } - // Check recording preference - const preference = await fetchWorkspaceMemberPreference(ownership.workspaceMemberId); + // Check recording preference and bot settings + const { preference, botName, botEntryMessage } = await fetchWorkspaceMemberBotSettings(ownership.workspaceMemberId); if (preference === 'RECORD_NONE') { logger.debug(`Workspace member ${ownership.workspaceMemberName ?? ownership.workspaceMemberId} has recording disabled`); return null; @@ -88,14 +114,21 @@ export const scheduleBot = async ( } } + // Fetch calendar event title for recording metadata + const meetingTitle = await fetchCalendarEventTitle(calendarEventId); + // Schedule the bot const client = new MeetingBaasApiClient(apiKey); const botId = await client.createScheduledBot({ meetingUrl: conferenceUrl, joinAt: startsAt, + botName, + ...(botEntryMessage && { entryMessage: botEntryMessage }), extra: { calendarEventId, workspaceMemberId: ownership.workspaceMemberId, + meeting_url: conferenceUrl, + meeting_title: meetingTitle, }, }); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts index 3e8936c9efa..6b7d22a7a37 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts @@ -1,3 +1,4 @@ +import axios from 'axios'; import { createBaasClient, type BaasClient, type V2 } from '@meeting-baas/sdk'; import { createLogger } from './logger'; import type { MeetingPlatform, RecordingData } from './types'; @@ -25,6 +26,7 @@ export class MeetingBaasApiClient { meetingUrl: string; joinAt: string; botName?: string; + entryMessage?: string; recordingMode?: 'speaker_view' | 'gallery_view' | 'audio_only'; extra?: Record; callbackUrl?: string; @@ -35,6 +37,8 @@ export class MeetingBaasApiClient { meeting_url: options.meetingUrl, join_at: options.joinAt, bot_name: options.botName || 'Twenty CRM Recorder', + transcription_enabled: true, + ...(options.entryMessage && { entry_message: options.entryMessage }), ...(options.recordingMode && { recording_mode: options.recordingMode }), ...(options.extra && { extra: options.extra }), ...(options.callbackUrl && { @@ -54,26 +58,85 @@ export class MeetingBaasApiClient { } // Transform V2 bot.completed webhook data into normalized RecordingData - transformWebhookData( + async transformWebhookData( data: V2.BotWebhookCompletedData, extra?: Record | null, - ): RecordingData { + ): Promise { const duration = data.duration_seconds ?? 0; const extraData = extra ?? {}; const meetingUrl = (extraData.meeting_url as string) || ''; const title = (extraData.meeting_title as string) || `Recording ${new Date().toLocaleDateString()}`; const platform: MeetingPlatform = detectPlatform(meetingUrl); + const transcript = await this.fetchTranscript(data); + return { botId: data.bot_id, title, date: data.joined_at || new Date().toISOString(), duration, - transcript: '', + transcript, mp4Url: data.video || '', meetingUrl, platform, extra: extraData, }; } + + // Fetch and format transcript from the signed URL provided in webhook data. + // V2 webhook provides `transcription` as a signed URL to a JSON file + // containing speaker-attributed segments. + private async fetchTranscript(data: V2.BotWebhookCompletedData): Promise { + const transcriptionUrl = data.transcription; + if (!transcriptionUrl) { + logger.debug('no transcription URL in webhook data'); + return ''; + } + + try { + logger.debug('fetching transcription from signed URL'); + const response = await axios.get(transcriptionUrl, { timeout: 30000 }); + const transcriptionData = response.data; + + // The transcription file is a JSON array of speaker segments: + // [{ speaker: "Name", words: [{ start, end, word }] }, ...] + if (Array.isArray(transcriptionData)) { + return this.formatTranscriptSegments(transcriptionData); + } + + // If it's a string (plain text transcript), return as-is + if (typeof transcriptionData === 'string') { + return transcriptionData; + } + + logger.warn('unexpected transcription format, storing as JSON'); + return JSON.stringify(transcriptionData); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.warn(`failed to fetch transcription: ${message}`); + return ''; + } + } + + // Format transcript segments into readable speaker-attributed text + private formatTranscriptSegments( + segments: Array<{ speaker?: string; words?: Array<{ word: string }> }>, + ): string { + const lines: string[] = []; + let currentSpeaker = ''; + + for (const segment of segments) { + const speaker = segment.speaker || 'Unknown'; + const text = segment.words?.map((w) => w.word).join(' ') || ''; + if (!text) continue; + + if (speaker !== currentSpeaker) { + currentSpeaker = speaker; + lines.push(`\n${speaker}:`); + } + lines.push(text); + } + + return lines.join('\n').trim(); + } } diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts index 31cc6ecce79..1ea57623281 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts @@ -182,6 +182,7 @@ export const syncBotRecording = async ( platform: MeetingPlatform; calendarEventId?: string; workspaceMemberId?: string; + status?: 'COMPLETED' | 'FAILED' | 'IN_PROGRESS'; }, result: SyncResult, ): Promise => { @@ -194,7 +195,7 @@ export const syncBotRecording = async ( date: recordingData.date, duration: durationMinutes, platform: recordingData.platform, - status: 'COMPLETED', + status: recordingData.status || 'COMPLETED', meetingUrl: recordingData.meetingUrl ? { primaryLinkLabel: 'Join Meeting', primaryLinkUrl: recordingData.meetingUrl, secondaryLinks: null } : null, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts index 8cce9d18d76..70f4ff1f64f 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts @@ -6,7 +6,7 @@ import { isValidMeetingBaasPayload, verifyWebhookApiKey } from './webhook-validator'; -import { syncBotRecording } from './twenty-sync-service'; +import { detectPlatform, syncBotRecording } from './twenty-sync-service'; declare const process: { env: Record }; @@ -44,7 +44,44 @@ export class WebhookHandler { if (payload.event === WebhookEvent.FAILED) { const failedData = payload.data; this.logger.error(`bot failed: ${failedData.error_message} (${failedData.error_code})`); - throw new Error(`Meeting BaaS bot failed: ${failedData.error_message}`); + + const extra = (payload.extra ?? {}) as Record; + const calendarEventId = extra.calendarEventId as string | undefined; + const workspaceMemberId = extra.workspaceMemberId as string | undefined; + const meetingUrl = (extra.meeting_url as string) || ''; + const meetingTitle = (extra.meeting_title as string) || ''; + + const failedSyncResult: SyncResult = { + recordingsProcessed: 0, + recordingsCreated: 0, + recordingsUpdated: 0, + errors: [], + }; + + const recordingId = await syncBotRecording( + { + botId: failedData.bot_id, + title: meetingTitle || `Failed recording — ${failedData.error_code}`, + date: new Date().toISOString(), + duration: 0, + transcript: `Bot failed: ${failedData.error_message} (${failedData.error_code})`, + mp4Url: '', + meetingUrl, + platform: detectPlatform(meetingUrl), + calendarEventId, + workspaceMemberId, + status: 'FAILED', + }, + failedSyncResult, + ); + + if (recordingId) { + result.recordingId = recordingId; + this.logger.debug(`created FAILED recording id=${recordingId}`); + } + + result.success = true; + return result; } if (payload.event === WebhookEvent.STATUS_CHANGE) { @@ -56,7 +93,7 @@ export class WebhookHandler { // Transform webhook data const completedData = payload.data; const meetingBaasClient = new MeetingBaasApiClient(meetingBaasApiKey); - const recordingData = meetingBaasClient.transformWebhookData( + const recordingData = await meetingBaasClient.transformWebhookData( completedData, payload.extra ?? undefined, ); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock b/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock index 9a19f0ceada..1a5c578fceb 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock +++ b/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock @@ -79,7 +79,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.28.6": +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.28.6": version: 7.28.6 resolution: "@babel/helper-module-imports@npm:7.28.6" dependencies: @@ -338,6 +338,13 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3": + version: 7.29.2 + resolution: "@babel/runtime@npm:7.29.2" + checksum: 10c0/30b80a0140d16467792e1bbeb06f655b0dab70407da38dfac7fedae9c859f9ae9d846ef14ad77bd3814c064295fe9b1bc551f1541ea14646ae9f22b71a8bc17a + languageName: node + linkType: hard + "@babel/template@npm:^7.28.6, @babel/template@npm:^7.3.3": version: 7.28.6 resolution: "@babel/template@npm:7.28.6" @@ -381,185 +388,101 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/aix-ppc64@npm:0.27.3" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-arm64@npm:0.27.3" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-arm@npm:0.27.3" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-x64@npm:0.27.3" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/darwin-arm64@npm:0.27.3" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/darwin-x64@npm:0.27.3" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/freebsd-arm64@npm:0.27.3" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/freebsd-x64@npm:0.27.3" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-arm64@npm:0.27.3" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-arm@npm:0.27.3" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-ia32@npm:0.27.3" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-loong64@npm:0.27.3" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-mips64el@npm:0.27.3" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-ppc64@npm:0.27.3" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-riscv64@npm:0.27.3" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-s390x@npm:0.27.3" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-x64@npm:0.27.3" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/netbsd-arm64@npm:0.27.3" - conditions: os=netbsd & cpu=arm64 +"@emotion/babel-plugin@npm:^11.13.5": + version: 11.13.5 + resolution: "@emotion/babel-plugin@npm:11.13.5" + dependencies: + "@babel/helper-module-imports": "npm:^7.16.7" + "@babel/runtime": "npm:^7.18.3" + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/serialize": "npm:^1.3.3" + babel-plugin-macros: "npm:^3.1.0" + convert-source-map: "npm:^1.5.0" + escape-string-regexp: "npm:^4.0.0" + find-root: "npm:^1.1.0" + source-map: "npm:^0.5.7" + stylis: "npm:4.2.0" + checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/netbsd-x64@npm:0.27.3" - conditions: os=netbsd & cpu=x64 +"@emotion/hash@npm:^0.9.2": + version: 0.9.2 + resolution: "@emotion/hash@npm:0.9.2" + checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openbsd-arm64@npm:0.27.3" - conditions: os=openbsd & cpu=arm64 +"@emotion/is-prop-valid@npm:^1.3.0": + version: 1.4.0 + resolution: "@emotion/is-prop-valid@npm:1.4.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + checksum: 10c0/5f857814ec7d8c7e727727346dfb001af6b1fb31d621a3ce9c3edf944a484d8b0d619546c30899ae3ade2f317c76390ba4394449728e9bf628312defc2c41ac3 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openbsd-x64@npm:0.27.3" - conditions: os=openbsd & cpu=x64 +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openharmony-arm64@npm:0.27.3" - conditions: os=openharmony & cpu=arm64 +"@emotion/serialize@npm:^1.3.3": + version: 1.3.3 + resolution: "@emotion/serialize@npm:1.3.3" + dependencies: + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/unitless": "npm:^0.10.0" + "@emotion/utils": "npm:^1.4.2" + csstype: "npm:^3.0.2" + checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/sunos-x64@npm:0.27.3" - conditions: os=sunos & cpu=x64 +"@emotion/styled@npm:^11.14.0": + version: 11.14.1 + resolution: "@emotion/styled@npm:11.14.1" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/is-prop-valid": "npm:^1.3.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/2bbf8451df49c967e41fbcf8111a7f6dafe6757f0cc113f2f6e287206c45ac1d54dc8a95a483b7c0cee8614b8a8d08155bded6453d6721de1f8cc8d5b9216963 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-arm64@npm:0.27.3" - conditions: os=win32 & cpu=arm64 +"@emotion/unitless@npm:^0.10.0": + version: 0.10.0 + resolution: "@emotion/unitless@npm:0.10.0" + checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-ia32@npm:0.27.3" - conditions: os=win32 & cpu=ia32 +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-x64@npm:0.27.3" - conditions: os=win32 & cpu=x64 +"@emotion/utils@npm:^1.4.2": + version: 1.4.2 + resolution: "@emotion/utils@npm:1.4.2" + checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a languageName: node linkType: hard @@ -1389,6 +1312,30 @@ __metadata: languageName: node linkType: hard +"@types/parse-json@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/parse-json@npm:4.0.2" + checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 + languageName: node + linkType: hard + +"@types/prop-types@npm:*": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + +"@types/react@npm:^18.2.0": + version: 18.3.28 + resolution: "@types/react@npm:18.3.28" + dependencies: + "@types/prop-types": "npm:*" + csstype: "npm:^3.2.2" + checksum: 10c0/683e19cd12b5c691215529af2e32b5ffbaccae3bf0ba93bfafa0e460e8dfee18423afed568be2b8eadf4b837c3749dd296a4f64e2d79f68fa66962c05f5af661 + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -1620,6 +1567,17 @@ __metadata: languageName: node linkType: hard +"babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + cosmiconfig: "npm:^7.0.0" + resolve: "npm:^1.19.0" + checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c + languageName: node + linkType: hard + "babel-preset-current-node-syntax@npm:^1.0.0": version: 1.2.0 resolution: "babel-preset-current-node-syntax@npm:1.2.0" @@ -2024,6 +1982,13 @@ __metadata: languageName: node linkType: hard +"convert-source-map@npm:^1.5.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + "convert-source-map@npm:^2.0.0": version: 2.0.0 resolution: "convert-source-map@npm:2.0.0" @@ -2031,6 +1996,19 @@ __metadata: languageName: node linkType: hard +"cosmiconfig@npm:^7.0.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 + languageName: node + linkType: hard + "create-jest@npm:^29.7.0": version: 29.7.0 resolution: "create-jest@npm:29.7.0" @@ -2059,6 +2037,13 @@ __metadata: languageName: node linkType: hard +"csstype@npm:^3.0.2, csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + "date-fns@npm:^1.27.2": version: 1.30.1 resolution: "date-fns@npm:1.30.1" @@ -2141,13 +2126,6 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^17.2.3": - version: 17.3.1 - resolution: "dotenv@npm:17.3.1" - checksum: 10c0/c78e0c2d5a549c751e544cc60e2b95e7cb67e0c551f42e094d161c6b297aa44b630a3c2dcacf5569e529a6c2a6b84e2ab9be8d37b299d425df5a18b81ce4a35f - languageName: node - linkType: hard - "dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -2238,95 +2216,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:~0.27.0": - version: 0.27.3 - resolution: "esbuild@npm:0.27.3" - dependencies: - "@esbuild/aix-ppc64": "npm:0.27.3" - "@esbuild/android-arm": "npm:0.27.3" - "@esbuild/android-arm64": "npm:0.27.3" - "@esbuild/android-x64": "npm:0.27.3" - "@esbuild/darwin-arm64": "npm:0.27.3" - "@esbuild/darwin-x64": "npm:0.27.3" - "@esbuild/freebsd-arm64": "npm:0.27.3" - "@esbuild/freebsd-x64": "npm:0.27.3" - "@esbuild/linux-arm": "npm:0.27.3" - "@esbuild/linux-arm64": "npm:0.27.3" - "@esbuild/linux-ia32": "npm:0.27.3" - "@esbuild/linux-loong64": "npm:0.27.3" - "@esbuild/linux-mips64el": "npm:0.27.3" - "@esbuild/linux-ppc64": "npm:0.27.3" - "@esbuild/linux-riscv64": "npm:0.27.3" - "@esbuild/linux-s390x": "npm:0.27.3" - "@esbuild/linux-x64": "npm:0.27.3" - "@esbuild/netbsd-arm64": "npm:0.27.3" - "@esbuild/netbsd-x64": "npm:0.27.3" - "@esbuild/openbsd-arm64": "npm:0.27.3" - "@esbuild/openbsd-x64": "npm:0.27.3" - "@esbuild/openharmony-arm64": "npm:0.27.3" - "@esbuild/sunos-x64": "npm:0.27.3" - "@esbuild/win32-arm64": "npm:0.27.3" - "@esbuild/win32-ia32": "npm:0.27.3" - "@esbuild/win32-x64": "npm:0.27.3" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 - languageName: node - linkType: hard - "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -2348,6 +2237,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + "esprima@npm:^4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" @@ -2491,6 +2387,13 @@ __metadata: languageName: node linkType: hard +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa + languageName: node + linkType: hard + "find-up@npm:^4.0.0, find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" @@ -2562,7 +2465,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:^2.3.2": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -2572,7 +2475,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -2654,15 +2557,6 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.5": - version: 4.13.6 - resolution: "get-tsconfig@npm:4.13.6" - dependencies: - resolve-pkg-maps: "npm:^1.0.0" - checksum: 10c0/bab6937302f542f97217cbe7cbbdfa7e85a56a377bc7a73e69224c1f0b7c9ae8365918e55752ae8648265903f506c1705f63c0de1d4bab1ec2830fef3e539a1a - languageName: node - linkType: hard - "glob-parent@npm:^5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -2873,6 +2767,16 @@ __metadata: languageName: node linkType: hard +"import-fresh@npm:^3.2.1": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec + languageName: node + linkType: hard + "import-local@npm:^3.0.2": version: 3.2.0 resolution: "import-local@npm:3.2.0" @@ -3556,7 +3460,7 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^4.0.0": +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed @@ -3773,6 +3677,17 @@ __metadata: languageName: node linkType: hard +"loose-envify@npm:^1.1.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + "lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": version: 11.2.6 resolution: "lru-cache@npm:11.2.6" @@ -3844,14 +3759,15 @@ __metadata: version: 0.0.0-use.local resolution: "meeting-baas-recorder@workspace:." dependencies: + "@emotion/styled": "npm:^11.14.0" "@meeting-baas/sdk": "npm:^6.0.5" "@types/jest": "npm:^29.5.5" "@types/node": "npm:^24.9.2" + "@types/react": "npm:^18.2.0" axios: "npm:^1.13.1" - dotenv: "npm:^17.2.3" jest: "npm:^29.7.0" + react: "npm:^18.2.0" ts-jest: "npm:^29.1.1" - tsx: "npm:^4.19.3" twenty-sdk: "npm:0.1.3" typescript: "npm:^5.9.3" languageName: unknown @@ -4240,7 +4156,16 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^5.2.0": +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" dependencies: @@ -4408,6 +4333,15 @@ __metadata: languageName: node linkType: hard +"react@npm:^18.2.0": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 + languageName: node + linkType: hard + "readdirp@npm:^4.0.1": version: 4.1.2 resolution: "readdirp@npm:4.1.2" @@ -4452,10 +4386,10 @@ __metadata: languageName: node linkType: hard -"resolve-pkg-maps@npm:^1.0.0": - version: 1.0.0 - resolution: "resolve-pkg-maps@npm:1.0.0" - checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 languageName: node linkType: hard @@ -4466,6 +4400,20 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.19.0": + version: 1.22.12 + resolution: "resolve@npm:1.22.12" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/b16dc9b537c02e8c3388f7d3dcff9741d3071625f9a97ac1c885f2b0ca51e78df22328fb6d6ef214dd9101fb7cfc19aa2836fe3410402a94f3f7b8639c7149bf + languageName: node + linkType: hard + "resolve@npm:^1.20.0": version: 1.22.11 resolution: "resolve@npm:1.22.11" @@ -4479,6 +4427,20 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin": + version: 1.22.12 + resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/fc6519984ae1f894d877c0060ba8b1f5ba3bc0e85a02f74e141929c118c23d74d9735619a9cc2965397387e514884245c65d72a40731dcb6cfc84c7bcdc8321e + languageName: node + linkType: hard + "resolve@patch:resolve@npm%3A^1.20.0#optional!builtin": version: 1.22.11 resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" @@ -4730,6 +4692,13 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 + languageName: node + linkType: hard + "source-map@npm:^0.6.0, source-map@npm:^0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" @@ -4852,6 +4821,13 @@ __metadata: languageName: node linkType: hard +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 + languageName: node + linkType: hard + "supports-color@npm:^2.0.0": version: 2.0.0 resolution: "supports-color@npm:2.0.0" @@ -5013,22 +4989,6 @@ __metadata: languageName: node linkType: hard -"tsx@npm:^4.19.3": - version: 4.21.0 - resolution: "tsx@npm:4.21.0" - dependencies: - esbuild: "npm:~0.27.0" - fsevents: "npm:~2.3.3" - get-tsconfig: "npm:^4.7.5" - dependenciesMeta: - fsevents: - optional: true - bin: - tsx: dist/cli.mjs - checksum: 10c0/f5072923cd8459a1f9a26df87823a2ab5754641739d69df2a20b415f61814322b751fa6be85db7c6ec73cf68ba8fac2fd1cfc76bdb0aa86ded984d84d5d2126b - languageName: node - linkType: hard - "twenty-sdk@npm:0.1.3": version: 0.1.3 resolution: "twenty-sdk@npm:0.1.3" @@ -5340,6 +5300,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^1.10.0": + version: 1.10.3 + resolution: "yaml@npm:1.10.3" + checksum: 10c0/c309ff85a0a569a981d71ab9cf0fef68672a16b9cdf40639d1c3b30034f6cd16ee428602bd6d64ecf006f8c8bee499023cac236538f79898aa99fb5db529a2ed + languageName: node + linkType: hard + "yargs-parser@npm:^18.1.2": version: 18.1.3 resolution: "yargs-parser@npm:18.1.3" From aa511a6861185b44904ca62ee81fdaf1dfac5936 Mon Sep 17 00:00:00 2001 From: Lazare-42 Date: Thu, 16 Apr 2026 23:42:44 +0200 Subject: [PATCH 4/5] Update meeting recorder app for current Twenty SDK --- .../application.config.ts | 7 +- .../meeting-baas-recorder/package.json | 3 +- ...entry-message-on-workspace-member.field.ts | 2 +- .../bot-name-on-workspace-member.field.ts | 2 +- .../calendar-event-on-recording.field.ts | 4 +- ...ng-preference-on-workspace-member.field.ts | 8 +- .../workspace-member-on-recording.field.ts | 4 +- .../meeting-baas-settings.front-component.tsx | 238 ++- .../on-calendar-event-created.ts | 2 +- .../on-calendar-event-updated.ts | 2 +- .../recordings.navigation-menu-item.ts | 2 +- .../src/objects/recording.ts | 34 +- .../src/receive-recording-webhook.ts | 2 +- .../src/roles/default.role.ts | 2 +- .../src/views/all-recordings.view.ts | 20 +- .../src/views/by-platform.view.ts | 13 +- .../src/views/completed-recordings.view.ts | 18 +- .../community/meeting-baas-recorder/yarn.lock | 1682 ++++++++++++++++- 18 files changed, 1845 insertions(+), 200 deletions(-) diff --git a/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts b/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts index a65fab6c741..7fcda7e7401 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/application.config.ts @@ -2,15 +2,16 @@ import { defineApplication } from 'twenty-sdk'; import { DEFAULT_ROLE_ID } from './src/roles/default.role'; export default defineApplication({ - universalIdentifier: 'b7e3a1f2-8d4c-4e6a-9f2b-1c5d7e8a3b4f', + universalIdentifier: 'c522c3c7-cff8-5c08-8c87-d1481adbd4a9', displayName: 'Meeting BaaS Recorder', description: 'Record meetings via Meeting BaaS and sync recordings, transcripts, and participants into Twenty.', icon: 'IconVideo', defaultRoleUniversalIdentifier: DEFAULT_ROLE_ID, - settingsCustomTabFrontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b', + settingsCustomTabFrontComponentUniversalIdentifier: + '7f2c17b4-2cd2-5447-b7d1-83ef12040837', applicationVariables: { MEETING_BAAS_API_KEY: { - universalIdentifier: 'c1d2e3f4-5a6b-7c8d-9e0f-a1b2c3d4e5f6', + universalIdentifier: '32cd6297-bbd3-5beb-a0f6-1f5662590f66', description: 'Meeting BaaS API key for authenticating requests and verifying webhooks', isSecret: true, value: '', diff --git a/packages/twenty-apps/community/meeting-baas-recorder/package.json b/packages/twenty-apps/community/meeting-baas-recorder/package.json index c12fd7e90de..b0362a76ee4 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/package.json +++ b/packages/twenty-apps/community/meeting-baas-recorder/package.json @@ -14,9 +14,10 @@ "dependencies": { "@meeting-baas/sdk": "^6.0.5", "axios": "^1.13.1", - "twenty-sdk": "0.1.3" + "twenty-sdk": "0.8.0" }, "devDependencies": { + "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@types/jest": "^29.5.5", "@types/node": "^24.9.2", diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts index 04fbf96cf5c..2ffe7c3e359 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts @@ -4,7 +4,7 @@ import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, } from 'twenty-sdk'; -export const BOT_ENTRY_MESSAGE_FIELD_ID = 'b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e'; +export const BOT_ENTRY_MESSAGE_FIELD_ID = '5d9be02b-138b-5437-9184-d72276f51f3d'; export default defineField({ universalIdentifier: BOT_ENTRY_MESSAGE_FIELD_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-name-on-workspace-member.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-name-on-workspace-member.field.ts index 1d545d2fc65..98218d4af20 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-name-on-workspace-member.field.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-name-on-workspace-member.field.ts @@ -4,7 +4,7 @@ import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, } from 'twenty-sdk'; -export const BOT_NAME_FIELD_ID = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'; +export const BOT_NAME_FIELD_ID = '6a37564a-25e6-5bdb-a119-1522e3817ae6'; export default defineField({ universalIdentifier: BOT_NAME_FIELD_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts index 571f173a8fc..86c9ed9d098 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts @@ -7,8 +7,8 @@ import { } from 'twenty-sdk'; import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; -export const CALENDAR_EVENT_ON_RECORDING_ID = '29fe48d1-7e7d-4253-9fea-0a876c2c116d'; -export const RECORDINGS_ON_CALENDAR_EVENT_ID = '131a78b1-f3c9-4b2e-9808-f9eb64bfb832'; +export const CALENDAR_EVENT_ON_RECORDING_ID = 'f41b7906-5667-5241-a269-b8140dcfdb53'; +export const RECORDINGS_ON_CALENDAR_EVENT_ID = 'b78cc9fb-d758-5308-a37b-4b4a60a6870b'; export default defineField({ universalIdentifier: CALENDAR_EVENT_ON_RECORDING_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts index c191105d6de..3266edf4875 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts @@ -4,7 +4,7 @@ import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, } from 'twenty-sdk'; -export const RECORDING_PREFERENCE_FIELD_ID = 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a'; +export const RECORDING_PREFERENCE_FIELD_ID = '082e8dd7-a365-5b6b-8d9b-1780f36154b3'; export default defineField({ universalIdentifier: RECORDING_PREFERENCE_FIELD_ID, @@ -18,21 +18,21 @@ export default defineField({ defaultValue: "'RECORD_NONE'", options: [ { - id: 'e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b', + id: 'dd08b14a-c5f2-5eb6-9b4b-e579437a4f6f', color: 'green', label: 'Record All', value: 'RECORD_ALL', position: 1, }, { - id: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c', + id: 'e880d3db-9b3b-58c2-af65-a8e0be61083d', color: 'yellow', label: 'Organizer Only', value: 'RECORD_ORGANIZED', position: 2, }, { - id: 'a3b4c5d6-e7f8-4a9b-0c1d-2e3f4a5b6c7d', + id: 'e9c62cd0-26ab-5241-811d-545a9d0180a6', color: 'gray', label: 'None', value: 'RECORD_NONE', diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts index 8522ab7e6f3..34655e465f3 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts @@ -7,8 +7,8 @@ import { } from 'twenty-sdk'; import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; -export const WORKSPACE_MEMBER_ON_RECORDING_ID = '3bb83966-51d6-41e1-9a33-5b53bc6313d9'; -export const RECORDINGS_ON_WORKSPACE_MEMBER_ID = '0fb0eaac-4e8d-4632-a6ad-f76df4fb93ad'; +export const WORKSPACE_MEMBER_ON_RECORDING_ID = '8e985272-eae7-51ac-939b-5277b1107bc8'; +export const RECORDINGS_ON_WORKSPACE_MEMBER_ID = '8ab15013-866c-51d5-95bb-21108a07442f'; export default defineField({ universalIdentifier: WORKSPACE_MEMBER_ON_RECORDING_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx b/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx index 0d6c67f71c5..ade9758d144 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx @@ -1,7 +1,6 @@ import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; import { defineFrontComponent } from 'twenty-sdk'; -import { Callout, H2Title, Status, themeCssVariables } from 'twenty-sdk/ui'; type RecordingPreference = 'RECORD_ALL' | 'RECORD_ORGANIZED' | 'RECORD_NONE'; @@ -19,25 +18,47 @@ type CalendarChannel = { const StyledContainer = styled.div` display: flex; flex-direction: column; - gap: ${themeCssVariables.spacing[4]}; + gap: 16px; width: 100%; `; +const StyledSection = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +const StyledHeading = styled.h2` + color: #1f2937; + font-family: Inter, sans-serif; + font-size: 20px; + font-weight: 600; + margin: 0; +`; + +const StyledSectionDescription = styled.p` + color: #6b7280; + font-family: Inter, sans-serif; + font-size: 14px; + line-height: 1.5; + margin: 0; +`; + const StyledCard = styled.div` align-items: center; - background: ${themeCssVariables.background.primary}; - border: 1px solid ${themeCssVariables.border.color.light}; - border-radius: ${themeCssVariables.border.radius.sm}; + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 8px; display: flex; - gap: ${themeCssVariables.spacing[3]}; - padding: ${themeCssVariables.spacing[4]}; + gap: 12px; + padding: 16px; `; const StyledIconContainer = styled.div` align-items: center; - background: ${themeCssVariables.background.tertiary}; - border-radius: ${themeCssVariables.border.radius.sm}; - color: ${themeCssVariables.font.color.secondary}; + background: #f3f4f6; + border-radius: 8px; + color: #4b5563; display: flex; flex-shrink: 0; height: 40px; @@ -54,45 +75,60 @@ const StyledTextContainer = styled.div` `; const StyledTitle = styled.span` - color: ${themeCssVariables.font.color.primary}; - font-family: ${themeCssVariables.font.family}; - font-size: ${themeCssVariables.font.size.md}; - font-weight: ${themeCssVariables.font.weight.medium}; + color: #1f2937; + font-family: Inter, sans-serif; + font-size: 14px; + font-weight: 600; `; const StyledDescription = styled.span` - color: ${themeCssVariables.font.color.tertiary}; - font-family: ${themeCssVariables.font.family}; - font-size: ${themeCssVariables.font.size.sm}; + color: #6b7280; + font-family: Inter, sans-serif; + font-size: 13px; +`; + +const StyledStatus = styled.span<{ tone: 'green' | 'red' }>` + align-items: center; + background: ${({ tone }) => (tone === 'green' ? '#dcfce7' : '#fee2e2')}; + border-radius: 999px; + color: ${({ tone }) => (tone === 'green' ? '#166534' : '#991b1b')}; + display: inline-flex; + flex-shrink: 0; + font-family: Inter, sans-serif; + font-size: 12px; + font-weight: 600; + height: 28px; + padding: 0 10px; + white-space: nowrap; `; const StyledRadioGroup = styled.div` display: flex; flex-direction: column; - gap: ${themeCssVariables.spacing[2]}; + gap: 8px; `; const StyledRadioLabel = styled.label<{ selected: boolean }>` align-items: center; background: ${({ selected }) => - selected ? themeCssVariables.accent.accent1 : themeCssVariables.background.primary}; + selected ? '#eff6ff' : '#ffffff'}; border: 1px solid ${({ selected }) => - selected ? themeCssVariables.accent.primary : themeCssVariables.border.color.light}; - border-radius: ${themeCssVariables.border.radius.sm}; + selected ? '#2563eb' : '#e5e7eb'}; + border-radius: 8px; cursor: pointer; display: flex; - gap: ${themeCssVariables.spacing[3]}; - padding: ${themeCssVariables.spacing[3]} ${themeCssVariables.spacing[4]}; + gap: 12px; + padding: 12px 16px; transition: all 0.15s ease; &:hover { - border-color: ${themeCssVariables.accent.primary}; + border-color: #2563eb; } `; const StyledRadioInput = styled.input` - accent-color: ${themeCssVariables.accent.primary}; + accent-color: #2563eb; cursor: pointer; height: 16px; margin: 0; @@ -106,61 +142,84 @@ const StyledRadioTextContainer = styled.div` `; const StyledRadioTitle = styled.span` - color: ${themeCssVariables.font.color.primary}; - font-family: ${themeCssVariables.font.family}; - font-size: ${themeCssVariables.font.size.md}; - font-weight: ${themeCssVariables.font.weight.medium}; + color: #1f2937; + font-family: Inter, sans-serif; + font-size: 14px; + font-weight: 600; `; const StyledRadioDescription = styled.span` - color: ${themeCssVariables.font.color.tertiary}; - font-family: ${themeCssVariables.font.family}; - font-size: ${themeCssVariables.font.size.xs}; + color: #6b7280; + font-family: Inter, sans-serif; + font-size: 12px; `; const StyledFieldGroup = styled.div` display: flex; flex-direction: column; - gap: ${themeCssVariables.spacing[3]}; + gap: 12px; `; const StyledFieldLabel = styled.label` - color: ${themeCssVariables.font.color.primary}; - font-family: ${themeCssVariables.font.family}; - font-size: ${themeCssVariables.font.size.sm}; - font-weight: ${themeCssVariables.font.weight.medium}; + color: #1f2937; + font-family: Inter, sans-serif; + font-size: 13px; + font-weight: 600; `; const StyledTextInput = styled.input` - background: ${themeCssVariables.background.primary}; - border: 1px solid ${themeCssVariables.border.color.light}; - border-radius: ${themeCssVariables.border.radius.sm}; - color: ${themeCssVariables.font.color.primary}; - font-family: ${themeCssVariables.font.family}; - font-size: ${themeCssVariables.font.size.md}; + background: #ffffff; + border: 1px solid #d1d5db; + border-radius: 8px; + color: #111827; + font-family: Inter, sans-serif; + font-size: 14px; margin-top: 4px; outline: none; padding: 10px 12px; width: 100%; &:focus { - border-color: ${themeCssVariables.accent.primary}; + border-color: #2563eb; } &::placeholder { - color: ${themeCssVariables.font.color.light}; + color: #9ca3af; } `; const StyledCharCount = styled.span<{ over: boolean }>` - color: ${({ over }) => - over ? themeCssVariables.color.red : themeCssVariables.font.color.tertiary}; - font-family: ${themeCssVariables.font.family}; - font-size: ${themeCssVariables.font.size.xxs}; + color: ${({ over }) => (over ? '#dc2626' : '#6b7280')}; + font-family: Inter, sans-serif; + font-size: 11px; margin-top: 2px; text-align: right; `; +const StyledCallout = styled.div<{ variant: 'warning' | 'info' }>` + background: ${({ variant }) => (variant === 'warning' ? '#fffbeb' : '#eff6ff')}; + border: 1px solid ${({ variant }) => (variant === 'warning' ? '#fcd34d' : '#93c5fd')}; + border-radius: 8px; + color: #1f2937; + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px 14px; +`; + +const StyledCalloutTitle = styled.span` + font-family: Inter, sans-serif; + font-size: 13px; + font-weight: 600; +`; + +const StyledCalloutDescription = styled.span` + color: #4b5563; + font-family: Inter, sans-serif; + font-size: 13px; + line-height: 1.5; +`; + const PREFERENCE_OPTIONS: Array<{ value: RecordingPreference; title: string; @@ -294,7 +353,10 @@ const MeetingBaasSettings = () => { if (isLoading) { return ( - + + Meeting Recording + Loading settings... + ); } @@ -302,11 +364,13 @@ const MeetingBaasSettings = () => { return ( {/* API Key Status */} -
- + +
+ Meeting BaaS Connection + + Meeting BaaS records your meetings and syncs transcripts into Twenty + +
🎥 @@ -319,28 +383,32 @@ const MeetingBaasSettings = () => { : 'No API key configured — add MEETING_BAAS_API_KEY in app variables'} - + + {apiKeyConfigured ? 'Connected' : 'Not Set'} + -
+ {/* Calendar Connection Banner */} {hasCalendar === false && ( - + + No calendar connected + + Connect your Google or Microsoft calendar in Settings {'->'} Accounts to enable + automatic meeting recording. + + )} {/* Recording Preference */} -
- + +
+ Recording Preference + + Choose which meetings are automatically recorded when they have a conference + link + +
{PREFERENCE_OPTIONS.map((option) => ( @@ -359,14 +427,16 @@ const MeetingBaasSettings = () => { ))} -
+ {/* Bot Customization */} -
- + +
+ Bot Customization + + Customize how the recording bot appears when it joins your meetings + +
Bot name @@ -397,20 +467,22 @@ const MeetingBaasSettings = () => {
-
+ {preference !== 'RECORD_NONE' && !apiKeyConfigured && ( - + + API key required + + Recording is enabled but no API key is set. Add `MEETING_BAAS_API_KEY` in + the app variables to start recording. + + )}
); }; -export const SETTINGS_FRONT_COMPONENT_ID = 'e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b'; +export const SETTINGS_FRONT_COMPONENT_ID = '7f2c17b4-2cd2-5447-b7d1-83ef12040837'; export default defineFrontComponent({ universalIdentifier: SETTINGS_FRONT_COMPONENT_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts index 8c04b029260..e8e7fe93ad3 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts @@ -48,7 +48,7 @@ const handler = async ( }; export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', + universalIdentifier: 'bc2a28a0-1362-5389-9c2f-b5ad10f1605c', name: 'on-calendar-event-created', description: 'Schedules a Meeting BaaS recording bot when a calendar event with a conference link is created', timeoutSeconds: 15, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts index 675a55100e7..65a35d4fd05 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts @@ -74,7 +74,7 @@ const handler = async ( }; export default defineLogicFunction({ - universalIdentifier: 'b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e', + universalIdentifier: 'c4f20492-beec-535d-9ac4-28a3f25a9813', name: 'on-calendar-event-updated', description: 'Schedules a Meeting BaaS recording bot when a calendar event gains a conference link', timeoutSeconds: 15, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts index 300b31d72bb..2dd830213ca 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts @@ -2,7 +2,7 @@ import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; export default defineNavigationMenuItem({ - universalIdentifier: 'a569ca64-99b6-4334-a51e-d5fc59e8da57', + universalIdentifier: '6e7bbaa0-d46f-5701-ae91-7346afcb936c', position: 0, type: NavigationMenuItemType.OBJECT, targetObjectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts index 15de141c1ce..4effbbf1b4a 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts @@ -1,18 +1,18 @@ import { defineObject, FieldType } from 'twenty-sdk'; export const RECORDING_UNIVERSAL_IDENTIFIER = - 'a8b9c0d1-2e3f-4a5b-6c7d-8e9f0a1b2c3d'; + '0ec52997-4307-50a7-a929-85ad6f24b5d9'; // Field UUIDs — exported for use in views, relations, and page layouts -export const NAME_FIELD_ID = 'b6209c31-8f6a-453e-8291-41191c0394ae'; -export const BOT_ID_FIELD_ID = '68967b34-482f-47c2-8d81-7fa1c824bd82'; -export const DATE_FIELD_ID = '74bd2089-d5e2-4488-97fb-017ea8e0d14a'; -export const DURATION_FIELD_ID = '2ee873e5-60eb-40b1-9a0f-b10d7d48f297'; -export const TRANSCRIPT_FIELD_ID = '06e9adf7-392d-44a0-abc9-ef5c1e81bdd7'; -export const MEETING_URL_FIELD_ID = 'ad56795c-49ac-4075-b099-42e938d9e36e'; -export const MP4_URL_FIELD_ID = '00ecab81-954c-47c4-a263-96fd5ccbc39d'; -export const PLATFORM_FIELD_ID = 'eaa24930-508b-4e63-80c3-cc607d8e2845'; -export const STATUS_FIELD_ID = '6fce2fe2-169e-4780-9fc4-d330d3cd39bc'; +export const NAME_FIELD_ID = '6b04e869-c04e-5ebe-bfc4-7778a1108b17'; +export const BOT_ID_FIELD_ID = '138d74e2-08b6-57e1-9eba-872c0747636f'; +export const DATE_FIELD_ID = 'e9bce8f0-1a6d-507b-b0d8-00a9fc1386a5'; +export const DURATION_FIELD_ID = '895cd235-1ad4-59b3-bfc9-8b2522d7d20f'; +export const TRANSCRIPT_FIELD_ID = 'ebbc2924-3eb3-5895-b1b8-0cddb430fc9e'; +export const MEETING_URL_FIELD_ID = 'ed410c1d-f23e-5d0a-8abd-699e8393edc8'; +export const MP4_URL_FIELD_ID = 'ca9a9c4d-2aea-5b3e-9d27-22618f2095ee'; +export const PLATFORM_FIELD_ID = '420c95de-9dbf-5eb5-b4fd-1327d0440941'; +export const STATUS_FIELD_ID = '4c05856d-9182-5d40-bf27-07bc6f6457c3'; export default defineObject({ universalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, @@ -82,10 +82,10 @@ export default defineObject({ name: 'platform', icon: 'IconDevices', options: [ - { id: 'b3521d94-c929-45b0-8573-26567e6dfc85', value: 'GOOGLE_MEET', label: 'Google Meet', position: 0, color: 'blue' }, - { id: 'd517462f-8737-4a71-b1bd-bca56a82a1fa', value: 'ZOOM', label: 'Zoom', position: 1, color: 'sky' }, - { id: '3e8c1061-6b12-4f2d-8e52-3bf4667dae01', value: 'MICROSOFT_TEAMS', label: 'Microsoft Teams', position: 2, color: 'purple' }, - { id: 'd0d8114f-811c-4d14-8c6d-fea5283e2b61', value: 'UNKNOWN', label: 'Unknown', position: 3, color: 'gray' }, + { id: '75e03212-d08e-5f37-8b1c-405229a2b3b6', value: 'GOOGLE_MEET', label: 'Google Meet', position: 0, color: 'blue' }, + { id: '30217257-f906-540a-b82a-8399e558fe4e', value: 'ZOOM', label: 'Zoom', position: 1, color: 'sky' }, + { id: 'b8971190-2e5a-5b52-a997-41bb0b227385', value: 'MICROSOFT_TEAMS', label: 'Microsoft Teams', position: 2, color: 'purple' }, + { id: '876a7992-ada9-5c28-a240-2e0843e66e15', value: 'UNKNOWN', label: 'Unknown', position: 3, color: 'gray' }, ], }, { @@ -95,9 +95,9 @@ export default defineObject({ name: 'status', icon: 'IconStatusChange', options: [ - { id: 'e127d867-4931-4e15-a5be-971caa8678a3', value: 'COMPLETED', label: 'Completed', position: 0, color: 'green' }, - { id: '776885a7-8a42-415c-92fe-e08f5a1274ea', value: 'FAILED', label: 'Failed', position: 1, color: 'red' }, - { id: '12374b43-a6fe-48d6-8a71-437242e59df2', value: 'IN_PROGRESS', label: 'In Progress', position: 2, color: 'orange' }, + { id: '9347a357-cea4-5ecd-843d-dd57687821b8', value: 'COMPLETED', label: 'Completed', position: 0, color: 'green' }, + { id: 'c1e846a4-159f-5d9d-b7ff-06120bf714f1', value: 'FAILED', label: 'Failed', position: 1, color: 'red' }, + { id: '28e99b5d-387f-5002-86ef-62478e95c69a', value: 'IN_PROGRESS', label: 'In Progress', position: 2, color: 'orange' }, ], }, ], diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts index 5f9c57af5fb..b8af5fed62d 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts @@ -3,7 +3,7 @@ import type { ProcessResult } from './types'; import { WebhookHandler } from './webhook-handler'; export default defineLogicFunction({ - universalIdentifier: '3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d', + universalIdentifier: '0c0c376e-3d0a-5348-b6a7-3b4f35b33f9e', name: 'receive-recording-webhook', description: 'Receives Meeting BaaS webhooks when recordings complete, and stores them in Twenty.', diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts index 1aff14dcbcc..8f8df90db2c 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts @@ -1,7 +1,7 @@ import { defineRole, PermissionFlag } from 'twenty-sdk'; import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; -export const DEFAULT_ROLE_ID = '3231cf40-5b90-4c2b-ae41-fcb5606299b4'; +export const DEFAULT_ROLE_ID = '5aceb3d4-2453-58e8-a19f-e3ae7e148e93'; export default defineRole({ universalIdentifier: DEFAULT_ROLE_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts index 220c42746d4..7108bc2625d 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts @@ -1,5 +1,5 @@ import { defineView } from 'twenty-sdk'; -import { ViewType } from 'twenty-shared/types'; +import { ViewKey, ViewType } from 'twenty-shared/types'; import { RECORDING_UNIVERSAL_IDENTIFIER, NAME_FIELD_ID, @@ -11,23 +11,23 @@ import { MP4_URL_FIELD_ID, } from '../objects/recording'; -export const ALL_RECORDINGS_VIEW_ID = 'bd6347b3-3b8a-4095-a0bb-24220b5040bd'; +export const ALL_RECORDINGS_VIEW_ID = '264b131d-2c52-5a8b-bdfe-2221e62d2123'; export default defineView({ universalIdentifier: ALL_RECORDINGS_VIEW_ID, name: 'All Recordings', objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, type: ViewType.TABLE, - key: 'INDEX', + key: ViewKey.INDEX, icon: 'IconVideo', position: 0, fields: [ - { universalIdentifier: 'e75cd4e3-e42e-43c7-bd94-532f2766ea11', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, - { universalIdentifier: '30411458-e7bc-463f-8308-a9c0c947122c', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, - { universalIdentifier: '74be7ee1-10f5-49a1-9af8-86174a2d037f', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, - { universalIdentifier: '83fc4647-d08a-400e-840e-01f3b9cbe51e', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, - { universalIdentifier: '59f601ca-c442-4feb-a803-476eff07d534', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, - { universalIdentifier: '3becc98e-ee5f-479a-a947-9e7db0412e8b', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, - { universalIdentifier: 'f72a4775-4443-467f-ad0b-8e31323bc322', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, + { universalIdentifier: '3adb270f-88f4-5d70-b41a-3bac6f45ee9b', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, + { universalIdentifier: '58c8d9ff-2bc1-59ed-91c3-786a8ca1ccf7', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, + { universalIdentifier: '39c719be-fc9f-51ed-8735-fae5381cb8ca', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, + { universalIdentifier: 'dc6cb2b6-d887-5243-ab51-495ac36bde0b', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, + { universalIdentifier: 'f8605012-003f-5460-8c3e-d5685a24b97a', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, + { universalIdentifier: 'ab2dce09-bbb9-5f64-af5f-48da59dd7a53', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, + { universalIdentifier: '3cd5d593-2f93-5a62-9de9-f70c1458a476', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, ], }); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts index ddc03da598b..24569f1cbe2 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts @@ -6,19 +6,20 @@ import { } from '../objects/recording'; export default defineView({ - universalIdentifier: '9d3114e6-3ba9-4e31-8197-45e510b69c4c', + universalIdentifier: 'c8f0b6fa-dc35-5adb-83c8-11749702025e', name: 'By Platform', objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, type: ViewType.KANBAN, icon: 'IconLayoutKanban', position: 2, + mainGroupByFieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, groups: [ - { universalIdentifier: '7a0144b1-6d0e-453d-8cda-cf1fa63d3b77', fieldValue: 'GOOGLE_MEET', isVisible: true, position: 0 }, - { universalIdentifier: '8ed53363-962d-4136-b34c-fae2eae7d3f9', fieldValue: 'ZOOM', isVisible: true, position: 1 }, - { universalIdentifier: '7ab9b893-6f97-4973-a4a9-2295f69f5d6d', fieldValue: 'MICROSOFT_TEAMS', isVisible: true, position: 2 }, - { universalIdentifier: '3e82c80b-66db-4be2-8043-552ee309d26c', fieldValue: 'UNKNOWN', isVisible: true, position: 3 }, + { universalIdentifier: 'd7cc1b3a-d079-54e9-a853-01bbe9306fab', fieldValue: 'GOOGLE_MEET', isVisible: true, position: 0 }, + { universalIdentifier: '708b2535-067b-5247-b9ef-09c5d80e6cff', fieldValue: 'ZOOM', isVisible: true, position: 1 }, + { universalIdentifier: 'a732280e-da0a-59ae-8421-86909b7e367f', fieldValue: 'MICROSOFT_TEAMS', isVisible: true, position: 2 }, + { universalIdentifier: 'e49d6c58-790b-5da3-b2d7-5f0dc641970c', fieldValue: 'UNKNOWN', isVisible: true, position: 3 }, ], fields: [ - { universalIdentifier: 'a578e600-97ec-4cd3-84fe-b254c3f89130', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 0, isVisible: true, size: 150 }, + { universalIdentifier: '1a38ccfd-71e3-59d5-8d50-94fc1c9d25ef', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 0, isVisible: true, size: 150 }, ], }); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts index 800a5f523b7..a18a022f1a6 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts @@ -12,24 +12,24 @@ import { } from '../objects/recording'; export default defineView({ - universalIdentifier: 'd6325047-8ba7-4976-a732-4e280db36dfa', + universalIdentifier: '394e8d23-555f-5bf7-9b4e-367b6dbf38b0', name: 'Completed Recordings', objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, type: ViewType.TABLE, icon: 'IconCheck', position: 1, fields: [ - { universalIdentifier: 'b953f475-0eb7-4864-b3e8-d2c4e8b02a3b', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, - { universalIdentifier: 'b3bd42e6-05cd-4c3a-bfad-726ef75a0c15', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, - { universalIdentifier: 'e15d29e8-468a-4b8f-93cb-a71d89f01d45', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, - { universalIdentifier: 'c2b37ddf-f16f-4b5f-b443-c6b5214eb4b4', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, - { universalIdentifier: '489703b0-1cd1-4422-98d8-6d8d32063821', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, - { universalIdentifier: '76d8a2c2-259f-49cc-8921-6ebad06ac276', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, - { universalIdentifier: '4d31d04c-f90e-413a-9c67-38cb6a3594a3', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, + { universalIdentifier: '0028769e-bca1-596c-aafe-fc5531db661a', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, + { universalIdentifier: '1fa50163-249f-5803-bbce-541ed672feea', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, + { universalIdentifier: 'f9905717-131e-59df-99a9-44f33a2beded', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, + { universalIdentifier: 'bdacee9b-9cb3-5948-9421-6908309c21b9', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, + { universalIdentifier: '62292205-0c46-55b9-be01-f15297b66d87', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, + { universalIdentifier: 'b9814d60-e66b-57c2-a67b-3ecd4eddbc43', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, + { universalIdentifier: '67643943-04f2-5b7c-9592-0d69824a3967', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, ], filters: [ { - universalIdentifier: 'ee754547-ed53-4d9e-8155-fbf58d8ed016', + universalIdentifier: '1f9a7d8e-19d2-5d39-8783-65529e0048fa', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, operand: ViewFilterOperand.IS, value: 'COMPLETED', diff --git a/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock b/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock index 1a5c578fceb..ce6c1ae15a9 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock +++ b/packages/twenty-apps/community/meeting-baas-recorder/yarn.lock @@ -5,6 +5,16 @@ __metadata: version: 8 cacheKey: 10c0 +"@alcalzone/ansi-tokenize@npm:^0.2.4": + version: 0.2.5 + resolution: "@alcalzone/ansi-tokenize@npm:0.2.5" + dependencies: + ansi-styles: "npm:^6.2.1" + is-fullwidth-code-point: "npm:^5.0.0" + checksum: 10c0/dd8622288426b5b7dbf8b68d51d4b930cea591d0e3b9dbc3f523131464d78ac922c165fcb7ba5307f133d35dbcaa0e6648a1c3fb6a0dc2725546d6f867b70af4 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": version: 7.29.0 resolution: "@babel/code-frame@npm:7.29.0" @@ -407,6 +417,19 @@ __metadata: languageName: node linkType: hard +"@emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + "@emotion/sheet": "npm:^1.4.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + stylis: "npm:4.2.0" + checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724 + languageName: node + linkType: hard + "@emotion/hash@npm:^0.9.2": version: 0.9.2 resolution: "@emotion/hash@npm:0.9.2" @@ -430,6 +453,27 @@ __metadata: languageName: node linkType: hard +"@emotion/react@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + hoist-non-react-statics: "npm:^3.3.1" + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031 + languageName: node + linkType: hard + "@emotion/serialize@npm:^1.3.3": version: 1.3.3 resolution: "@emotion/serialize@npm:1.3.3" @@ -443,6 +487,13 @@ __metadata: languageName: node linkType: hard +"@emotion/sheet@npm:^1.4.0": + version: 1.4.0 + resolution: "@emotion/sheet@npm:1.4.0" + checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7 + languageName: node + linkType: hard + "@emotion/styled@npm:^11.14.0": version: 11.14.1 resolution: "@emotion/styled@npm:11.14.1" @@ -486,6 +537,377 @@ __metadata: languageName: node linkType: hard +"@emotion/weak-memoize@npm:^0.4.0": + version: 0.4.0 + resolution: "@emotion/weak-memoize@npm:0.4.0" + checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/aix-ppc64@npm:0.25.12" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/aix-ppc64@npm:0.27.7" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm64@npm:0.25.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-arm64@npm:0.27.7" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm@npm:0.25.12" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-arm@npm:0.27.7" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-x64@npm:0.25.12" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-x64@npm:0.27.7" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-arm64@npm:0.25.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/darwin-arm64@npm:0.27.7" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-x64@npm:0.25.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/darwin-x64@npm:0.27.7" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-arm64@npm:0.25.12" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/freebsd-arm64@npm:0.27.7" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-x64@npm:0.25.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/freebsd-x64@npm:0.27.7" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm64@npm:0.25.12" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-arm64@npm:0.27.7" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm@npm:0.25.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-arm@npm:0.27.7" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ia32@npm:0.25.12" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-ia32@npm:0.27.7" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-loong64@npm:0.25.12" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-loong64@npm:0.27.7" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-mips64el@npm:0.25.12" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-mips64el@npm:0.27.7" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ppc64@npm:0.25.12" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-ppc64@npm:0.27.7" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-riscv64@npm:0.25.12" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-riscv64@npm:0.27.7" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-s390x@npm:0.25.12" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-s390x@npm:0.27.7" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-x64@npm:0.25.12" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-x64@npm:0.27.7" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-arm64@npm:0.25.12" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/netbsd-arm64@npm:0.27.7" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-x64@npm:0.25.12" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/netbsd-x64@npm:0.27.7" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-arm64@npm:0.25.12" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openbsd-arm64@npm:0.27.7" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-x64@npm:0.25.12" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openbsd-x64@npm:0.27.7" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openharmony-arm64@npm:0.25.12" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openharmony-arm64@npm:0.27.7" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/sunos-x64@npm:0.25.12" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/sunos-x64@npm:0.27.7" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-arm64@npm:0.25.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-arm64@npm:0.27.7" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-ia32@npm:0.25.12" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-ia32@npm:0.27.7" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-x64@npm:0.25.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-x64@npm:0.27.7" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@fastify/busboy@npm:^2.0.0": version: 2.1.1 resolution: "@fastify/busboy@npm:2.1.1" @@ -526,6 +948,26 @@ __metadata: languageName: node linkType: hard +"@genql/runtime@npm:^2.10.0": + version: 2.10.0 + resolution: "@genql/runtime@npm:2.10.0" + dependencies: + "@types/qs": "npm:^6.9.0" + "@types/ws": "npm:^6.0.1" + graphql-query-batcher: "npm:^1.0.1" + isomorphic-unfetch: "npm:^3.0.0" + lodash: "npm:^4.17.20" + subscriptions-transport-ws: "npm:^0.9.16" + tslib: "npm:^2.0.0" + utility-types: "npm:^3.10.0" + ws: "npm:^6.1.4" + zen-observable-ts: "npm:^0.8.21" + peerDependencies: + graphql: "*" + checksum: 10c0/e2a886c2469c933681e2b0ddd6a5b7f4cb12932251ba460e3cf2db4246817da79313ea4ba9769ec7cbe53ab9c1cb81ad8fcce6a969cd241185b79398d2a4f3c6 + languageName: node + linkType: hard + "@graphql-tools/graphql-file-loader@npm:^7.5.11": version: 7.5.17 resolution: "@graphql-tools/graphql-file-loader@npm:7.5.17" @@ -1152,6 +1594,181 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.60.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-android-arm64@npm:4.60.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.60.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.60.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.60.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.60.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.60.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.60.1" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.60.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.60.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.60.1" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.60.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.60.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.60.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.60.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.60.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.60.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.60.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.60.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.60.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.60.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.60.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.60.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.60.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.60.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@samverschueren/stream-to-observable@npm:^0.3.0": version: 0.3.1 resolution: "@samverschueren/stream-to-observable@npm:0.3.1" @@ -1191,6 +1808,13 @@ __metadata: languageName: node linkType: hard +"@sniptt/guards@npm:^0.2.0": + version: 0.2.0 + resolution: "@sniptt/guards@npm:0.2.0" + checksum: 10c0/749bb0f550d1ddd4abdb23dc1076cba26e977659922b73d000bceeb253c09270aaced0d18e92f09d4ce2fdaae33e55f60f2142f5359bc90ba505422af0873526 + languageName: node + linkType: hard + "@types/babel__core@npm:^7.1.14": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -1232,6 +1856,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.3": version: 4.1.9 resolution: "@types/graceful-fs@npm:4.1.9" @@ -1326,6 +1957,13 @@ __metadata: languageName: node linkType: hard +"@types/qs@npm:^6.9.0": + version: 6.15.0 + resolution: "@types/qs@npm:6.15.0" + checksum: 10c0/1b104cac50e655fc41d7fc1de2c2aba2908c4cf833a555b6808fb4c96752662b439238f2392a15d2590a7a6ca75dbd40e42d9378ac2be0d548ee484954363688 + languageName: node + linkType: hard + "@types/react@npm:^18.2.0": version: 18.3.28 resolution: "@types/react@npm:18.3.28" @@ -1350,6 +1988,15 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:^6.0.1": + version: 6.0.4 + resolution: "@types/ws@npm:6.0.4" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/fa958e64596ca9487c3ed6012834de70b47f25d971f1950cfb8e6a99cb77ff340ae82ac7627744e01b58010674ef8ede07d5a2ac29ca9ad0d67a430fcc69ae14 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.3 resolution: "@types/yargs-parser@npm:21.0.3" @@ -1396,6 +2043,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^7.3.0": + version: 7.3.0 + resolution: "ansi-escapes@npm:7.3.0" + dependencies: + environment: "npm:^1.0.0" + checksum: 10c0/068961d99f0ef28b661a4a9f84a5d645df93ccf3b9b93816cc7d46bbe1913321d4cdf156bb842a4e1e4583b7375c631fa963efb43001c4eb7ff9ab8f78fc0679 + languageName: node + linkType: hard + "ansi-regex@npm:^2.0.0": version: 2.1.1 resolution: "ansi-regex@npm:2.1.1" @@ -1417,6 +2073,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^6.2.2": + version: 6.2.2 + resolution: "ansi-regex@npm:6.2.2" + checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f + languageName: node + linkType: hard + "ansi-styles@npm:^2.2.1": version: 2.2.1 resolution: "ansi-styles@npm:2.2.1" @@ -1449,6 +2112,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 + languageName: node + linkType: hard + "any-observable@npm:^0.3.0": version: 0.3.0 resolution: "any-observable@npm:0.3.0" @@ -1496,6 +2166,13 @@ __metadata: languageName: node linkType: hard +"async-limiter@npm:~1.0.0": + version: 1.0.1 + resolution: "async-limiter@npm:1.0.1" + checksum: 10c0/0693d378cfe86842a70d4c849595a0bb50dc44c11649640ca982fa90cbfc74e3cc4753b5a0847e51933f2e9c65ce8e05576e75e5e1fd963a086e673735b35969 + languageName: node + linkType: hard + "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -1503,6 +2180,13 @@ __metadata: languageName: node linkType: hard +"auto-bind@npm:^5.0.1": + version: 5.0.1 + resolution: "auto-bind@npm:5.0.1" + checksum: 10c0/a703375350ea7b6e92405d8e6bcc6dbfb84b0d7c7172b33e5788a7593929a18227999ff9aa9c32436741d06d021e6672457b1cec73287efe3fab95cff6627eaf + languageName: node + linkType: hard + "axios@npm:1.8.3": version: 1.8.3 resolution: "axios@npm:1.8.3" @@ -1514,7 +2198,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.13.1, axios@npm:^1.6.0": +"axios@npm:^1.13.1": version: 1.13.6 resolution: "axios@npm:1.13.6" dependencies: @@ -1525,6 +2209,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.13.5": + version: 1.15.0 + resolution: "axios@npm:1.15.0" + dependencies: + follow-redirects: "npm:^1.15.11" + form-data: "npm:^4.0.5" + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/47e0f860e98d4d7aa145e89ce0cae00e1fb0f1d2485f065c21fce955ddb1dba4103a46bd0e47acd18a27208a7f62c96249e620db575521b92a968619ab133409 + languageName: node + linkType: hard + "babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -1615,6 +2310,13 @@ __metadata: languageName: node linkType: hard +"backo2@npm:^1.0.2": + version: 1.0.2 + resolution: "backo2@npm:1.0.2" + checksum: 10c0/a9e825a6a38a6d1c4a94476eabc13d6127dfaafb0967baf104affbb67806ae26abbb58dab8d572d2cd21ef06634ff57c3ad48dff14b904e18de1474cc2f22bf3 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -1807,7 +2509,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.3.0": +"chalk@npm:^5.3.0, chalk@npm:^5.6.0": version: 5.6.2 resolution: "chalk@npm:5.6.2" checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 @@ -1858,6 +2560,13 @@ __metadata: languageName: node linkType: hard +"cli-boxes@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-boxes@npm:3.0.0" + checksum: 10c0/4db3e8fbfaf1aac4fb3a6cbe5a2d3fa048bee741a45371b906439b9ffc821c6e626b0f108bdcd3ddf126a4a319409aedcf39a0730573ff050fdd7b6731e99fb9 + languageName: node + linkType: hard + "cli-cursor@npm:^2.0.0, cli-cursor@npm:^2.1.0": version: 2.1.0 resolution: "cli-cursor@npm:2.1.0" @@ -1867,6 +2576,15 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-cursor@npm:4.0.0" + dependencies: + restore-cursor: "npm:^4.0.0" + checksum: 10c0/e776e8c3c6727300d0539b0d25160b2bb56aed1a63942753ba1826b012f337a6f4b7ace3548402e4f2f13b5e16bfd751be672c44b203205e7eca8be94afec42c + languageName: node + linkType: hard + "cli-truncate@npm:^0.2.1": version: 0.2.1 resolution: "cli-truncate@npm:0.2.1" @@ -1877,6 +2595,16 @@ __metadata: languageName: node linkType: hard +"cli-truncate@npm:^5.1.1": + version: 5.2.0 + resolution: "cli-truncate@npm:5.2.0" + dependencies: + slice-ansi: "npm:^8.0.0" + string-width: "npm:^8.2.0" + checksum: 10c0/0d4ec94702ca85b64522ac93633837fb5ea7db17b79b1322a60f6045e6ae2b8cd7bd4c1d19ac7d1f9e10e3bbda1112e172e439b68c02b785ee00da8d6a5c5471 + languageName: node + linkType: hard + "cli-width@npm:^4.1.0": version: 4.1.0 resolution: "cli-width@npm:4.1.0" @@ -1913,6 +2641,15 @@ __metadata: languageName: node linkType: hard +"code-excerpt@npm:^4.0.0": + version: 4.0.0 + resolution: "code-excerpt@npm:4.0.0" + dependencies: + convert-to-spaces: "npm:^2.0.1" + checksum: 10c0/b6c5a06e039cecd2ab6a0e10ee0831de8362107d1f298ca3558b5f9004cb8e0260b02dd6c07f57b9a0e346c76864d2873311ee1989809fdeb05bd5fbbadde773 + languageName: node + linkType: hard + "code-point-at@npm:^1.0.0": version: 1.1.0 resolution: "code-point-at@npm:1.1.0" @@ -1996,6 +2733,13 @@ __metadata: languageName: node linkType: hard +"convert-to-spaces@npm:^2.0.1": + version: 2.0.1 + resolution: "convert-to-spaces@npm:2.0.1" + checksum: 10c0/d90aa0e3b6a27f9d5265a8d32def3c5c855b3e823a9db1f26d772f8146d6b91020a2fdfd905ce8048a73fad3aaf836fef8188c67602c374405e2ae8396c4ac46 + languageName: node + linkType: hard + "cosmiconfig@npm:^7.0.0": version: 7.1.0 resolution: "cosmiconfig@npm:7.1.0" @@ -2158,6 +2902,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 10c0/1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -2172,6 +2923,13 @@ __metadata: languageName: node linkType: hard +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d + languageName: node + linkType: hard + "error-ex@npm:^1.3.1": version: 1.3.4 resolution: "error-ex@npm:1.3.4" @@ -2216,6 +2974,196 @@ __metadata: languageName: node linkType: hard +"es-toolkit@npm:^1.39.10": + version: 1.45.1 + resolution: "es-toolkit@npm:1.45.1" + dependenciesMeta: + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + checksum: 10c0/b19180c778af8fe2fb450e8e05a5793166c91e0aa66b87d9fcfcc5618bd33e6ceec9c103e074458d32b2044972dc4fc63631b3b615834fde261917e9561f6f59 + languageName: node + linkType: hard + +"esbuild@npm:^0.25.0": + version: 0.25.12 + resolution: "esbuild@npm:0.25.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.12" + "@esbuild/android-arm": "npm:0.25.12" + "@esbuild/android-arm64": "npm:0.25.12" + "@esbuild/android-x64": "npm:0.25.12" + "@esbuild/darwin-arm64": "npm:0.25.12" + "@esbuild/darwin-x64": "npm:0.25.12" + "@esbuild/freebsd-arm64": "npm:0.25.12" + "@esbuild/freebsd-x64": "npm:0.25.12" + "@esbuild/linux-arm": "npm:0.25.12" + "@esbuild/linux-arm64": "npm:0.25.12" + "@esbuild/linux-ia32": "npm:0.25.12" + "@esbuild/linux-loong64": "npm:0.25.12" + "@esbuild/linux-mips64el": "npm:0.25.12" + "@esbuild/linux-ppc64": "npm:0.25.12" + "@esbuild/linux-riscv64": "npm:0.25.12" + "@esbuild/linux-s390x": "npm:0.25.12" + "@esbuild/linux-x64": "npm:0.25.12" + "@esbuild/netbsd-arm64": "npm:0.25.12" + "@esbuild/netbsd-x64": "npm:0.25.12" + "@esbuild/openbsd-arm64": "npm:0.25.12" + "@esbuild/openbsd-x64": "npm:0.25.12" + "@esbuild/openharmony-arm64": "npm:0.25.12" + "@esbuild/sunos-x64": "npm:0.25.12" + "@esbuild/win32-arm64": "npm:0.25.12" + "@esbuild/win32-ia32": "npm:0.25.12" + "@esbuild/win32-x64": "npm:0.25.12" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b + languageName: node + linkType: hard + +"esbuild@npm:^0.27.0": + version: 0.27.7 + resolution: "esbuild@npm:0.27.7" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.7" + "@esbuild/android-arm": "npm:0.27.7" + "@esbuild/android-arm64": "npm:0.27.7" + "@esbuild/android-x64": "npm:0.27.7" + "@esbuild/darwin-arm64": "npm:0.27.7" + "@esbuild/darwin-x64": "npm:0.27.7" + "@esbuild/freebsd-arm64": "npm:0.27.7" + "@esbuild/freebsd-x64": "npm:0.27.7" + "@esbuild/linux-arm": "npm:0.27.7" + "@esbuild/linux-arm64": "npm:0.27.7" + "@esbuild/linux-ia32": "npm:0.27.7" + "@esbuild/linux-loong64": "npm:0.27.7" + "@esbuild/linux-mips64el": "npm:0.27.7" + "@esbuild/linux-ppc64": "npm:0.27.7" + "@esbuild/linux-riscv64": "npm:0.27.7" + "@esbuild/linux-s390x": "npm:0.27.7" + "@esbuild/linux-x64": "npm:0.27.7" + "@esbuild/netbsd-arm64": "npm:0.27.7" + "@esbuild/netbsd-x64": "npm:0.27.7" + "@esbuild/openbsd-arm64": "npm:0.27.7" + "@esbuild/openbsd-x64": "npm:0.27.7" + "@esbuild/openharmony-arm64": "npm:0.27.7" + "@esbuild/sunos-x64": "npm:0.27.7" + "@esbuild/win32-arm64": "npm:0.27.7" + "@esbuild/win32-ia32": "npm:0.27.7" + "@esbuild/win32-x64": "npm:0.27.7" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/ccd51f0555708bc9ff4ec9dc3ac92d3daacd45ecaac949ca8645984c5c323bf8cefe98c2df307418685e0b4ce37f9a3bdbfe8e3651fe632a0059a436195a17d4 + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -2254,6 +3202,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^3.1.0": + version: 3.1.2 + resolution: "eventemitter3@npm:3.1.2" + checksum: 10c0/c67262eccbf85848b7cc6d4abb6c6e34155e15686db2a01c57669fd0d44441a574a19d44d25948b442929e065774cbe5003d8e77eed47674fbf876ac77887793 + languageName: node + linkType: hard + "execa@npm:^5.0.0": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -2438,17 +3393,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.2.0": - version: 11.3.3 - resolution: "fs-extra@npm:11.3.3" - dependencies: - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10c0/984924ff4104e3e9f351b658a864bf3b354b2c90429f57aec0acd12d92c4e6b762cbacacdffb4e745b280adce882e1f980c485d9f02c453f769ab4e7fc646ce3 - languageName: node - linkType: hard - "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -2465,7 +3409,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2": +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -2475,7 +3419,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -2512,6 +3456,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0": + version: 1.5.0 + resolution: "get-east-asian-width@npm:1.5.0" + checksum: 10c0/bff8bbc8d81790b9477f7aa55b1806b9f082a8dc1359fff7bd8b96939622c86b729685afc2bfeb22def1fc6ef1e5228e4d87dd4e6da60bc43a5edfb03c4ee167 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": version: 1.3.1 resolution: "get-intrinsic@npm:1.3.1" @@ -2605,6 +3556,13 @@ __metadata: languageName: node linkType: hard +"globrex@npm:^0.1.2": + version: 0.1.2 + resolution: "globrex@npm:0.1.2" + checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 + languageName: node + linkType: hard + "gopd@npm:^1.2.0": version: 1.2.0 resolution: "gopd@npm:1.2.0" @@ -2619,6 +3577,13 @@ __metadata: languageName: node linkType: hard +"graphql-query-batcher@npm:^1.0.1": + version: 1.0.1 + resolution: "graphql-query-batcher@npm:1.0.1" + checksum: 10c0/804d0f4064721a2116a16b9eac422e9233e85f4ab5b250cb8f83662725658ffde35779a8ae8211037f3dd2f9717de8cb63b394ad8057ce22171a83db2471196a + languageName: node + linkType: hard + "graphql-sse@npm:^2.5.4": version: 2.6.0 resolution: "graphql-sse@npm:2.6.0" @@ -2701,6 +3666,15 @@ __metadata: languageName: node linkType: hard +"hoist-non-react-statics@npm:^3.3.1": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -2803,6 +3777,13 @@ __metadata: languageName: node linkType: hard +"indent-string@npm:^5.0.0": + version: 5.0.0 + resolution: "indent-string@npm:5.0.0" + checksum: 10c0/8ee77b57d92e71745e133f6f444d6fa3ed503ad0e1bcd7e80c8da08b42375c07117128d670589725ed07b1978065803fa86318c309ba45415b7fe13e7f170220 + languageName: node + linkType: hard + "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -2820,6 +3801,48 @@ __metadata: languageName: node linkType: hard +"ink@npm:^6.8.0": + version: 6.8.0 + resolution: "ink@npm:6.8.0" + dependencies: + "@alcalzone/ansi-tokenize": "npm:^0.2.4" + ansi-escapes: "npm:^7.3.0" + ansi-styles: "npm:^6.2.1" + auto-bind: "npm:^5.0.1" + chalk: "npm:^5.6.0" + cli-boxes: "npm:^3.0.0" + cli-cursor: "npm:^4.0.0" + cli-truncate: "npm:^5.1.1" + code-excerpt: "npm:^4.0.0" + es-toolkit: "npm:^1.39.10" + indent-string: "npm:^5.0.0" + is-in-ci: "npm:^2.0.0" + patch-console: "npm:^2.0.0" + react-reconciler: "npm:^0.33.0" + scheduler: "npm:^0.27.0" + signal-exit: "npm:^3.0.7" + slice-ansi: "npm:^8.0.0" + stack-utils: "npm:^2.0.6" + string-width: "npm:^8.1.1" + terminal-size: "npm:^4.0.1" + type-fest: "npm:^5.4.1" + widest-line: "npm:^6.0.0" + wrap-ansi: "npm:^9.0.0" + ws: "npm:^8.18.0" + yoga-layout: "npm:~3.2.1" + peerDependencies: + "@types/react": ">=19.0.0" + react: ">=19.0.0" + react-devtools-core: ">=6.1.2" + peerDependenciesMeta: + "@types/react": + optional: true + react-devtools-core: + optional: true + checksum: 10c0/50500e547fdf6a1f1d836d6befbd4770e3ab649ef0be1884500a6da411fb68a90e22dd7dcc9c404911d30e9f87506b3b9d8e997c6c6ceac85ee054b4dadefaff + languageName: node + linkType: hard + "inquirer@npm:^10.0.0": version: 10.2.2 resolution: "inquirer@npm:10.2.2" @@ -2889,6 +3912,15 @@ __metadata: languageName: node linkType: hard +"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0": + version: 5.1.0 + resolution: "is-fullwidth-code-point@npm:5.1.0" + dependencies: + get-east-asian-width: "npm:^1.3.1" + checksum: 10c0/c1172c2e417fb73470c56c431851681591f6a17233603a9e6f94b7ba870b2e8a5266506490573b607fb1081318589372034aa436aec07b465c2029c0bc7f07a4 + languageName: node + linkType: hard + "is-generator-fn@npm:^2.0.0": version: 2.1.0 resolution: "is-generator-fn@npm:2.1.0" @@ -2905,6 +3937,15 @@ __metadata: languageName: node linkType: hard +"is-in-ci@npm:^2.0.0": + version: 2.0.0 + resolution: "is-in-ci@npm:2.0.0" + bin: + is-in-ci: cli.js + checksum: 10c0/1e1d1056939a681e8206035de5ad84e0404556eaa7622bb55f0f1868b9788bff3df427bc0b1ed5a172623154a90fcb1e759a230817cd73d09435543ae3c71feb + languageName: node + linkType: hard + "is-number@npm:^7.0.0": version: 7.0.0 resolution: "is-number@npm:7.0.0" @@ -2956,6 +3997,16 @@ __metadata: languageName: node linkType: hard +"isomorphic-unfetch@npm:^3.0.0": + version: 3.1.0 + resolution: "isomorphic-unfetch@npm:3.1.0" + dependencies: + node-fetch: "npm:^2.6.1" + unfetch: "npm:^4.2.0" + checksum: 10c0/d3b61fca06304db692b7f76bdfd3a00f410e42cfa7403c3b250546bf71589d18cf2f355922f57198e4cc4a9872d3647b20397a5c3edf1a347c90d57c83cf2a89 + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" @@ -3021,6 +4072,13 @@ __metadata: languageName: node linkType: hard +"iterall@npm:^1.2.1": + version: 1.3.0 + resolution: "iterall@npm:1.3.0" + checksum: 10c0/40de624e5fe937c4c0e511981b91caea9ff2142bfc0316cccc8506eaa03aa253820cc17c5bc5f0a98706c7268a373e5ebee9af9a0c8a359730cf7c05938b57b5 + languageName: node + linkType: hard + "jest-changed-files@npm:^29.7.0": version: 29.7.0 resolution: "jest-changed-files@npm:29.7.0" @@ -3615,27 +4673,6 @@ __metadata: languageName: node linkType: hard -"lodash.camelcase@npm:^4.3.0": - version: 4.3.0 - resolution: "lodash.camelcase@npm:4.3.0" - checksum: 10c0/fcba15d21a458076dd309fce6b1b4bf611d84a0ec252cb92447c948c533ac250b95d2e00955801ebc367e5af5ed288b996d75d37d2035260a937008e14eaf432 - languageName: node - linkType: hard - -"lodash.capitalize@npm:^4.2.1": - version: 4.2.1 - resolution: "lodash.capitalize@npm:4.2.1" - checksum: 10c0/b289326497c2e24d6b8afa2af2ca4e068ef6ef007ade36bfb6f70af77ce10ea3f090eeee947d5fdcf2db4bcfa4703c8c10a5857a2b39e308bddfd1d11ad35970 - languageName: node - linkType: hard - -"lodash.kebabcase@npm:^4.1.1": - version: 4.1.1 - resolution: "lodash.kebabcase@npm:4.1.1" - checksum: 10c0/da5d8f41dbb5bc723d4bf9203d5096ca8da804d6aec3d2b56457156ba6c8d999ff448d347ebd97490da853cb36696ea4da09a431499f1ee8deb17b094ecf4e33 - languageName: node - linkType: hard - "lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -3643,10 +4680,10 @@ __metadata: languageName: node linkType: hard -"lodash.startcase@npm:^4.4.0": - version: 4.4.0 - resolution: "lodash.startcase@npm:4.4.0" - checksum: 10c0/bd82aa87a45de8080e1c5ee61128c7aee77bf7f1d86f4ff94f4a6d7438fc9e15e5f03374b947be577a93804c8ad6241f0251beaf1452bf716064eeb657b3a9f0 +"lodash@npm:^4.17.20": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 languageName: node linkType: hard @@ -3759,6 +4796,7 @@ __metadata: version: 0.0.0-use.local resolution: "meeting-baas-recorder@workspace:." dependencies: + "@emotion/react": "npm:^11.14.0" "@emotion/styled": "npm:^11.14.0" "@meeting-baas/sdk": "npm:^6.0.5" "@types/jest": "npm:^29.5.5" @@ -3768,7 +4806,7 @@ __metadata: jest: "npm:^29.7.0" react: "npm:^18.2.0" ts-jest: "npm:^29.1.1" - twenty-sdk: "npm:0.1.3" + twenty-sdk: "npm:0.8.0" typescript: "npm:^5.9.3" languageName: unknown linkType: soft @@ -3953,6 +4991,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + "native-fetch@npm:^4.0.2": version: 4.0.2 resolution: "native-fetch@npm:4.0.2" @@ -3983,6 +5030,20 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^2.6.1": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 12.2.0 resolution: "node-gyp@npm:12.2.0" @@ -4092,7 +5153,7 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^5.1.2": +"onetime@npm:^5.1.0, onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" dependencies: @@ -4177,6 +5238,13 @@ __metadata: languageName: node linkType: hard +"patch-console@npm:^2.0.0": + version: 2.0.0 + resolution: "patch-console@npm:2.0.0" + checksum: 10c0/486602591a0af7af8d4c76d8eea42cad32b6de7200488819c6383c75e43733ca7bdc80e30f2e68ce05f06a1607cce1683a1706c6672ca27dada1921b366e8f1c + languageName: node + linkType: hard + "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -4243,6 +5311,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + "pirates@npm:^4.0.4": version: 4.0.7 resolution: "pirates@npm:4.0.7" @@ -4259,6 +5334,24 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.6": + version: 8.5.10 + resolution: "postcss@npm:8.5.10" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/c592dffa0c4873b401f01955b265538d9942f425040df5e2b8f0ad34c83773a792ea0fa5859ccc99cfb5b955b4ebff118ab7056315388dc83b107b0fa8313576 + languageName: node + linkType: hard + +"preact@npm:^10.28.3": + version: 10.29.1 + resolution: "preact@npm:10.29.1" + checksum: 10c0/55da3994d0d9ebcc26476552c6571f8be1150cccdafe842aad40eff9abc84c867555f6d01db386fc63ac4cbe9f9da245002468bb24df0642365162d8f1b545e2 + languageName: node + linkType: hard + "prettier@npm:^2.8.0": version: 2.8.8 resolution: "prettier@npm:2.8.8" @@ -4303,6 +5396,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 + languageName: node + linkType: hard + "pure-rand@npm:^6.0.0": version: 6.1.0 resolution: "pure-rand@npm:6.1.0" @@ -4326,6 +5426,24 @@ __metadata: languageName: node linkType: hard +"react-dom@npm:^19.0.0": + version: 19.2.5 + resolution: "react-dom@npm:19.2.5" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.5 + checksum: 10c0/8067606e9f58e4c2e8cb5f09570217dbc71c4843ebcaa20ae2085912d3e3a351f17d8f7c1713313cdda7f272840c8c34ff6c860fcb840862071bceea218e0c63 + languageName: node + linkType: hard + +"react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + "react-is@npm:^18.0.0": version: 18.3.1 resolution: "react-is@npm:18.3.1" @@ -4333,6 +5451,17 @@ __metadata: languageName: node linkType: hard +"react-reconciler@npm:^0.33.0": + version: 0.33.0 + resolution: "react-reconciler@npm:0.33.0" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.0 + checksum: 10c0/3f7b27ea8d0ff4c8bf0e402a285e1af9b7d0e6f4c1a70a28f4384938bc1130bc82a90a31df0b79ef5e380e2e55e2598bd90b4dbf802b1203d735ba0355817d3a + languageName: node + linkType: hard + "react@npm:^18.2.0": version: 18.3.1 resolution: "react@npm:18.3.1" @@ -4342,6 +5471,13 @@ __metadata: languageName: node linkType: hard +"react@npm:^19.0.0": + version: 19.2.5 + resolution: "react@npm:19.2.5" + checksum: 10c0/4b5f231dbef92886f602533c9ce3bde04d99f0e71dfb5d794c43e02726efaad0421c08688f75fc98a6d6e1dc017372e1af7abbfecdc86a79968f461675931a7a + languageName: node + linkType: hard + "readdirp@npm:^4.0.1": version: 4.1.2 resolution: "readdirp@npm:4.1.2" @@ -4464,6 +5600,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^4.0.0": + version: 4.0.0 + resolution: "restore-cursor@npm:4.0.0" + dependencies: + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/6f7da8c5e422ac26aa38354870b1afac09963572cf2879443540449068cb43476e9cbccf6f8de3e0171e0d6f7f533c2bc1a0a008003c9a525bbc098e89041318 + languageName: node + linkType: hard + "retry@npm:^0.13.1": version: 0.13.1 resolution: "retry@npm:0.13.1" @@ -4489,6 +5635,96 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.43.0": + version: 4.60.1 + resolution: "rollup@npm:4.60.1" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.60.1" + "@rollup/rollup-android-arm64": "npm:4.60.1" + "@rollup/rollup-darwin-arm64": "npm:4.60.1" + "@rollup/rollup-darwin-x64": "npm:4.60.1" + "@rollup/rollup-freebsd-arm64": "npm:4.60.1" + "@rollup/rollup-freebsd-x64": "npm:4.60.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.60.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.60.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.60.1" + "@rollup/rollup-linux-loong64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-loong64-musl": "npm:4.60.1" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-ppc64-musl": "npm:4.60.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-riscv64-musl": "npm:4.60.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.60.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-x64-musl": "npm:4.60.1" + "@rollup/rollup-openbsd-x64": "npm:4.60.1" + "@rollup/rollup-openharmony-arm64": "npm:4.60.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.60.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.60.1" + "@rollup/rollup-win32-x64-gnu": "npm:4.60.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.60.1" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/48d3f2216b5533639b007e6756e2275c7f594e45adee21ce03674aa2e004406c661f8b86c7a0b471c9e889c6a9efbb29240ca0b7673c50e391406c490c309833 + languageName: node + linkType: hard + "run-async@npm:^3.0.0": version: 3.0.0 resolution: "run-async@npm:3.0.0" @@ -4530,6 +5766,13 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 + languageName: node + linkType: hard + "semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -4654,6 +5897,16 @@ __metadata: languageName: node linkType: hard +"slice-ansi@npm:^8.0.0": + version: 8.0.0 + resolution: "slice-ansi@npm:8.0.0" + dependencies: + ansi-styles: "npm:^6.2.3" + is-fullwidth-code-point: "npm:^5.1.0" + checksum: 10c0/0ce4aa91febb7cea4a00c2c27bb820fa53b6d2862ce0f80f7120134719f7914fc416b0ed966cf35250a3169e152916392f35917a2d7cad0fcc5d8b841010fa9a + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -4682,6 +5935,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + "source-map-support@npm:0.5.13": version: 0.5.13 resolution: "source-map-support@npm:0.5.13" @@ -4722,7 +5982,7 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.3": +"stack-utils@npm:^2.0.3, stack-utils@npm:^2.0.6": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" dependencies: @@ -4773,6 +6033,27 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.0.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"string-width@npm:^8.1.0, string-width@npm:^8.1.1, string-width@npm:^8.2.0": + version: 8.2.0 + resolution: "string-width@npm:8.2.0" + dependencies: + get-east-asian-width: "npm:^1.5.0" + strip-ansi: "npm:^7.1.2" + checksum: 10c0/d8915428b43519b0f494da6590dbe4491857d8a12e40250e50fc01fbb616ffd8400a436bbe25712255ee129511fe0414c49d3b6b9627e2bc3a33dcec1d2eda02 + languageName: node + linkType: hard + "strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": version: 3.0.1 resolution: "strip-ansi@npm:3.0.1" @@ -4800,6 +6081,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" + dependencies: + ansi-regex: "npm:^6.2.2" + checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 + languageName: node + linkType: hard + "strip-bom@npm:^4.0.0": version: 4.0.0 resolution: "strip-bom@npm:4.0.0" @@ -4828,6 +6118,21 @@ __metadata: languageName: node linkType: hard +"subscriptions-transport-ws@npm:^0.9.16": + version: 0.9.19 + resolution: "subscriptions-transport-ws@npm:0.9.19" + dependencies: + backo2: "npm:^1.0.2" + eventemitter3: "npm:^3.1.0" + iterall: "npm:^1.2.1" + symbol-observable: "npm:^1.0.4" + ws: "npm:^5.2.0 || ^6.0.0 || ^7.0.0" + peerDependencies: + graphql: ">=0.10.0" + checksum: 10c0/6f2ade56865f0ba291d3ff82c79781b051c2374873bac853286fedfdbc05001b8c4018ab7cba44af667ead7f573e48d18892d58a8f9ca8d90dfb4bff5c125045 + languageName: node + linkType: hard + "supports-color@npm:^2.0.0": version: 2.0.0 resolution: "supports-color@npm:2.0.0" @@ -4869,13 +6174,20 @@ __metadata: languageName: node linkType: hard -"symbol-observable@npm:^1.1.0": +"symbol-observable@npm:^1.0.4, symbol-observable@npm:^1.1.0": version: 1.2.0 resolution: "symbol-observable@npm:1.2.0" checksum: 10c0/009fee50798ef80ed4b8195048288f108b03de162db07493f2e1fd993b33fafa72d659e832b584da5a2427daa78e5a738fb2a9ab027ee9454252e0bedbcd1fdc languageName: node linkType: hard +"tagged-tag@npm:^1.0.0": + version: 1.0.0 + resolution: "tagged-tag@npm:1.0.0" + checksum: 10c0/91d25c9ffb86a91f20522cefb2cbec9b64caa1febe27ad0df52f08993ff60888022d771e868e6416cf2e72dab68449d2139e8709ba009b74c6c7ecd4000048d1 + languageName: node + linkType: hard + "tar@npm:^7.5.4": version: 7.5.9 resolution: "tar@npm:7.5.9" @@ -4889,6 +6201,13 @@ __metadata: languageName: node linkType: hard +"terminal-size@npm:^4.0.1": + version: 4.0.1 + resolution: "terminal-size@npm:4.0.1" + checksum: 10c0/89afd9d816dd9dbfe4499da9aeea70491bbde4ff4592226a9c8ac71074a7580afead6a78e95ecc35f6d42e09087b55ffcb1019302cd55e0cc957b6ce5c4847e8 + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -4910,6 +6229,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.15": + version: 0.2.16 + resolution: "tinyglobby@npm:0.2.16" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/f2e09fd93dd95c41e522113b686ff6f7c13020962f8698a864a257f3d7737599afc47722b7ab726e12f8a813f779906187911ff8ee6701ede65072671a7e934b + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -4935,6 +6264,13 @@ __metadata: languageName: node linkType: hard +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + "ts-jest@npm:^29.1.1": version: 29.4.6 resolution: "ts-jest@npm:29.4.6" @@ -4975,44 +6311,77 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.9.0": +"tsconfck@npm:^3.0.3": + version: 3.1.6 + resolution: "tsconfck@npm:3.1.6" + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + bin: + tsconfck: bin/tsconfck.js + checksum: 10c0/269c3c513540be44844117bb9b9258fe6f8aeab026d32aeebf458d5299125f330711429dbb556dbf125a0bc25f4a81e6c24ac96de2740badd295c3fb400f66c4 + languageName: node + linkType: hard + +"tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 languageName: node linkType: hard -"tslib@npm:^2.1.0, tslib@npm:^2.4.0": +"tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.4.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 languageName: node linkType: hard -"twenty-sdk@npm:0.1.3": - version: 0.1.3 - resolution: "twenty-sdk@npm:0.1.3" +"twenty-client-sdk@npm:0.8.0": + version: 0.8.0 + resolution: "twenty-client-sdk@npm:0.8.0" + dependencies: + "@genql/cli": "npm:^3.0.3" + "@genql/runtime": "npm:^2.10.0" + esbuild: "npm:^0.25.0" + graphql: "npm:^16.8.1" + checksum: 10c0/174a137994484769023236a66f4cdb498aa7a96c8ab4521e3386c6919743402d1888c898a2a7bb2d2483e1ee31b346b656d45ae0cffd3c9e456a642502a82b86 + languageName: node + linkType: hard + +"twenty-sdk@npm:0.8.0": + version: 0.8.0 + resolution: "twenty-sdk@npm:0.8.0" dependencies: "@genql/cli": "npm:^3.0.3" - axios: "npm:^1.6.0" + "@genql/runtime": "npm:^2.10.0" + "@sniptt/guards": "npm:^0.2.0" + axios: "npm:^1.13.5" chalk: "npm:^5.3.0" chokidar: "npm:^4.0.0" commander: "npm:^12.0.0" dotenv: "npm:^16.4.0" - fs-extra: "npm:^11.2.0" + esbuild: "npm:^0.25.0" graphql: "npm:^16.8.1" graphql-sse: "npm:^2.5.4" + ink: "npm:^6.8.0" inquirer: "npm:^10.0.0" jsonc-parser: "npm:^3.2.0" - lodash.camelcase: "npm:^4.3.0" - lodash.capitalize: "npm:^4.2.1" - lodash.kebabcase: "npm:^4.1.1" - lodash.startcase: "npm:^4.4.0" + preact: "npm:^10.28.3" + react: "npm:^19.0.0" + react-dom: "npm:^19.0.0" + tinyglobby: "npm:^0.2.15" + twenty-client-sdk: "npm:0.8.0" typescript: "npm:^5.9.2" uuid: "npm:^13.0.0" + vite: "npm:^7.0.0" + vite-tsconfig-paths: "npm:^4.2.1" + zod: "npm:^4.1.11" bin: twenty: dist/cli.cjs - checksum: 10c0/bb7a43331c61d34e250c7850e1746e6aac09fa8c552e0e19598b8ad44f56e3a9d51dc985d07da4c205ed179f49ba1ede52c6a9ed8be002689f70b571d1ac9c6d + checksum: 10c0/f38786981769b7dde40b37ad214136eaec785b2122cffed0709102e05e34e2794b951834f22ea9737879e2aef169f458b0d5d5dbfeb3c6162038985674503af8 languageName: node linkType: hard @@ -5037,6 +6406,15 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^5.4.1": + version: 5.5.0 + resolution: "type-fest@npm:5.5.0" + dependencies: + tagged-tag: "npm:^1.0.0" + checksum: 10c0/60bf79a8df45abf99490e3204eceb5cf7f915413f8a69fb578c75cab37ddcb7d29ee21f185f0e1617323ac0b2a441e001b8dc691e220d0b087e9c29ea205538c + languageName: node + linkType: hard + "typescript@npm:^5.9.2, typescript@npm:^5.9.3": version: 5.9.3 resolution: "typescript@npm:5.9.3" @@ -5096,6 +6474,13 @@ __metadata: languageName: node linkType: hard +"unfetch@npm:^4.2.0": + version: 4.2.0 + resolution: "unfetch@npm:4.2.0" + checksum: 10c0/a5c0a896a6f09f278b868075aea65652ad185db30e827cb7df45826fe5ab850124bf9c44c4dafca4bf0c55a0844b17031e8243467fcc38dd7a7d435007151f1b + languageName: node + linkType: hard + "unique-filename@npm:^5.0.0": version: 5.0.0 resolution: "unique-filename@npm:5.0.0" @@ -5144,6 +6529,13 @@ __metadata: languageName: node linkType: hard +"utility-types@npm:^3.10.0": + version: 3.11.0 + resolution: "utility-types@npm:3.11.0" + checksum: 10c0/2f1580137b0c3e6cf5405f37aaa8f5249961a76d26f1ca8efc0ff49a2fc0e0b2db56de8e521a174d075758e0c7eb3e590edec0832eb44478b958f09914920f19 + languageName: node + linkType: hard + "uuid@npm:^13.0.0": version: 13.0.0 resolution: "uuid@npm:13.0.0" @@ -5171,6 +6563,77 @@ __metadata: languageName: node linkType: hard +"vite-tsconfig-paths@npm:^4.2.1": + version: 4.3.2 + resolution: "vite-tsconfig-paths@npm:4.3.2" + dependencies: + debug: "npm:^4.1.1" + globrex: "npm:^0.1.2" + tsconfck: "npm:^3.0.3" + peerDependencies: + vite: "*" + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/f390ac1d1c3992fc5ac50f9274c1090f8b55ab34a89ea88893db9a6924a3b26c9f64bc1163615150ad100749db73b6b2cf1d57f6cd60df6e762ceb5b8ad30024 + languageName: node + linkType: hard + +"vite@npm:^7.0.0": + version: 7.3.2 + resolution: "vite@npm:7.3.2" + dependencies: + esbuild: "npm:^0.27.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/74be36907e208916f18bfec81c8eba18b869f0a170f1ece0a4dcb14874d0f0e7c022fb6c2ad896e3ee6c973fe88f53ac23b4078879ada340d8b263260868b8d4 + languageName: node + linkType: hard + "walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" @@ -5180,6 +6643,23 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + "which-module@npm:^2.0.0": version: 2.0.1 resolution: "which-module@npm:2.0.1" @@ -5209,6 +6689,15 @@ __metadata: languageName: node linkType: hard +"widest-line@npm:^6.0.0": + version: 6.0.0 + resolution: "widest-line@npm:6.0.0" + dependencies: + string-width: "npm:^8.1.0" + checksum: 10c0/735f1fdcd97fe765a07bb8b5e73c020bed8e53ab34e83ce0ef01693ba3c914d9e7977fe5f5facf0d0b670297a82dd5e376d3efa0896860dfcdaf7cd6924c0fb7 + languageName: node + linkType: hard + "wordwrap@npm:^1.0.0": version: 1.0.0 resolution: "wordwrap@npm:1.0.0" @@ -5248,6 +6737,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^9.0.0": + version: 9.0.2 + resolution: "wrap-ansi@npm:9.0.2" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/3305839b9a0d6fb930cb63a52f34d3936013d8b0682ff3ec133c9826512620f213800ffa19ea22904876d5b7e9a3c1f40682f03597d986a4ca881fa7b033688c + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -5265,6 +6765,45 @@ __metadata: languageName: node linkType: hard +"ws@npm:^5.2.0 || ^6.0.0 || ^7.0.0": + version: 7.5.10 + resolution: "ws@npm:7.5.10" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/bd7d5f4aaf04fae7960c23dcb6c6375d525e00f795dd20b9385902bd008c40a94d3db3ce97d878acc7573df852056ca546328b27b39f47609f80fb22a0a9b61d + languageName: node + linkType: hard + +"ws@npm:^6.1.4": + version: 6.2.3 + resolution: "ws@npm:6.2.3" + dependencies: + async-limiter: "npm:~1.0.0" + checksum: 10c0/56a35b9799993cea7ce2260197e7879f21bbbb194a967f31acbbda6f7f46ecda4365951966fb062044c95197e19fb2f053be6f65c172435455186835f494de41 + languageName: node + linkType: hard + +"ws@npm:^8.18.0": + version: 8.20.0 + resolution: "ws@npm:8.20.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/956ac5f11738c914089b65878b9223692ace77337ba55379ae68e1ecbeae9b47a0c6eb9403688f609999a58c80d83d99865fe0029b229d308b08c1ef93d4ea14 + languageName: node + linkType: hard + "y18n@npm:^4.0.0": version: 4.0.3 resolution: "y18n@npm:4.0.3" @@ -5372,9 +6911,40 @@ __metadata: languageName: node linkType: hard +"yoga-layout@npm:~3.2.1": + version: 3.2.1 + resolution: "yoga-layout@npm:3.2.1" + checksum: 10c0/9001e51be993c85e03757e5a04a2b61b8b30c9e5a7865d0156ca87a6431a3b717d51eb4990bfe588189fcfeac688dd9c3de707bbd50d1c344a84e63974cc54a8 + languageName: node + linkType: hard + +"zen-observable-ts@npm:^0.8.21": + version: 0.8.21 + resolution: "zen-observable-ts@npm:0.8.21" + dependencies: + tslib: "npm:^1.9.3" + zen-observable: "npm:^0.8.0" + checksum: 10c0/fe4a02f862b5f7e8ae0f86230c37b84c7d5611f5c206981afb4043e732d04cf7067a6cbe1ba82d20f18b735a3387937195a12542158a631d308ae3959a1d93c4 + languageName: node + linkType: hard + +"zen-observable@npm:^0.8.0": + version: 0.8.15 + resolution: "zen-observable@npm:0.8.15" + checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8 + languageName: node + linkType: hard + "zod@npm:3.24.2": version: 3.24.2 resolution: "zod@npm:3.24.2" checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565 languageName: node linkType: hard + +"zod@npm:^4.1.11": + version: 4.3.6 + resolution: "zod@npm:4.3.6" + checksum: 10c0/860d25a81ab41d33aa25f8d0d07b091a04acb426e605f396227a796e9e800c44723ed96d0f53a512b57be3d1520f45bf69c0cb3b378a232a00787a2609625307 + languageName: node + linkType: hard From 98003902def75ed1f57a71c721761ff38b08a80d Mon Sep 17 00:00:00 2001 From: Lazare-42 Date: Fri, 17 Apr 2026 14:10:52 +0200 Subject: [PATCH 5/5] chore: sync meeting-baas-recorder from standalone repo Syncs all fixes and features from the standalone meeting-baas-recorder repository (source of truth): - Fix REST response unwrapping (recordingPreference, calendarChannel, connectedAccount, workspaceMember lookups) - Add bot name/entry message settings and pass to Meeting BaaS API - Add bot.status_change webhook handling - Add batch-schedule-bots logic function - Add AI summary generation for transcripts - Add tests for webhook handler, validator, and sync service - Fix absolute imports, ViewKey enum shim Co-Authored-By: Claude Opus 4.6 --- .../__tests__/app-install.integration-test.ts | 76 +++ .../src/__tests__/setup-test.ts | 55 ++ .../src/__tests__/setup.ts | 13 - .../src/application-config.ts | 30 ++ .../src/constants/universal-identifiers.ts | 4 + .../calendar-event-on-recording.field.ts | 4 +- ...ng-preference-on-workspace-member.field.ts | 8 +- .../workspace-member-on-recording.field.ts | 4 +- .../meeting-baas-settings.front-component.tsx | 476 +++++++++--------- .../src/generate-summary.ts | 45 ++ .../meeting-baas-recorder/src/index.ts | 13 +- .../logic-functions/batch-schedule-bots.ts | 271 ++++++++++ .../on-calendar-event-created.ts | 2 +- .../on-calendar-event-updated.ts | 12 +- .../src/logic-functions/schedule-bot.ts | 61 ++- .../src/meeting-baas-api-client.ts | 142 ++++-- .../recordings.navigation-menu-item.ts | 2 +- .../src/objects/recording.ts | 42 +- .../src/receive-recording-webhook.test.ts | 17 + .../src/receive-recording-webhook.ts | 6 +- .../src/roles/default.role.ts | 11 +- .../src/shims/twenty-shared/types.ts | 34 ++ .../src/twenty-sync-service.test.ts | 89 ++++ .../src/twenty-sync-service.ts | 204 +++++--- .../meeting-baas-recorder/src/types.ts | 7 +- .../meeting-baas-recorder/src/utils.ts | 37 ++ .../src/views/all-recordings.view.ts | 16 +- .../src/views/by-platform.view.ts | 12 +- .../src/views/completed-recordings.view.ts | 18 +- .../src/webhook-handler.test.ts | 156 ++++++ .../src/webhook-handler.ts | 144 ++---- .../src/webhook-validator.test.ts | 111 ++++ .../src/webhook-validator.ts | 147 ++++-- 33 files changed, 1691 insertions(+), 578 deletions(-) create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/app-install.integration-test.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup-test.ts delete mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/application-config.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/constants/universal-identifiers.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/generate-summary.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/batch-schedule-bots.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.test.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/shims/twenty-shared/types.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.test.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.test.ts create mode 100644 packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.test.ts diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/app-install.integration-test.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/app-install.integration-test.ts new file mode 100644 index 00000000000..bd223f3da0c --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/app-install.integration-test.ts @@ -0,0 +1,76 @@ +import { APPLICATION_UNIVERSAL_IDENTIFIER } from '../constants/universal-identifiers'; +import { + ensureIntegrationTestEnvironment, + hasIntegrationTestEnvironment, +} from './setup-test'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe.skipIf(!hasIntegrationTestEnvironment())('App installation', () => { + beforeAll(async () => { + await ensureIntegrationTestEnvironment(); + + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error( + `Build failed: ${buildResult.error?.message ?? 'Unknown error'}`, + ); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error( + `Deploy failed: ${deployResult.error?.message ?? 'Unknown error'}`, + ); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error( + `Install failed: ${installResult.error?.message ?? 'Unknown error'}`, + ); + } + }); + + afterAll(async () => { + const uninstallResult = await appUninstall({ appPath: APP_PATH }); + + if (!uninstallResult.success) { + console.warn( + `App uninstall failed: ${uninstallResult.error?.message ?? 'Unknown error'}`, + ); + } + }); + + it('should find the installed app in the applications list', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (application: { universalIdentifier: string }) => + application.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup-test.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup-test.ts new file mode 100644 index 00000000000..14667b25f31 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup-test.ts @@ -0,0 +1,55 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +const CONFIG_DIR = path.join(os.homedir(), '.twenty'); +const CONFIG_PATH = path.join(CONFIG_DIR, 'config.test.json'); + +export const hasIntegrationTestEnvironment = (): boolean => + Boolean(process.env.TWENTY_API_URL && process.env.TWENTY_API_KEY); + +export const ensureIntegrationTestEnvironment = async () => { + const apiUrl = process.env.TWENTY_API_URL!; + const token = process.env.TWENTY_API_KEY!; + + if (!apiUrl || !token) { + throw new Error( + 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' + + 'Start a local server: yarn twenty server start\n' + + 'Or set them in vitest env config.', + ); + } + + let response: Response; + + try { + response = await fetch(`${apiUrl}/healthz`); + } catch { + throw new Error( + `Twenty server is not reachable at ${apiUrl}. ` + + 'Make sure the server is running before executing integration tests.', + ); + } + + if (!response.ok) { + throw new Error(`Server at ${apiUrl} returned ${response.status}`); + } + + fs.mkdirSync(CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + CONFIG_PATH, + JSON.stringify( + { + remotes: { + local: { apiUrl, apiKey: token }, + }, + defaultRemote: 'local', + }, + null, + 2, + ), + ); + + process.env.TWENTY_APP_ACCESS_TOKEN ??= token; +}; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup.ts deleted file mode 100644 index c35674aea55..00000000000 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Global test setup for Meeting BaaS Recorder - -global.fetch = jest.fn(); - -process.env.MEETING_BAAS_API_KEY = 'test-meeting-baas-api-key'; -process.env.TWENTY_API_KEY = 'test-twenty-api-key'; -process.env.SERVER_URL = 'http://localhost:3000'; -process.env.LOG_LEVEL = 'silent'; -process.env.NODE_ENV = 'test'; - -beforeEach(() => { - jest.clearAllMocks(); -}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/application-config.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/application-config.ts new file mode 100644 index 00000000000..93cc48fef2d --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/application-config.ts @@ -0,0 +1,30 @@ +import { defineApplication } from 'twenty-sdk'; + +import { + APP_DESCRIPTION, + APP_DISPLAY_NAME, + APPLICATION_UNIVERSAL_IDENTIFIER, + DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +} from './constants/universal-identifiers'; + +export default defineApplication({ + universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + displayName: APP_DISPLAY_NAME, + description: APP_DESCRIPTION, + icon: 'IconVideo', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + settingsCustomTabFrontComponentUniversalIdentifier: '4ea804f4-6c22-457b-b8a2-66673bb6fc76', + applicationVariables: { + MEETING_BAAS_API_KEY: { + universalIdentifier: 'c1d2e3f4-5a6b-7c8d-9e0f-a1b2c3d4e5f6', + description: 'Meeting BaaS API key for authenticating requests and verifying webhooks', + isSecret: true, + value: '', + }, + AUTO_CREATE_CONTACTS: { + universalIdentifier: '9637bafd-5888-4f34-bf8f-a4c82dbc4942', + description: 'Whether to auto-create contacts for unknown participants (true/false)', + value: 'true', + }, + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/constants/universal-identifiers.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/constants/universal-identifiers.ts new file mode 100644 index 00000000000..e9b8c3186fe --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/constants/universal-identifiers.ts @@ -0,0 +1,4 @@ +export const APP_DISPLAY_NAME = 'Meeting BaaS Recorder'; +export const APP_DESCRIPTION = 'Record meetings via Meeting BaaS and sync recordings, transcripts, and participants into Twenty.'; +export const APPLICATION_UNIVERSAL_IDENTIFIER = 'b7e3a1f2-8d4c-4e6a-9f2b-1c5d7e8a3b4f'; +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = '3231cf40-5b90-4c2b-ae41-fcb5606299b4'; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts index 86c9ed9d098..571f173a8fc 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/calendar-event-on-recording.field.ts @@ -7,8 +7,8 @@ import { } from 'twenty-sdk'; import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; -export const CALENDAR_EVENT_ON_RECORDING_ID = 'f41b7906-5667-5241-a269-b8140dcfdb53'; -export const RECORDINGS_ON_CALENDAR_EVENT_ID = 'b78cc9fb-d758-5308-a37b-4b4a60a6870b'; +export const CALENDAR_EVENT_ON_RECORDING_ID = '29fe48d1-7e7d-4253-9fea-0a876c2c116d'; +export const RECORDINGS_ON_CALENDAR_EVENT_ID = '131a78b1-f3c9-4b2e-9808-f9eb64bfb832'; export default defineField({ universalIdentifier: CALENDAR_EVENT_ON_RECORDING_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts index 3266edf4875..38ebe50d25b 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/recording-preference-on-workspace-member.field.ts @@ -4,7 +4,7 @@ import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, } from 'twenty-sdk'; -export const RECORDING_PREFERENCE_FIELD_ID = '082e8dd7-a365-5b6b-8d9b-1780f36154b3'; +export const RECORDING_PREFERENCE_FIELD_ID = '95ec702a-e4b4-4028-b563-900216d92b01'; export default defineField({ universalIdentifier: RECORDING_PREFERENCE_FIELD_ID, @@ -18,21 +18,21 @@ export default defineField({ defaultValue: "'RECORD_NONE'", options: [ { - id: 'dd08b14a-c5f2-5eb6-9b4b-e579437a4f6f', + id: 'e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b', color: 'green', label: 'Record All', value: 'RECORD_ALL', position: 1, }, { - id: 'e880d3db-9b3b-58c2-af65-a8e0be61083d', + id: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c', color: 'yellow', label: 'Organizer Only', value: 'RECORD_ORGANIZED', position: 2, }, { - id: 'e9c62cd0-26ab-5241-811d-545a9d0180a6', + id: '243e25fd-4bae-4b50-8844-971f89d6e679', color: 'gray', label: 'None', value: 'RECORD_NONE', diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts index 34655e465f3..8522ab7e6f3 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/fields/workspace-member-on-recording.field.ts @@ -7,8 +7,8 @@ import { } from 'twenty-sdk'; import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; -export const WORKSPACE_MEMBER_ON_RECORDING_ID = '8e985272-eae7-51ac-939b-5277b1107bc8'; -export const RECORDINGS_ON_WORKSPACE_MEMBER_ID = '8ab15013-866c-51d5-95bb-21108a07442f'; +export const WORKSPACE_MEMBER_ON_RECORDING_ID = '3bb83966-51d6-41e1-9a33-5b53bc6313d9'; +export const RECORDINGS_ON_WORKSPACE_MEMBER_ID = '0fb0eaac-4e8d-4632-a6ad-f76df4fb93ad'; export default defineField({ universalIdentifier: WORKSPACE_MEMBER_ON_RECORDING_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx b/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx index ade9758d144..e8c802cd2b8 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/front-components/meeting-baas-settings.front-component.tsx @@ -7,14 +7,15 @@ type RecordingPreference = 'RECORD_ALL' | 'RECORD_ORGANIZED' | 'RECORD_NONE'; type WorkspaceMember = { id: string; recordingPreference?: RecordingPreference; - botName?: string; - botEntryMessage?: string; }; type CalendarChannel = { id: string; }; +const getApiUrl = () => process.env.TWENTY_API_URL ?? ''; +const getToken = () => process.env.TWENTY_APP_ACCESS_TOKEN ?? ''; + const StyledContainer = styled.div` display: flex; flex-direction: column; @@ -22,32 +23,26 @@ const StyledContainer = styled.div` width: 100%; `; -const StyledSection = styled.div` - display: flex; - flex-direction: column; - gap: 12px; -`; - -const StyledHeading = styled.h2` - color: #1f2937; - font-family: Inter, sans-serif; - font-size: 20px; +const StyledSectionTitle = styled.h3` + color: #333; + font-family: 'Inter', sans-serif; + font-size: 15px; font-weight: 600; - margin: 0; + margin: 0 0 4px 0; `; -const StyledSectionDescription = styled.p` - color: #6b7280; - font-family: Inter, sans-serif; - font-size: 14px; - line-height: 1.5; - margin: 0; +const StyledSectionSubtitle = styled.p` + color: #818181; + font-family: 'Inter', sans-serif; + font-size: 13px; + font-weight: 400; + margin: 0 0 12px 0; `; const StyledCard = styled.div` align-items: center; - background: #ffffff; - border: 1px solid #e5e7eb; + background: #fff; + border: 1px solid #ebebeb; border-radius: 8px; display: flex; gap: 12px; @@ -56,9 +51,9 @@ const StyledCard = styled.div` const StyledIconContainer = styled.div` align-items: center; - background: #f3f4f6; + background: #f5f5f5; border-radius: 8px; - color: #4b5563; + color: #666; display: flex; flex-shrink: 0; height: 40px; @@ -75,30 +70,30 @@ const StyledTextContainer = styled.div` `; const StyledTitle = styled.span` - color: #1f2937; - font-family: Inter, sans-serif; + color: #333; + font-family: 'Inter', sans-serif; font-size: 14px; - font-weight: 600; + font-weight: 500; `; const StyledDescription = styled.span` - color: #6b7280; - font-family: Inter, sans-serif; + color: #818181; + font-family: 'Inter', sans-serif; font-size: 13px; `; -const StyledStatus = styled.span<{ tone: 'green' | 'red' }>` +const StyledStatusBadge = styled.span<{ connected: boolean }>` align-items: center; - background: ${({ tone }) => (tone === 'green' ? '#dcfce7' : '#fee2e2')}; - border-radius: 999px; - color: ${({ tone }) => (tone === 'green' ? '#166534' : '#991b1b')}; + background: ${({ connected }) => (connected ? '#10b981' : '#ef4444')}; + border-radius: 4px; + color: #fff; display: inline-flex; flex-shrink: 0; - font-family: Inter, sans-serif; - font-size: 12px; - font-weight: 600; - height: 28px; - padding: 0 10px; + font-family: 'Inter', sans-serif; + font-size: 13px; + font-weight: 500; + height: 32px; + padding: 0 12px; white-space: nowrap; `; @@ -110,11 +105,8 @@ const StyledRadioGroup = styled.div` const StyledRadioLabel = styled.label<{ selected: boolean }>` align-items: center; - background: ${({ selected }) => - selected ? '#eff6ff' : '#ffffff'}; - border: 1px solid - ${({ selected }) => - selected ? '#2563eb' : '#e5e7eb'}; + background: ${({ selected }) => (selected ? '#f0f0ff' : '#fff')}; + border: 1px solid ${({ selected }) => (selected ? '#5e5adb' : '#ebebeb')}; border-radius: 8px; cursor: pointer; display: flex; @@ -123,12 +115,12 @@ const StyledRadioLabel = styled.label<{ selected: boolean }>` transition: all 0.15s ease; &:hover { - border-color: #2563eb; + border-color: #5e5adb; } `; const StyledRadioInput = styled.input` - accent-color: #2563eb; + accent-color: #5e5adb; cursor: pointer; height: 16px; margin: 0; @@ -142,83 +134,78 @@ const StyledRadioTextContainer = styled.div` `; const StyledRadioTitle = styled.span` - color: #1f2937; - font-family: Inter, sans-serif; + color: #333; + font-family: 'Inter', sans-serif; font-size: 14px; - font-weight: 600; + font-weight: 500; `; const StyledRadioDescription = styled.span` - color: #6b7280; - font-family: Inter, sans-serif; + color: #818181; + font-family: 'Inter', sans-serif; font-size: 12px; `; -const StyledFieldGroup = styled.div` +const StyledBanner = styled.div<{ variant: 'info' | 'warning' }>` + align-items: center; + background: ${({ variant }) => (variant === 'warning' ? '#fef3c7' : '#eff6ff')}; + border: 1px solid ${({ variant }) => (variant === 'warning' ? '#f59e0b' : '#3b82f6')}; + border-radius: 8px; + color: ${({ variant }) => (variant === 'warning' ? '#92400e' : '#1e40af')}; display: flex; - flex-direction: column; - gap: 12px; -`; - -const StyledFieldLabel = styled.label` - color: #1f2937; - font-family: Inter, sans-serif; + font-family: 'Inter', sans-serif; font-size: 13px; - font-weight: 600; + gap: 8px; + padding: 12px 16px; `; -const StyledTextInput = styled.input` - background: #ffffff; - border: 1px solid #d1d5db; +const StyledButton = styled.button<{ variant?: 'primary' | 'secondary' }>` + align-items: center; + background: ${({ variant }) => (variant === 'secondary' ? '#fff' : '#5e5adb')}; + border: 1px solid ${({ variant }) => (variant === 'secondary' ? '#ebebeb' : '#5e5adb')}; border-radius: 8px; - color: #111827; - font-family: Inter, sans-serif; + color: ${({ variant }) => (variant === 'secondary' ? '#333' : '#fff')}; + cursor: pointer; + display: inline-flex; + font-family: 'Inter', sans-serif; font-size: 14px; - margin-top: 4px; - outline: none; - padding: 10px 12px; - width: 100%; + font-weight: 500; + gap: 8px; + height: 40px; + justify-content: center; + padding: 0 20px; + transition: all 0.15s ease; + white-space: nowrap; - &:focus { - border-color: #2563eb; + &:hover:not(:disabled) { + opacity: 0.9; } - &::placeholder { - color: #9ca3af; + &:disabled { + cursor: not-allowed; + opacity: 0.5; } `; -const StyledCharCount = styled.span<{ over: boolean }>` - color: ${({ over }) => (over ? '#dc2626' : '#6b7280')}; - font-family: Inter, sans-serif; - font-size: 11px; - margin-top: 2px; - text-align: right; -`; - -const StyledCallout = styled.div<{ variant: 'warning' | 'info' }>` - background: ${({ variant }) => (variant === 'warning' ? '#fffbeb' : '#eff6ff')}; - border: 1px solid ${({ variant }) => (variant === 'warning' ? '#fcd34d' : '#93c5fd')}; +const StyledResultBanner = styled.div<{ variant: 'success' | 'error' }>` + align-items: center; + background: ${({ variant }) => (variant === 'success' ? '#ecfdf5' : '#fef2f2')}; + border: 1px solid ${({ variant }) => (variant === 'success' ? '#10b981' : '#ef4444')}; border-radius: 8px; - color: #1f2937; + color: ${({ variant }) => (variant === 'success' ? '#065f46' : '#991b1b')}; display: flex; - flex-direction: column; - gap: 4px; - padding: 12px 14px; -`; - -const StyledCalloutTitle = styled.span` - font-family: Inter, sans-serif; + font-family: 'Inter', sans-serif; font-size: 13px; - font-weight: 600; + gap: 8px; + padding: 12px 16px; `; -const StyledCalloutDescription = styled.span` - color: #4b5563; - font-family: Inter, sans-serif; - font-size: 13px; - line-height: 1.5; -`; +type BatchScheduleResult = { + scheduled: number; + skipped: number; + errors: string[]; + hasMore: boolean; +}; const PREFERENCE_OPTIONS: Array<{ value: RecordingPreference; @@ -243,60 +230,117 @@ const PREFERENCE_OPTIONS: Array<{ ]; const fetchCurrentWorkspaceMember = async (): Promise => { - const response = await fetch(`${process.env.TWENTY_API_URL}/rest/currentWorkspaceMember`, { - headers: { Authorization: `Bearer ${process.env.TWENTY_API_KEY}` }, + // Use metadata GraphQL to get the current user's workspace member + const response = await fetch(`${getApiUrl()}/metadata`, { + method: 'POST', + headers: { + Authorization: `Bearer ${getToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `{ currentUser { id workspaceMember { id } } }`, + }), }); const data = await response.json(); - return data?.data ?? data; + const memberId = data?.data?.currentUser?.workspaceMember?.id; + if (!memberId) throw new Error('Could not find workspace member'); + + // Fetch the full workspace member with recordingPreference via REST + const memberResponse = await fetch( + `${getApiUrl()}/rest/workspaceMembers/${memberId}`, + { headers: { Authorization: `Bearer ${getToken()}` } }, + ); + const memberData = await memberResponse.json(); + const member = memberData?.data?.workspaceMember ?? memberData?.data; + return { id: memberId, recordingPreference: member?.recordingPreference }; }; const updateWorkspaceMember = async ( memberId: string, - fields: { - recordingPreference?: RecordingPreference; - botName?: string | null; - botEntryMessage?: string | null; - }, + recordingPreference: RecordingPreference, ): Promise => { - const response = await fetch(`${process.env.TWENTY_API_URL}/rest/workspaceMembers/${memberId}`, { + const response = await fetch(`${getApiUrl()}/rest/workspaceMembers/${memberId}`, { method: 'PATCH', headers: { - Authorization: `Bearer ${process.env.TWENTY_API_KEY}`, + Authorization: `Bearer ${getToken()}`, 'Content-Type': 'application/json', }, - body: JSON.stringify(fields), + body: JSON.stringify({ recordingPreference }), }); if (!response.ok) { - throw new Error(`PATCH failed: ${response.status}`); + throw new Error(`Failed to update preference: ${response.status}`); } }; -const fetchCalendarChannels = async (): Promise => { - const response = await fetch(`${process.env.TWENTY_API_URL}/rest/calendarChannels?limit=1`, { - headers: { Authorization: `Bearer ${process.env.TWENTY_API_KEY}` }, +const fetchUserCalendarChannels = async (_memberId: string): Promise => { + // Use the metadata GraphQL endpoint with the app access token (user-scoped) + const response = await fetch(`${getApiUrl()}/metadata`, { + method: 'POST', + headers: { + Authorization: `Bearer ${getToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `{ myCalendarChannels { id } }`, + }), }); const data = await response.json(); - return data?.data?.calendarChannels ?? []; + return data?.data?.myCalendarChannels ?? []; +}; + +const SECRET_VARIABLE_MASK = '********'; + +const checkApiKeyConfigured = async (): Promise => { + try { + const response = await fetch(`${getApiUrl()}/metadata`, { + method: 'POST', + headers: { + Authorization: `Bearer ${getToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `{ findManyApplications { name applicationVariables { key value isSecret } } }`, + }), + }); + const data = await response.json(); + const apps = data?.data?.findManyApplications ?? []; + for (const app of apps) { + const vars = app.applicationVariables ?? []; + const apiKeyVar = vars.find( + (v: { key: string }) => v.key === 'MEETING_BAAS_API_KEY', + ); + if (apiKeyVar) { + // An empty secret returns exactly '********', a real value returns + // a prefix + mask (e.g. '5z********'). So !== mask means configured. + return apiKeyVar.value !== SECRET_VARIABLE_MASK; + } + } + return false; + } catch { + return false; + } }; const MeetingBaasSettings = () => { const [member, setMember] = useState(null); const [preference, setPreference] = useState('RECORD_NONE'); - const [botName, setBotName] = useState(''); - const [botEntryMessage, setBotEntryMessage] = useState(''); const [hasCalendar, setHasCalendar] = useState(null); + const [apiKeyConfigured, setApiKeyConfigured] = useState(false); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); - - const apiKeyConfigured = Boolean(process.env.MEETING_BAAS_API_KEY); + const [isBatchScheduling, setIsBatchScheduling] = useState(false); + const [batchResult, setBatchResult] = useState(null); useEffect(() => { - Promise.all([fetchCurrentWorkspaceMember(), fetchCalendarChannels()]) - .then(([memberData, channels]) => { + Promise.all([ + fetchCurrentWorkspaceMember(), + checkApiKeyConfigured(), + ]) + .then(async ([memberData, hasApiKey]) => { setMember(memberData); setPreference(memberData.recordingPreference ?? 'RECORD_NONE'); - setBotName(memberData.botName ?? ''); - setBotEntryMessage(memberData.botEntryMessage ?? ''); + setApiKeyConfigured(hasApiKey); + const channels = await fetchUserCalendarChannels(memberData.id); setHasCalendar(channels.length > 0); }) .catch(() => { @@ -307,56 +351,51 @@ const MeetingBaasSettings = () => { const handlePreferenceChange = async (newPreference: RecordingPreference) => { if (!member || isSaving) return; - const previousPreference = preference; setIsSaving(true); setPreference(newPreference); try { - await updateWorkspaceMember(member.id, { recordingPreference: newPreference }); - setMember({ ...member, recordingPreference: newPreference }); + await updateWorkspaceMember(member.id, newPreference); } catch { - setPreference(previousPreference); + setPreference(member.recordingPreference ?? 'RECORD_NONE'); } finally { setIsSaving(false); } }; - const handleBotNameBlur = async () => { - if (!member || isSaving) return; - const trimmed = botName.trim(); - if (trimmed === (member.botName ?? '')) return; - setIsSaving(true); + const handleBatchSchedule = async () => { + if (isBatchScheduling) return; + setIsBatchScheduling(true); + setBatchResult(null); try { - await updateWorkspaceMember(member.id, { botName: trimmed || null }); - setMember({ ...member, botName: trimmed || undefined }); + const response = await fetch(`${getApiUrl()}/s/batch-schedule-bots`, { + method: 'POST', + headers: { + Authorization: `Bearer ${getToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }); + const data = await response.json(); + setBatchResult({ + scheduled: data.scheduled ?? 0, + skipped: data.skipped ?? 0, + errors: data.errors ?? [], + hasMore: data.hasMore ?? false, + }); } catch { - setBotName(member.botName ?? ''); + setBatchResult({ scheduled: 0, skipped: 0, errors: ['Request failed'], hasMore: false }); } finally { - setIsSaving(false); + setIsBatchScheduling(false); } }; - const handleEntryMessageBlur = async () => { - if (!member || isSaving) return; - const trimmed = botEntryMessage.trim().slice(0, 500); - if (trimmed === (member.botEntryMessage ?? '')) return; - setIsSaving(true); - try { - await updateWorkspaceMember(member.id, { botEntryMessage: trimmed || null }); - setMember({ ...member, botEntryMessage: trimmed || undefined }); - } catch { - setBotEntryMessage(member.botEntryMessage ?? ''); - } finally { - setIsSaving(false); - } - }; + const showBatchButton = preference !== 'RECORD_NONE' && apiKeyConfigured && hasCalendar; if (isLoading) { return ( - - Meeting Recording - Loading settings... - + Meeting Recording + Loading settings... ); } @@ -364,51 +403,43 @@ const MeetingBaasSettings = () => { return ( {/* API Key Status */} - -
- Meeting BaaS Connection - - Meeting BaaS records your meetings and syncs transcripts into Twenty - -
+
+ Meeting BaaS Connection + + Meeting BaaS records your meetings and syncs transcripts into Twenty + - 🎥 + V API Key {apiKeyConfigured ? 'Your Meeting BaaS API key is configured' - : 'No API key configured — add MEETING_BAAS_API_KEY in app variables'} + : 'No API key configured — set MEETING_BAAS_API_KEY in the Variables tab'} - + {apiKeyConfigured ? 'Connected' : 'Not Set'} - + - +
{/* Calendar Connection Banner */} {hasCalendar === false && ( - - No calendar connected - - Connect your Google or Microsoft calendar in Settings {'->'} Accounts to enable - automatic meeting recording. - - + + No calendar connected. Connect your Google or Microsoft calendar in Settings > Accounts + to enable automatic meeting recording. + )} {/* Recording Preference */} - -
- Recording Preference - - Choose which meetings are automatically recorded when they have a conference - link - -
+
+ Recording Preference + + Choose which meetings are automatically recorded when they have a conference link + {PREFERENCE_OPTIONS.map((option) => ( @@ -427,62 +458,53 @@ const MeetingBaasSettings = () => { ))} - +
- {/* Bot Customization */} - + {preference !== 'RECORD_NONE' && !apiKeyConfigured && ( + + Recording is enabled but no API key is set. Set MEETING_BAAS_API_KEY in the Variables tab + to start recording. + + )} + + {/* Batch Schedule Existing Meetings */} + {showBatchButton && (
- Bot Customization - - Customize how the recording bot appears when it joins your meetings - + Existing Meetings + + Schedule bots for future calendar events that were synced before recording was enabled + + + {isBatchScheduling ? 'Scheduling...' : 'Schedule existing meetings'} + + + {batchResult && (batchResult.errors?.length ?? 0) === 0 && ( + + {batchResult.scheduled > 0 + ? `Scheduled bots for ${batchResult.scheduled} meeting${batchResult.scheduled !== 1 ? 's' : ''} (${batchResult.skipped} skipped)` + : 'No new meetings to schedule'} + {batchResult.hasMore && ' — click again to process more'} + + )} + + {batchResult && (batchResult.errors?.length ?? 0) > 0 && ( + + {batchResult.scheduled > 0 + ? `Scheduled ${batchResult.scheduled}, but ${batchResult.errors.length} error${batchResult.errors.length !== 1 ? 's' : ''} occurred` + : `Failed: ${batchResult.errors[0]}`} + + )}
- -
- Bot name - ) => setBotName(e.target.value)} - onBlur={handleBotNameBlur} - disabled={isSaving} - /> -
-
- Entry message - ) => - setBotEntryMessage(e.target.value.slice(0, 500)) - } - onBlur={handleEntryMessageBlur} - disabled={isSaving} - maxLength={500} - /> - = 500}> - {botEntryMessage.length}/500 - -
-
-
- - {preference !== 'RECORD_NONE' && !apiKeyConfigured && ( - - API key required - - Recording is enabled but no API key is set. Add `MEETING_BAAS_API_KEY` in - the app variables to start recording. - - )}
); }; -export const SETTINGS_FRONT_COMPONENT_ID = '7f2c17b4-2cd2-5447-b7d1-83ef12040837'; +export const SETTINGS_FRONT_COMPONENT_ID = '4ea804f4-6c22-457b-b8a2-66673bb6fc76'; export default defineFrontComponent({ universalIdentifier: SETTINGS_FRONT_COMPONENT_ID, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/generate-summary.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/generate-summary.ts new file mode 100644 index 00000000000..830fc4fb77a --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/generate-summary.ts @@ -0,0 +1,45 @@ +import axios from 'axios'; +import { getApiUrl } from './utils'; + +const SYSTEM_PROMPT = [ + 'You are summarizing a meeting transcript.', + 'Produce a concise summary covering:', + '- Key topics discussed', + '- Decisions made', + '- Action items with owners (if mentioned)', + 'Keep it brief — no more than a few paragraphs.', +].join('\n'); + +export const generateSummary = async ( + transcript: string, +): Promise => { + const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? ''; + if (!token) return null; + + if (!transcript || transcript.trim().length === 0) return null; + + try { + const response = await axios({ + method: 'POST', + url: `${getApiUrl()}/rest/ai/generate-text`, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + data: { + userPrompt: transcript, + systemPrompt: SYSTEM_PROMPT, + }, + }); + + const text = response.data?.text; + if (typeof text === 'string' && text.trim().length > 0) { + return text.trim(); + } + + return null; + } catch { + // Non-fatal — recording still saves without a summary + return null; + } +}; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts index 01e10094a00..e8e7a6965d2 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/index.ts @@ -1,7 +1,7 @@ // SDK entity definitions (discovered automatically by twenty app build) export { default as RecordingObject, RECORDING_UNIVERSAL_IDENTIFIER } from './objects/recording'; -// Fields on standard object +// Fields on standard objects export { default as RecordingPreferenceField } from './fields/recording-preference-on-workspace-member.field'; export { default as BotNameField } from './fields/bot-name-on-workspace-member.field'; export { default as BotEntryMessageField } from './fields/bot-entry-message-on-workspace-member.field'; @@ -13,6 +13,9 @@ export { default as MeetingBaasSettingsComponent } from './front-components/meet export { default as OnCalendarEventCreated } from './logic-functions/on-calendar-event-created'; export { default as OnCalendarEventUpdated } from './logic-functions/on-calendar-event-updated'; +// Logic functions (HTTP-triggered) +export { default as BatchScheduleBots } from './logic-functions/batch-schedule-bots'; + // Types export { WebhookEvent } from './types'; export type { @@ -34,9 +37,13 @@ export type { export { MeetingBaasApiClient } from './meeting-baas-api-client'; export { WebhookHandler } from './webhook-handler'; +// AI summary +export { generateSummary } from './generate-summary'; + // Sync service export { checkIfRecordingExists, + checkIfRecordingExistsForEvent, detectPlatform, resolveCalendarEventOwner, syncBotRecording, @@ -47,7 +54,9 @@ export { export { createLogger } from './logger'; export { getApiUrl, getRestApiUrl, restHeaders } from './utils'; export { + WebhookPayloadSchema, getApiKeyFingerprint, - isValidMeetingBaasPayload, + parseWebhookPayload, verifyWebhookApiKey, } from './webhook-validator'; +export type { ParsedWebhookPayload } from './webhook-validator'; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/batch-schedule-bots.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/batch-schedule-bots.ts new file mode 100644 index 00000000000..b8bb9c6d44a --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/batch-schedule-bots.ts @@ -0,0 +1,271 @@ +import axios from 'axios'; +import { defineLogicFunction } from 'twenty-sdk'; +import { MeetingBaasApiClient } from '../meeting-baas-api-client'; +import { + resolveCalendarEventOwner, + checkIfRecordingExistsForEvent, + upsertRecording, + detectPlatform, +} from '../twenty-sync-service'; +import { createLogger } from '../logger'; +import { buildRestUrl, restHeaders } from '../utils'; + +const logger = createLogger('batch-schedule-bots'); + +type RecordingPreference = 'RECORD_ALL' | 'RECORD_ORGANIZED' | 'RECORD_NONE'; + +// Max events to inspect per invocation (logic function has a 60s timeout) +const MAX_EVENTS_PER_RUN = 200; +// SDK batch endpoint supports up to 100 items +const BATCH_SIZE = 100; + +type ConferenceLink = { + primaryLinkUrl?: string; + primaryLinkLabel?: string; + secondaryLinks?: unknown[]; +}; + +type CalendarEvent = { + id: string; + conferenceUrl?: string; + startsAt?: string; + title?: string; +}; + +type BatchResult = { + scheduled: number; + skipped: number; + errors: string[]; + hasMore: boolean; +}; + +const fetchWorkspaceMemberPreference = async ( + workspaceMemberId: string, +): Promise => { + try { + const response = await axios.get( + buildRestUrl(`workspaceMembers/${workspaceMemberId}`), + { headers: restHeaders() }, + ); + const body = response.data?.data ?? response.data; + const memberData = body?.workspaceMember ?? body; + return (memberData?.recordingPreference as RecordingPreference) ?? 'RECORD_NONE'; + } catch { + return 'RECORD_NONE'; + } +}; + +const isOrganizer = async ( + calendarEventId: string, + workspaceMemberId: string, +): Promise => { + try { + const url = buildRestUrl('calendarEventParticipants', { + filter: { calendarEventId: { eq: calendarEventId } }, + limit: 50, + }); + const response = await axios.get(url, { headers: restHeaders() }); + const participants: Record[] = + response.data?.data?.calendarEventParticipants ?? []; + return participants.some( + (p) => p.isOrganizer === true && p.workspaceMemberId === workspaceMemberId, + ); + } catch { + return false; + } +}; + +// Paginate through future calendar events with conference links +const fetchFutureCalendarEvents = async ( + maxEvents: number, +): Promise<{ events: CalendarEvent[]; hasMore: boolean }> => { + const now = new Date().toISOString(); + const events: CalendarEvent[] = []; + let cursor: string | undefined; + + while (events.length < maxEvents) { + const pageSize = Math.min(20, maxEvents - events.length); + const url = buildRestUrl('calendarEvents', { + filter: { startsAt: { gte: now } }, + limit: pageSize, + cursor, + }); + + let page: Record[]; + try { + const response = await axios.get(url, { headers: restHeaders() }); + page = response.data?.data?.calendarEvents ?? []; + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + logger.error(`REST API error ${error.response.status}: ${JSON.stringify(error.response.data)}`); + } + throw error; + } + + if (page.length === 0) break; + + for (const event of page) { + const conferenceLink = event.conferenceLink as ConferenceLink | undefined; + const conferenceUrl = conferenceLink?.primaryLinkUrl; + if (conferenceUrl) { + events.push({ + id: event.id as string, + conferenceUrl, + startsAt: event.startsAt as string | undefined, + title: event.title as string | undefined, + }); + } + } + + // If we got fewer results than the page size, there are no more pages + if (page.length < pageSize) break; + + cursor = page[page.length - 1].id as string; + } + + return { events, hasMore: events.length >= maxEvents }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a7d3e1f2-8b4c-4d5e-9f6a-1c2d3e4f5a6b', + name: 'batch-schedule-bots', + description: + 'Schedules bots for all existing future calendar events. Call after enabling recording to catch events that were already synced.', + timeoutSeconds: 60, + httpRouteTriggerSettings: { + path: '/batch-schedule-bots', + httpMethod: 'POST', + isAuthRequired: true, + forwardedRequestHeaders: [], + }, + handler: async (): Promise => { + const result: BatchResult = { scheduled: 0, skipped: 0, errors: [], hasMore: false }; + + const apiKey = process.env.MEETING_BAAS_API_KEY; + if (!apiKey) { + result.errors.push('MEETING_BAAS_API_KEY not configured'); + return result; + } + + const serverUrl = process.env.TWENTY_API_URL ?? ''; + const callbackUrl = serverUrl ? `${serverUrl}/s/webhook/meeting-baas` : undefined; + + // 1. Fetch future calendar events with conference links + logger.debug('fetching future calendar events'); + const { events, hasMore } = await fetchFutureCalendarEvents(MAX_EVENTS_PER_RUN); + result.hasMore = hasMore; + logger.debug(`found ${events.length} future events with conference links`); + + if (events.length === 0) return result; + + // 2. Filter: dedup, resolve ownership, check preferences + type QualifiedEvent = CalendarEvent & { + workspaceMemberId: string; + }; + const qualified: QualifiedEvent[] = []; + + // Cache preferences per workspace member to avoid redundant API calls + const preferenceCache = new Map(); + + for (const event of events) { + // Dedup: skip if recording already exists + const exists = await checkIfRecordingExistsForEvent(event.id); + if (exists) { + result.skipped++; + continue; + } + + // Resolve ownership + const ownership = await resolveCalendarEventOwner(event.id); + if (!ownership.workspaceMemberId) { + result.skipped++; + continue; + } + + // Check preference (cached per workspace member) + let preference = preferenceCache.get(ownership.workspaceMemberId); + if (preference === undefined) { + preference = await fetchWorkspaceMemberPreference(ownership.workspaceMemberId); + preferenceCache.set(ownership.workspaceMemberId, preference); + } + + if (preference === 'RECORD_NONE') { + result.skipped++; + continue; + } + + if (preference === 'RECORD_ORGANIZED') { + const memberIsOrganizer = await isOrganizer(event.id, ownership.workspaceMemberId); + if (!memberIsOrganizer) { + result.skipped++; + continue; + } + } + + qualified.push({ ...event, workspaceMemberId: ownership.workspaceMemberId }); + } + + logger.debug(`${qualified.length} events qualified, ${result.skipped} skipped`); + + if (qualified.length === 0) return result; + + // 3. Batch create scheduled bots + const client = new MeetingBaasApiClient(apiKey); + + for (let i = 0; i < qualified.length; i += BATCH_SIZE) { + const batch = qualified.slice(i, i + BATCH_SIZE); + + const items = batch.map((event) => ({ + meetingUrl: event.conferenceUrl!, + joinAt: event.startsAt!, + extra: { + calendarEventId: event.id, + workspaceMemberId: event.workspaceMemberId, + meeting_url: event.conferenceUrl, + }, + callbackUrl, + callbackSecret: apiKey, + })); + + try { + const { botIds, errors } = await client.batchCreateScheduledBots(items); + + // Create placeholder recordings for successfully scheduled bots + for (let j = 0; j < botIds.length; j++) { + const event = batch[j]; + try { + await upsertRecording({ + botId: botIds[j], + name: event.title ? `Scheduled: ${event.title}` : `Scheduled: ${event.conferenceUrl}`, + date: event.startsAt!, + duration: 0, + platform: detectPlatform(event.conferenceUrl!), + status: 'IN_PROGRESS', + meetingUrl: { primaryLinkLabel: 'Join Meeting', primaryLinkUrl: event.conferenceUrl!, secondaryLinks: null }, + mp4Url: null, + transcript: '', + calendarEventId: event.id, + workspaceMemberId: event.workspaceMemberId, + }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + logger.warn(`Failed to create placeholder for bot ${botIds[j]}: ${msg}`); + } + result.scheduled++; + } + + for (const err of errors) { + const event = batch[err.index]; + result.errors.push(`Event ${event?.id}: ${err.code} - ${err.message}`); + } + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + result.errors.push(`Batch API error: ${msg}`); + logger.error(`Batch API call failed: ${msg}`); + } + } + + logger.debug(`batch complete: ${result.scheduled} scheduled, ${result.errors.length} errors`); + return result; + }, +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts index e8e7fe93ad3..8c04b029260 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-created.ts @@ -48,7 +48,7 @@ const handler = async ( }; export default defineLogicFunction({ - universalIdentifier: 'bc2a28a0-1362-5389-9c2f-b5ad10f1605c', + universalIdentifier: 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', name: 'on-calendar-event-created', description: 'Schedules a Meeting BaaS recording bot when a calendar event with a conference link is created', timeoutSeconds: 15, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts index 65a35d4fd05..f9f8879a968 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/on-calendar-event-updated.ts @@ -5,7 +5,7 @@ import { type ObjectRecordUpdateEvent, } from 'twenty-sdk'; import { createLogger } from '../logger'; -import { getRestApiUrl } from '../utils'; +import { buildRestUrl } from '../utils'; import { scheduleBot } from './schedule-bot'; const logger = createLogger('on-calendar-event-updated'); @@ -26,10 +26,12 @@ const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? ''; // Check if a recording is already linked to this calendar event const hasExistingRecording = async (calendarEventId: string): Promise => { try { - const response = await axios({ - method: 'GET', + const url = buildRestUrl('recordings', { + filter: { calendarEventId: { eq: calendarEventId } }, + limit: 1, + }); + const response = await axios.get(url, { headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, - url: `${getRestApiUrl()}/recordings?filter=calendarEventId%5Beq%5D%3A%22${encodeURIComponent(calendarEventId)}%22&limit=1`, }); const recordings: Record[] = response.data?.data?.recordings ?? []; @@ -74,7 +76,7 @@ const handler = async ( }; export default defineLogicFunction({ - universalIdentifier: 'c4f20492-beec-535d-9ac4-28a3f25a9813', + universalIdentifier: 'b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e', name: 'on-calendar-event-updated', description: 'Schedules a Meeting BaaS recording bot when a calendar event gains a conference link', timeoutSeconds: 15, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts index 93c5f78a08d..2e2d8141c72 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts @@ -1,8 +1,9 @@ import axios from 'axios'; import { MeetingBaasApiClient } from '../meeting-baas-api-client'; -import { resolveCalendarEventOwner } from '../twenty-sync-service'; +import { resolveCalendarEventOwner, checkIfRecordingExistsForEvent, upsertRecording } from '../twenty-sync-service'; +import { detectPlatform } from '../twenty-sync-service'; import { createLogger } from '../logger'; -import { getRestApiUrl, restHeaders } from '../utils'; +import { buildRestUrl, restHeaders } from '../utils'; const logger = createLogger('schedule-bot'); @@ -14,8 +15,6 @@ type BotSettings = { botEntryMessage: string; }; -const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? ''; - const fetchWorkspaceMemberBotSettings = async ( workspaceMemberId: string, ): Promise => { @@ -23,9 +22,10 @@ const fetchWorkspaceMemberBotSettings = async ( const response = await axios({ method: 'GET', headers: restHeaders(), - url: `${getRestApiUrl()}/workspaceMembers/${workspaceMemberId}`, + url: buildRestUrl(`workspaceMembers/${workspaceMemberId}`), }); - const memberData = response.data?.data ?? response.data; + const body = response.data?.data ?? response.data; + const memberData = body?.workspaceMember ?? body; return { preference: (memberData?.recordingPreference as RecordingPreference) ?? 'RECORD_NONE', botName: (memberData?.botName as string) || 'Twenty CRM Recorder', @@ -45,25 +45,29 @@ const fetchCalendarEventTitle = async ( const response = await axios({ method: 'GET', headers: restHeaders(), - url: `${getRestApiUrl()}/calendarEvents/${calendarEventId}`, + url: buildRestUrl(`calendarEvents/${calendarEventId}`), }); - const eventData = response.data?.data ?? response.data; + const body = response.data?.data ?? response.data; + const eventData = body?.calendarEvent ?? body; return (eventData?.title as string) || ''; } catch { return ''; } }; +// The recording preference is fetched per-call rather than from the event payload +// because Twenty's database event payloads only contain the triggering object's fields +// (calendarEvent), not joined data from other objects (workspaceMember). const isOrganizer = async ( calendarEventId: string, workspaceMemberId: string, ): Promise => { try { - const response = await axios({ - method: 'GET', - headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, - url: `${getRestApiUrl()}/calendarEventParticipants?filter=calendarEventId%5Beq%5D%3A%22${encodeURIComponent(calendarEventId)}%22&limit=50`, + const url = buildRestUrl('calendarEventParticipants', { + filter: { calendarEventId: { eq: calendarEventId } }, + limit: 50, }); + const response = await axios.get(url, { headers: restHeaders() }); const participants: Record[] = response.data?.data?.calendarEventParticipants ?? []; @@ -79,7 +83,7 @@ const isOrganizer = async ( } }; -// Shared handler: resolve owner → check preference → check organizer → schedule bot +// Shared handler: resolve owner -> check preference -> check organizer -> schedule bot export const scheduleBot = async ( calendarEventId: string, conferenceUrl: string, @@ -91,6 +95,13 @@ export const scheduleBot = async ( return null; } + // Dedup: check if a recording already exists for this calendar event + const alreadyExists = await checkIfRecordingExistsForEvent(calendarEventId); + if (alreadyExists) { + logger.debug(`Recording already exists for calendar event ${calendarEventId}, skipping`); + return null; + } + // Resolve calendar event owner const ownership = await resolveCalendarEventOwner(calendarEventId); if (!ownership.workspaceMemberId) { @@ -119,6 +130,7 @@ export const scheduleBot = async ( // Schedule the bot const client = new MeetingBaasApiClient(apiKey); + const serverUrl = process.env.TWENTY_API_URL ?? ''; const botId = await client.createScheduledBot({ meetingUrl: conferenceUrl, joinAt: startsAt, @@ -130,8 +142,31 @@ export const scheduleBot = async ( meeting_url: conferenceUrl, meeting_title: meetingTitle, }, + callbackUrl: serverUrl ? `${serverUrl}/s/webhook/meeting-baas` : undefined, + callbackSecret: apiKey, }); logger.debug(`Scheduled bot ${botId} for calendar event ${calendarEventId} (${conferenceUrl})`); + + // Create placeholder recording so subsequent triggers detect the dedup + try { + await upsertRecording({ + botId, + name: meetingTitle ? `Scheduled: ${meetingTitle}` : `Scheduled: ${conferenceUrl}`, + date: startsAt, + duration: 0, + platform: detectPlatform(conferenceUrl), + status: 'IN_PROGRESS', + meetingUrl: { primaryLinkLabel: 'Join Meeting', primaryLinkUrl: conferenceUrl, secondaryLinks: null }, + mp4Url: null, + transcript: '', + calendarEventId, + workspaceMemberId: ownership.workspaceMemberId, + }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + logger.warn(`Failed to create placeholder recording: ${msg}`); + } + return botId; }; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts index 6b7d22a7a37..487a422ba8c 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/meeting-baas-api-client.ts @@ -1,4 +1,3 @@ -import axios from 'axios'; import { createBaasClient, type BaasClient, type V2 } from '@meeting-baas/sdk'; import { createLogger } from './logger'; import type { MeetingPlatform, RecordingData } from './types'; @@ -30,6 +29,7 @@ export class MeetingBaasApiClient { recordingMode?: 'speaker_view' | 'gallery_view' | 'audio_only'; extra?: Record; callbackUrl?: string; + callbackSecret?: string; }): Promise { logger.debug(`scheduling bot for meeting: ${options.meetingUrl} at ${options.joinAt}`); @@ -43,7 +43,10 @@ export class MeetingBaasApiClient { ...(options.extra && { extra: options.extra }), ...(options.callbackUrl && { callback_enabled: true, - callback_config: { url: options.callbackUrl }, + callback_config: { + url: options.callbackUrl, + ...(options.callbackSecret && { secret: options.callbackSecret }), + }, }), }); @@ -57,25 +60,72 @@ export class MeetingBaasApiClient { return botId; } + // Batch-create scheduled bots (up to 100 items per call) + async batchCreateScheduledBots( + items: Array<{ + meetingUrl: string; + joinAt: string; + botName?: string; + recordingMode?: 'speaker_view' | 'gallery_view' | 'audio_only'; + extra?: Record; + callbackUrl?: string; + callbackSecret?: string; + }>, + ): Promise<{ botIds: string[]; errors: Array<{ index: number; code: string; message: string }> }> { + logger.debug(`batch scheduling ${items.length} bots`); + + const params = items.map((item) => ({ + meeting_url: item.meetingUrl, + join_at: item.joinAt, + bot_name: item.botName || 'Twenty CRM Recorder', + ...(item.recordingMode && { recording_mode: item.recordingMode }), + ...(item.extra && { extra: item.extra }), + ...(item.callbackUrl && { + callback_enabled: true as const, + callback_config: { + url: item.callbackUrl, + ...(item.callbackSecret && { secret: item.callbackSecret }), + }, + }), + })); + + const result = await this.client.batchCreateScheduledBots(params); + + if (!result.success) { + const errorInfo = 'code' in result ? ` (${result.code})` : ''; + throw new Error(`Meeting BaaS batch API error${errorInfo}: ${result.error}`); + } + + const botIds = result.data.map((d) => d.bot_id); + const errors = result.errors.map((e) => ({ + index: e.index, + code: e.code, + message: e.message, + })); + + logger.debug(`batch result: ${botIds.length} created, ${errors.length} failed`); + return { botIds, errors }; + } + // Transform V2 bot.completed webhook data into normalized RecordingData - async transformWebhookData( + transformWebhookData( data: V2.BotWebhookCompletedData, extra?: Record | null, - ): Promise { + ): RecordingData { const duration = data.duration_seconds ?? 0; const extraData = extra ?? {}; const meetingUrl = (extraData.meeting_url as string) || ''; const title = (extraData.meeting_title as string) || `Recording ${new Date().toLocaleDateString()}`; const platform: MeetingPlatform = detectPlatform(meetingUrl); - const transcript = await this.fetchTranscript(data); - return { botId: data.bot_id, title, date: data.joined_at || new Date().toISOString(), duration, - transcript, + transcript: '', + transcriptionUrl: data.transcription || undefined, + diarizationUrl: data.diarization || undefined, mp4Url: data.video || '', meetingUrl, platform, @@ -83,60 +133,46 @@ export class MeetingBaasApiClient { }; } - // Fetch and format transcript from the signed URL provided in webhook data. - // V2 webhook provides `transcription` as a signed URL to a JSON file - // containing speaker-attributed segments. - private async fetchTranscript(data: V2.BotWebhookCompletedData): Promise { - const transcriptionUrl = data.transcription; - if (!transcriptionUrl) { - logger.debug('no transcription URL in webhook data'); - return ''; - } + // Fetch and format transcript from diarization or transcription URL + async fetchTranscript(recordingData: RecordingData): Promise { + // Prefer diarization (speaker-attributed) + const url = recordingData.diarizationUrl || recordingData.transcriptionUrl; + if (!url) return ''; try { - logger.debug('fetching transcription from signed URL'); - const response = await axios.get(transcriptionUrl, { timeout: 30000 }); - const transcriptionData = response.data; - - // The transcription file is a JSON array of speaker segments: - // [{ speaker: "Name", words: [{ start, end, word }] }, ...] - if (Array.isArray(transcriptionData)) { - return this.formatTranscriptSegments(transcriptionData); + const response = await fetch(url); + if (!response.ok) { + logger.warn(`Failed to fetch transcript: ${response.status}`); + return ''; } - // If it's a string (plain text transcript), return as-is - if (typeof transcriptionData === 'string') { - return transcriptionData; + const text = await response.text(); + + if (recordingData.diarizationUrl) { + // Diarization is JSONL: one JSON object per line + // Format: {"speaker": "Name", "text": "...", "start": 0.0, "end": 1.0} + return text + .split('\n') + .filter((line) => line.trim()) + .map((line) => { + try { + const entry = JSON.parse(line) as Record; + const speaker = (entry.speaker as string) || 'Unknown'; + const content = (entry.text as string) || ''; + return `${speaker}: ${content}`; + } catch { + return ''; + } + }) + .filter(Boolean) + .join('\n'); } - logger.warn('unexpected transcription format, storing as JSON'); - return JSON.stringify(transcriptionData); + return text; } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.warn(`failed to fetch transcription: ${message}`); + const msg = error instanceof Error ? error.message : String(error); + logger.warn(`Failed to fetch transcript: ${msg}`); return ''; } } - - // Format transcript segments into readable speaker-attributed text - private formatTranscriptSegments( - segments: Array<{ speaker?: string; words?: Array<{ word: string }> }>, - ): string { - const lines: string[] = []; - let currentSpeaker = ''; - - for (const segment of segments) { - const speaker = segment.speaker || 'Unknown'; - const text = segment.words?.map((w) => w.word).join(' ') || ''; - if (!text) continue; - - if (speaker !== currentSpeaker) { - currentSpeaker = speaker; - lines.push(`\n${speaker}:`); - } - lines.push(text); - } - - return lines.join('\n').trim(); - } } diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts index 2dd830213ca..300b31d72bb 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/navigation-menu-items/recordings.navigation-menu-item.ts @@ -2,7 +2,7 @@ import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; export default defineNavigationMenuItem({ - universalIdentifier: '6e7bbaa0-d46f-5701-ae91-7346afcb936c', + universalIdentifier: 'a569ca64-99b6-4334-a51e-d5fc59e8da57', position: 0, type: NavigationMenuItemType.OBJECT, targetObjectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts index 4effbbf1b4a..138f6dc891e 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/objects/recording.ts @@ -1,18 +1,19 @@ import { defineObject, FieldType } from 'twenty-sdk'; export const RECORDING_UNIVERSAL_IDENTIFIER = - '0ec52997-4307-50a7-a929-85ad6f24b5d9'; + 'cf2647b6-ce44-44ca-a866-5db0071899da'; // Field UUIDs — exported for use in views, relations, and page layouts -export const NAME_FIELD_ID = '6b04e869-c04e-5ebe-bfc4-7778a1108b17'; -export const BOT_ID_FIELD_ID = '138d74e2-08b6-57e1-9eba-872c0747636f'; -export const DATE_FIELD_ID = 'e9bce8f0-1a6d-507b-b0d8-00a9fc1386a5'; -export const DURATION_FIELD_ID = '895cd235-1ad4-59b3-bfc9-8b2522d7d20f'; -export const TRANSCRIPT_FIELD_ID = 'ebbc2924-3eb3-5895-b1b8-0cddb430fc9e'; -export const MEETING_URL_FIELD_ID = 'ed410c1d-f23e-5d0a-8abd-699e8393edc8'; -export const MP4_URL_FIELD_ID = 'ca9a9c4d-2aea-5b3e-9d27-22618f2095ee'; -export const PLATFORM_FIELD_ID = '420c95de-9dbf-5eb5-b4fd-1327d0440941'; -export const STATUS_FIELD_ID = '4c05856d-9182-5d40-bf27-07bc6f6457c3'; +export const NAME_FIELD_ID = 'b6209c31-8f6a-453e-8291-41191c0394ae'; +export const BOT_ID_FIELD_ID = '68967b34-482f-47c2-8d81-7fa1c824bd82'; +export const DATE_FIELD_ID = '74bd2089-d5e2-4488-97fb-017ea8e0d14a'; +export const DURATION_FIELD_ID = '2ee873e5-60eb-40b1-9a0f-b10d7d48f297'; +export const TRANSCRIPT_FIELD_ID = '06e9adf7-392d-44a0-abc9-ef5c1e81bdd7'; +export const SUMMARY_FIELD_ID = 'c4a7e2f1-9b3d-4e8a-b5c6-d7f0a1e2b3c4'; +export const MEETING_URL_FIELD_ID = 'ad56795c-49ac-4075-b099-42e938d9e36e'; +export const MP4_URL_FIELD_ID = '00ecab81-954c-47c4-a263-96fd5ccbc39d'; +export const PLATFORM_FIELD_ID = 'eaa24930-508b-4e63-80c3-cc607d8e2845'; +export const STATUS_FIELD_ID = '6fce2fe2-169e-4780-9fc4-d330d3cd39bc'; export default defineObject({ universalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, @@ -61,6 +62,13 @@ export default defineObject({ name: 'transcript', icon: 'IconFileText', }, + { + universalIdentifier: SUMMARY_FIELD_ID, + type: FieldType.TEXT, + label: 'Summary', + name: 'summary', + icon: 'IconNotes', + }, { universalIdentifier: MEETING_URL_FIELD_ID, type: FieldType.LINKS, @@ -82,10 +90,10 @@ export default defineObject({ name: 'platform', icon: 'IconDevices', options: [ - { id: '75e03212-d08e-5f37-8b1c-405229a2b3b6', value: 'GOOGLE_MEET', label: 'Google Meet', position: 0, color: 'blue' }, - { id: '30217257-f906-540a-b82a-8399e558fe4e', value: 'ZOOM', label: 'Zoom', position: 1, color: 'sky' }, - { id: 'b8971190-2e5a-5b52-a997-41bb0b227385', value: 'MICROSOFT_TEAMS', label: 'Microsoft Teams', position: 2, color: 'purple' }, - { id: '876a7992-ada9-5c28-a240-2e0843e66e15', value: 'UNKNOWN', label: 'Unknown', position: 3, color: 'gray' }, + { id: 'b3521d94-c929-45b0-8573-26567e6dfc85', value: 'GOOGLE_MEET', label: 'Google Meet', position: 0, color: 'blue' }, + { id: 'd517462f-8737-4a71-b1bd-bca56a82a1fa', value: 'ZOOM', label: 'Zoom', position: 1, color: 'sky' }, + { id: '3e8c1061-6b12-4f2d-8e52-3bf4667dae01', value: 'MICROSOFT_TEAMS', label: 'Microsoft Teams', position: 2, color: 'purple' }, + { id: 'd0d8114f-811c-4d14-8c6d-fea5283e2b61', value: 'UNKNOWN', label: 'Unknown', position: 3, color: 'gray' }, ], }, { @@ -95,9 +103,9 @@ export default defineObject({ name: 'status', icon: 'IconStatusChange', options: [ - { id: '9347a357-cea4-5ecd-843d-dd57687821b8', value: 'COMPLETED', label: 'Completed', position: 0, color: 'green' }, - { id: 'c1e846a4-159f-5d9d-b7ff-06120bf714f1', value: 'FAILED', label: 'Failed', position: 1, color: 'red' }, - { id: '28e99b5d-387f-5002-86ef-62478e95c69a', value: 'IN_PROGRESS', label: 'In Progress', position: 2, color: 'orange' }, + { id: 'e127d867-4931-4e15-a5be-971caa8678a3', value: 'COMPLETED', label: 'Completed', position: 0, color: 'green' }, + { id: '776885a7-8a42-415c-92fe-e08f5a1274ea', value: 'FAILED', label: 'Failed', position: 1, color: 'red' }, + { id: '12374b43-a6fe-48d6-8a71-437242e59df2', value: 'IN_PROGRESS', label: 'In Progress', position: 2, color: 'orange' }, ], }, ], diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.test.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.test.ts new file mode 100644 index 00000000000..fba67e4c8d3 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.test.ts @@ -0,0 +1,17 @@ +import routeDefinition from './receive-recording-webhook'; + +import { describe, expect, it } from 'vitest'; + +describe('receive-recording-webhook route', () => { + it('forwards the Meeting BaaS auth headers required by the handler', () => { + expect(routeDefinition.config.httpRouteTriggerSettings).toMatchObject({ + path: '/webhook/meeting-baas', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: [ + 'x-mb-secret', + 'content-type', + ], + }); + }); +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts index b8af5fed62d..fe8a984cd55 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/receive-recording-webhook.ts @@ -3,7 +3,7 @@ import type { ProcessResult } from './types'; import { WebhookHandler } from './webhook-handler'; export default defineLogicFunction({ - universalIdentifier: '0c0c376e-3d0a-5348-b6a7-3b4f35b33f9e', + universalIdentifier: 'c1449053-7e9d-4377-8502-9db6a89a1781', name: 'receive-recording-webhook', description: 'Receives Meeting BaaS webhooks when recordings complete, and stores them in Twenty.', @@ -12,6 +12,10 @@ export default defineLogicFunction({ path: '/webhook/meeting-baas', httpMethod: 'POST', isAuthRequired: false, + forwardedRequestHeaders: [ + 'x-mb-secret', + 'content-type', + ], }, handler: async ( params: unknown, diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts index 8f8df90db2c..10d50f710c5 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/roles/default.role.ts @@ -1,13 +1,18 @@ import { defineRole, PermissionFlag } from 'twenty-sdk'; import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording'; -export const DEFAULT_ROLE_ID = '5aceb3d4-2453-58e8-a19f-e3ae7e148e93'; +export const DEFAULT_ROLE_ID = '3231cf40-5b90-4c2b-ae41-fcb5606299b4'; export default defineRole({ universalIdentifier: DEFAULT_ROLE_ID, label: 'Meeting BaaS Recorder role', description: 'Default role for the Meeting BaaS Recorder app', - canReadAllObjectRecords: false, + // Needs read access to calendarEvent, calendarEventParticipant, + // calendarChannelEventAssociation, calendarChannel, connectedAccount, + // workspaceMember for ownership resolution and preference checks. + // Per-object permissions on system objects are not supported, so we + // grant broad read access here while keeping writes constrained. + canReadAllObjectRecords: true, canUpdateAllObjectRecords: false, canSoftDeleteAllObjectRecords: false, canDestroyAllObjectRecords: false, @@ -24,5 +29,5 @@ export default defineRole({ canDestroyObjectRecords: false, }, ], - permissionFlags: [PermissionFlag.APPLICATIONS], + permissionFlags: [PermissionFlag.APPLICATIONS, PermissionFlag.AI], }); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/shims/twenty-shared/types.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/shims/twenty-shared/types.ts new file mode 100644 index 00000000000..f9c64bd9379 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/shims/twenty-shared/types.ts @@ -0,0 +1,34 @@ +// Local shim for twenty-shared/types +// The twenty-sdk type declarations reference twenty-shared/types but the package +// is not published with proper subpath exports. This shim provides the types +// needed by this app's view definitions. + +export enum ViewType { + TABLE = 'TABLE', + KANBAN = 'KANBAN', + CALENDAR = 'CALENDAR', + FIELDS_WIDGET = 'FIELDS_WIDGET', +} + +export enum ViewKey { + INDEX = 'INDEX', +} + +export enum ViewFilterOperand { + IS = 'IS', + IS_NOT_NULL = 'IS_NOT_NULL', + IS_NOT = 'IS_NOT', + LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL', + GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL', + IS_BEFORE = 'IS_BEFORE', + IS_AFTER = 'IS_AFTER', + CONTAINS = 'CONTAINS', + DOES_NOT_CONTAIN = 'DOES_NOT_CONTAIN', + IS_EMPTY = 'IS_EMPTY', + IS_NOT_EMPTY = 'IS_NOT_EMPTY', + IS_RELATIVE = 'IS_RELATIVE', + IS_IN_PAST = 'IS_IN_PAST', + IS_IN_FUTURE = 'IS_IN_FUTURE', + IS_TODAY = 'IS_TODAY', + VECTOR_SEARCH = 'VECTOR_SEARCH', +} diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.test.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.test.ts new file mode 100644 index 00000000000..388d9e9eff4 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.test.ts @@ -0,0 +1,89 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mocks = vi.hoisted(() => ({ + axiosGet: vi.fn(), +})); + +vi.mock('axios', () => ({ + default: { + get: mocks.axiosGet, + }, +})); + +describe('resolveCalendarEventOwner', () => { + beforeEach(() => { + vi.resetModules(); + mocks.axiosGet.mockReset(); + process.env.TWENTY_API_KEY = 'test-api-key'; + process.env.TWENTY_API_URL = 'https://twenty.example.com'; + }); + + it('falls back to a participant workspace member when the channel chain is unavailable', async () => { + mocks.axiosGet.mockImplementation(async (url: string) => { + if (url.includes('/calendarChannelEventAssociations')) { + throw new Error('404 Not Found'); + } + if (url.includes('/calendarEventParticipants')) { + return { + data: { + data: { + calendarEventParticipants: [ + { workspaceMemberId: 'wm-123' }, + ], + }, + }, + }; + } + if (url.endsWith('/workspaceMembers/wm-123')) { + return { + data: { + data: { + name: { firstName: 'Ada', lastName: 'Lovelace' }, + }, + }, + }; + } + throw new Error(`Unexpected URL: ${url}`); + }); + + const { resolveCalendarEventOwner } = await import('./twenty-sync-service'); + + await expect(resolveCalendarEventOwner('event-participant-fallback')).resolves.toEqual({ + workspaceMemberId: 'wm-123', + workspaceMemberName: 'Ada Lovelace', + }); + }); + + it('returns no owner instead of guessing from the workspace when ownership cannot be resolved', async () => { + mocks.axiosGet.mockImplementation(async (url: string) => { + if (url.includes('/calendarChannelEventAssociations')) { + return { + data: { + data: { + calendarChannelEventAssociations: [], + }, + }, + }; + } + if (url.includes('/calendarEventParticipants')) { + return { + data: { + data: { + calendarEventParticipants: [], + }, + }, + }; + } + throw new Error(`Unexpected URL: ${url}`); + }); + + const { resolveCalendarEventOwner } = await import('./twenty-sync-service'); + + await expect(resolveCalendarEventOwner('event-no-owner')).resolves.toEqual({}); + expect( + mocks.axiosGet.mock.calls.some(([url]) => + String(url).includes('/workspaceMembers?'), + ), + ).toBe(false); + }); +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts index 1ea57623281..8c58103aeaa 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/twenty-sync-service.ts @@ -5,10 +5,23 @@ import type { RecordingUpsertInput, SyncResult, } from './types'; -import { getRestApiUrl, restHeaders } from './utils'; +import { buildRestUrl, getRestApiUrl, restHeaders } from './utils'; const TWENTY_API_KEY: string = process.env.TWENTY_API_KEY ?? ''; +const authHeaders = () => ({ Authorization: `Bearer ${TWENTY_API_KEY}` }); + +// --- REST API response types --- + +type TwentyListResponse = { + data?: Record[]>; +}; + +type TwentyDetailResponse = { + data?: Record; + id?: string; +}; + // --- Platform Detection --- export const detectPlatform = (meetingUrl: string): MeetingPlatform => { @@ -22,87 +35,115 @@ export const detectPlatform = (meetingUrl: string): MeetingPlatform => { }; // --- Calendar event ownership resolution --- -// Walk CalendarEvent -> CalendarChannelEventAssociation -> CalendarChannel -> ConnectedAccount -> WorkspaceMember +// Primary: CalendarEvent -> CalendarChannelEventAssociation -> CalendarChannel -> ConnectedAccount -> WorkspaceMember +// Fallback: CalendarEventParticipant with workspaceMemberId const ownershipCache = new Map(); -export const resolveCalendarEventOwner = async ( +const resolveViaChannelChain = async ( calendarEventId: string, -): Promise => { - const cached = ownershipCache.get(calendarEventId); - if (cached) return cached; +): Promise => { + // 1. CalendarEvent -> CalendarChannelEventAssociation -> calendarChannelId + const assocUrl = buildRestUrl('calendarChannelEventAssociations', { + filter: { calendarEventId: { eq: calendarEventId } }, + limit: 1, + }); + const assocResponse = await axios.get>( + assocUrl, + { headers: authHeaders() }, + ); - const result: CalendarEventOwnership = {}; + const associations = assocResponse.data?.data?.calendarChannelEventAssociations ?? []; + if (associations.length === 0) return null; - try { - // 1. CalendarEvent -> CalendarChannelEventAssociation -> calendarChannelId - const assocResponse = await axios({ - method: 'GET', - headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, - url: `${getRestApiUrl()}/calendarChannelEventAssociations?filter=calendarEventId%5Beq%5D%3A%22${encodeURIComponent(calendarEventId)}%22&limit=1`, - }); + const calendarChannelId = associations[0].calendarChannelId as string | undefined; + if (!calendarChannelId) return null; - const associations: Record[] = - assocResponse.data?.data?.calendarChannelEventAssociations ?? []; - if (associations.length === 0) { - ownershipCache.set(calendarEventId, result); - return result; - } + // 2. CalendarChannel -> connectedAccountId + const channelResponse = await axios.get( + `${getRestApiUrl()}/calendarChannels/${calendarChannelId}`, + { headers: authHeaders() }, + ); - const calendarChannelId = associations[0].calendarChannelId as string | undefined; - if (!calendarChannelId) { - ownershipCache.set(calendarEventId, result); - return result; - } + const channelBody = channelResponse.data?.data ?? channelResponse.data; + const channelData = (channelBody as Record)?.calendarChannel ?? channelBody; + const connectedAccountId = (channelData as Record)?.connectedAccountId as string | undefined; + if (!connectedAccountId) return null; - // 2. CalendarChannel -> connectedAccountId - const channelResponse = await axios({ - method: 'GET', - headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, - url: `${getRestApiUrl()}/calendarChannels/${calendarChannelId}`, - }); + // 3. ConnectedAccount -> accountOwnerId (= workspaceMemberId) + const accountResponse = await axios.get( + `${getRestApiUrl()}/connectedAccounts/${connectedAccountId}`, + { headers: authHeaders() }, + ); - const channelData = channelResponse.data?.data ?? channelResponse.data; - const connectedAccountId = channelData?.connectedAccountId as string | undefined; - if (!connectedAccountId) { - ownershipCache.set(calendarEventId, result); - return result; - } + const accountBody = accountResponse.data?.data ?? accountResponse.data; + const accountData = (accountBody as Record)?.connectedAccount ?? accountBody; + const workspaceMemberId = (accountData as Record)?.accountOwnerId as string | undefined; + if (!workspaceMemberId) return null; - // 3. ConnectedAccount -> accountOwnerId (= workspaceMemberId) - const accountResponse = await axios({ - method: 'GET', - headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, - url: `${getRestApiUrl()}/connectedAccounts/${connectedAccountId}`, - }); + return { workspaceMemberId }; +}; + +const resolveViaParticipants = async ( + calendarEventId: string, +): Promise => { + const url = buildRestUrl('calendarEventParticipants', { + filter: { calendarEventId: { eq: calendarEventId } }, + limit: 10, + }); + const response = await axios.get>( + url, + { headers: authHeaders() }, + ); + const participants = response.data?.data?.calendarEventParticipants ?? []; + for (const p of participants) { + const wmId = p.workspaceMemberId as string | undefined; + if (wmId) return { workspaceMemberId: wmId }; + } + return null; +}; - const accountData = accountResponse.data?.data ?? accountResponse.data; - const workspaceMemberId = accountData?.accountOwnerId as string | undefined; - if (!workspaceMemberId) { - ownershipCache.set(calendarEventId, result); - return result; +export const resolveCalendarEventOwner = async ( + calendarEventId: string, +): Promise => { + const cached = ownershipCache.get(calendarEventId); + if (cached) return cached; + + let result: CalendarEventOwnership = {}; + + // Try primary chain: association -> channel -> account -> member + try { + const primary = await resolveViaChannelChain(calendarEventId); + if (primary?.workspaceMemberId) result = primary; + } catch { + // Chain broken (404 on channel/account), fall through + } + + // Fallback 1: participant with workspaceMemberId + if (!result.workspaceMemberId) { + try { + const participant = await resolveViaParticipants(calendarEventId); + if (participant?.workspaceMemberId) result = participant; + } catch { + // fall through } - result.workspaceMemberId = workspaceMemberId; + } - // 4. WorkspaceMember -> display name + // Resolve display name if we have a workspaceMemberId but no name + if (result.workspaceMemberId && !result.workspaceMemberName) { try { - const memberResponse = await axios({ - method: 'GET', - headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, - url: `${getRestApiUrl()}/workspaceMembers/${workspaceMemberId}`, - }); - - const memberData = memberResponse.data?.data ?? memberResponse.data; - const firstName = (memberData?.name?.firstName as string) ?? ''; - const lastName = (memberData?.name?.lastName as string) ?? ''; - const fullName = [firstName, lastName].filter(Boolean).join(' '); + const memberResponse = await axios.get( + `${getRestApiUrl()}/workspaceMembers/${result.workspaceMemberId}`, + { headers: authHeaders() }, + ); + const memberBody = memberResponse.data?.data ?? memberResponse.data; + const memberData = (memberBody as Record)?.workspaceMember ?? memberBody; + const name = (memberData as Record)?.name as { firstName?: string; lastName?: string } | undefined; + const fullName = [name?.firstName, name?.lastName].filter(Boolean).join(' '); if (fullName) result.workspaceMemberName = fullName; } catch { - // Non-fatal: we still have the workspaceMemberId + // Non-fatal } - } catch (error) { - const msg = error instanceof Error ? error.message : String(error); - console.warn(`Ownership resolution failed for calendarEvent ${calendarEventId}: ${msg}`); } ownershipCache.set(calendarEventId, result); @@ -111,17 +152,36 @@ export const resolveCalendarEventOwner = async ( // --- Recording CRUD --- +export const checkIfRecordingExistsForEvent = async ( + calendarEventId: string, +): Promise => { + try { + const url = buildRestUrl('recordings', { + filter: { calendarEventId: { eq: calendarEventId } }, + limit: 1, + }); + const response = await axios.get>(url, { + headers: authHeaders(), + }); + const recordings = response.data?.data?.recordings ?? []; + return recordings.length > 0; + } catch { + return false; + } +}; + export const checkIfRecordingExists = async ( botId: string, ): Promise => { try { - const response = await axios({ - method: 'GET', - headers: { Authorization: `Bearer ${TWENTY_API_KEY}` }, - url: `${getRestApiUrl()}/recordings?filter=botId%5Beq%5D%3A%22${encodeURIComponent(botId)}%22`, + const url = buildRestUrl('recordings', { + filter: { botId: { eq: botId } }, + }); + const response = await axios.get>(url, { + headers: authHeaders(), }); const recording = response.data?.data?.recordings?.[0]; - return recording?.id ?? null; + return (recording?.id as string) ?? null; } catch { return null; } @@ -141,6 +201,7 @@ export const upsertRecording = async ( status: opts.status, transcript: opts.transcript, }; + if (opts.summary) data.summary = opts.summary; if (opts.meetingUrl) data.meetingUrl = opts.meetingUrl; if (opts.mp4Url) data.mp4Url = opts.mp4Url; if (opts.calendarEventId) data.calendarEventId = opts.calendarEventId; @@ -177,12 +238,12 @@ export const syncBotRecording = async ( date: string; duration: number; transcript: string; + summary?: string; mp4Url: string; meetingUrl: string; platform: MeetingPlatform; calendarEventId?: string; workspaceMemberId?: string; - status?: 'COMPLETED' | 'FAILED' | 'IN_PROGRESS'; }, result: SyncResult, ): Promise => { @@ -195,7 +256,7 @@ export const syncBotRecording = async ( date: recordingData.date, duration: durationMinutes, platform: recordingData.platform, - status: recordingData.status || 'COMPLETED', + status: 'COMPLETED', meetingUrl: recordingData.meetingUrl ? { primaryLinkLabel: 'Join Meeting', primaryLinkUrl: recordingData.meetingUrl, secondaryLinks: null } : null, @@ -203,6 +264,7 @@ export const syncBotRecording = async ( ? { primaryLinkLabel: 'Watch Recording', primaryLinkUrl: recordingData.mp4Url, secondaryLinks: null } : null, transcript: recordingData.transcript, + summary: recordingData.summary, calendarEventId: recordingData.calendarEventId, workspaceMemberId: recordingData.workspaceMemberId, }); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/types.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/types.ts index e5f82fdcbcd..3f073c9cc45 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/types.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/types.ts @@ -1,4 +1,4 @@ -// SDK V2 types and runtime constants +// SDK V2 types import type { V2 } from '@meeting-baas/sdk'; export type BotWebhookCompleted = V2.BotWebhookCompleted; @@ -7,13 +7,11 @@ export type BotWebhookFailed = V2.BotWebhookFailed; export type BotWebhookFailedData = V2.BotWebhookFailedData; export type BotWebhookStatusChange = V2.BotWebhookStatusChange; -// Union of all webhook payloads we handle export type MeetingBaasWebhookPayload = | BotWebhookCompleted | BotWebhookFailed | BotWebhookStatusChange; -// Webhook event discriminants derived from SDK types export const WebhookEvent = { COMPLETED: 'bot.completed' as BotWebhookCompleted['event'], FAILED: 'bot.failed' as BotWebhookFailed['event'], @@ -34,6 +32,8 @@ export type RecordingData = { date: string; duration: number; transcript: string; + transcriptionUrl?: string; + diarizationUrl?: string; mp4Url: string; meetingUrl: string; platform: MeetingPlatform; @@ -59,6 +59,7 @@ export type RecordingUpsertInput = { meetingUrl: { primaryLinkLabel: string; primaryLinkUrl: string; secondaryLinks: null } | null; mp4Url: { primaryLinkLabel: string; primaryLinkUrl: string; secondaryLinks: null } | null; transcript: string; + summary?: string; calendarEventId?: string; workspaceMemberId?: string; }; diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/utils.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/utils.ts index 84ea92a0232..7ef2f9e6d19 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/utils.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/utils.ts @@ -11,6 +11,43 @@ export const restHeaders = (): Record => ({ 'Content-Type': 'application/json', }); +// Build a Twenty REST API URL with typed filter and limit params. +// Example: buildRestUrl('recordings', { filter: { botId: { eq: 'bot-123' } }, limit: 1 }) +// → https://api.twenty.com/rest/recordings?filter=botId[eq]:"bot-123"&limit=1 + +type FilterCondition = Partial>; + +export const buildRestUrl = ( + resource: string, + options?: { filter?: Record; limit?: number; cursor?: string }, +): string => { + const base = `${getRestApiUrl()}/${resource}`; + if (!options) return base; + + const params = new URLSearchParams(); + + if (options.filter) { + const filterParts = Object.entries(options.filter) + .flatMap(([field, conditions]) => + Object.entries(conditions).map( + ([op, value]) => `${field}[${op}]:"${value}"`, + ), + ) + .join(','); + params.set('filter', filterParts); + } + + if (options.limit !== undefined) { + params.set('limit', String(options.limit)); + } + + if (options.cursor) { + params.set('starting_after', options.cursor); + } + + return `${base}?${params.toString()}`; +}; + export const formatDuration = (seconds: number): string => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts index 7108bc2625d..a7112d80eaf 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/all-recordings.view.ts @@ -11,7 +11,7 @@ import { MP4_URL_FIELD_ID, } from '../objects/recording'; -export const ALL_RECORDINGS_VIEW_ID = '264b131d-2c52-5a8b-bdfe-2221e62d2123'; +export const ALL_RECORDINGS_VIEW_ID = 'bd6347b3-3b8a-4095-a0bb-24220b5040bd'; export default defineView({ universalIdentifier: ALL_RECORDINGS_VIEW_ID, @@ -22,12 +22,12 @@ export default defineView({ icon: 'IconVideo', position: 0, fields: [ - { universalIdentifier: '3adb270f-88f4-5d70-b41a-3bac6f45ee9b', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, - { universalIdentifier: '58c8d9ff-2bc1-59ed-91c3-786a8ca1ccf7', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, - { universalIdentifier: '39c719be-fc9f-51ed-8735-fae5381cb8ca', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, - { universalIdentifier: 'dc6cb2b6-d887-5243-ab51-495ac36bde0b', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, - { universalIdentifier: 'f8605012-003f-5460-8c3e-d5685a24b97a', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, - { universalIdentifier: 'ab2dce09-bbb9-5f64-af5f-48da59dd7a53', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, - { universalIdentifier: '3cd5d593-2f93-5a62-9de9-f70c1458a476', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, + { universalIdentifier: 'e75cd4e3-e42e-43c7-bd94-532f2766ea11', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, + { universalIdentifier: '30411458-e7bc-463f-8308-a9c0c947122c', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, + { universalIdentifier: '74be7ee1-10f5-49a1-9af8-86174a2d037f', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, + { universalIdentifier: '83fc4647-d08a-400e-840e-01f3b9cbe51e', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, + { universalIdentifier: '59f601ca-c442-4feb-a803-476eff07d534', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, + { universalIdentifier: '3becc98e-ee5f-479a-a947-9e7db0412e8b', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, + { universalIdentifier: 'f72a4775-4443-467f-ad0b-8e31323bc322', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, ], }); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts index 24569f1cbe2..dcd358ecbcc 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/by-platform.view.ts @@ -6,7 +6,7 @@ import { } from '../objects/recording'; export default defineView({ - universalIdentifier: 'c8f0b6fa-dc35-5adb-83c8-11749702025e', + universalIdentifier: '9d3114e6-3ba9-4e31-8197-45e510b69c4c', name: 'By Platform', objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, type: ViewType.KANBAN, @@ -14,12 +14,12 @@ export default defineView({ position: 2, mainGroupByFieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, groups: [ - { universalIdentifier: 'd7cc1b3a-d079-54e9-a853-01bbe9306fab', fieldValue: 'GOOGLE_MEET', isVisible: true, position: 0 }, - { universalIdentifier: '708b2535-067b-5247-b9ef-09c5d80e6cff', fieldValue: 'ZOOM', isVisible: true, position: 1 }, - { universalIdentifier: 'a732280e-da0a-59ae-8421-86909b7e367f', fieldValue: 'MICROSOFT_TEAMS', isVisible: true, position: 2 }, - { universalIdentifier: 'e49d6c58-790b-5da3-b2d7-5f0dc641970c', fieldValue: 'UNKNOWN', isVisible: true, position: 3 }, + { universalIdentifier: '7a0144b1-6d0e-453d-8cda-cf1fa63d3b77', fieldValue: 'GOOGLE_MEET', isVisible: true, position: 0 }, + { universalIdentifier: '8ed53363-962d-4136-b34c-fae2eae7d3f9', fieldValue: 'ZOOM', isVisible: true, position: 1 }, + { universalIdentifier: '7ab9b893-6f97-4973-a4a9-2295f69f5d6d', fieldValue: 'MICROSOFT_TEAMS', isVisible: true, position: 2 }, + { universalIdentifier: '3e82c80b-66db-4be2-8043-552ee309d26c', fieldValue: 'UNKNOWN', isVisible: true, position: 3 }, ], fields: [ - { universalIdentifier: '1a38ccfd-71e3-59d5-8d50-94fc1c9d25ef', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 0, isVisible: true, size: 150 }, + { universalIdentifier: 'a578e600-97ec-4cd3-84fe-b254c3f89130', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 0, isVisible: true, size: 150 }, ], }); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts index a18a022f1a6..800a5f523b7 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/views/completed-recordings.view.ts @@ -12,24 +12,24 @@ import { } from '../objects/recording'; export default defineView({ - universalIdentifier: '394e8d23-555f-5bf7-9b4e-367b6dbf38b0', + universalIdentifier: 'd6325047-8ba7-4976-a732-4e280db36dfa', name: 'Completed Recordings', objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER, type: ViewType.TABLE, icon: 'IconCheck', position: 1, fields: [ - { universalIdentifier: '0028769e-bca1-596c-aafe-fc5531db661a', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, - { universalIdentifier: '1fa50163-249f-5803-bbce-541ed672feea', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, - { universalIdentifier: 'f9905717-131e-59df-99a9-44f33a2beded', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, - { universalIdentifier: 'bdacee9b-9cb3-5948-9421-6908309c21b9', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, - { universalIdentifier: '62292205-0c46-55b9-be01-f15297b66d87', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, - { universalIdentifier: 'b9814d60-e66b-57c2-a67b-3ecd4eddbc43', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, - { universalIdentifier: '67643943-04f2-5b7c-9592-0d69824a3967', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, + { universalIdentifier: 'b953f475-0eb7-4864-b3e8-d2c4e8b02a3b', fieldMetadataUniversalIdentifier: NAME_FIELD_ID, position: 0, isVisible: true, size: 200 }, + { universalIdentifier: 'b3bd42e6-05cd-4c3a-bfad-726ef75a0c15', fieldMetadataUniversalIdentifier: DATE_FIELD_ID, position: 1, isVisible: true, size: 150 }, + { universalIdentifier: 'e15d29e8-468a-4b8f-93cb-a71d89f01d45', fieldMetadataUniversalIdentifier: DURATION_FIELD_ID, position: 2, isVisible: true, size: 100 }, + { universalIdentifier: 'c2b37ddf-f16f-4b5f-b443-c6b5214eb4b4', fieldMetadataUniversalIdentifier: PLATFORM_FIELD_ID, position: 3, isVisible: true, size: 130 }, + { universalIdentifier: '489703b0-1cd1-4422-98d8-6d8d32063821', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, position: 4, isVisible: true, size: 110 }, + { universalIdentifier: '76d8a2c2-259f-49cc-8921-6ebad06ac276', fieldMetadataUniversalIdentifier: MEETING_URL_FIELD_ID, position: 5, isVisible: true, size: 140 }, + { universalIdentifier: '4d31d04c-f90e-413a-9c67-38cb6a3594a3', fieldMetadataUniversalIdentifier: MP4_URL_FIELD_ID, position: 6, isVisible: true, size: 140 }, ], filters: [ { - universalIdentifier: '1f9a7d8e-19d2-5d39-8783-65529e0048fa', + universalIdentifier: 'ee754547-ed53-4d9e-8155-fbf58d8ed016', fieldMetadataUniversalIdentifier: STATUS_FIELD_ID, operand: ViewFilterOperand.IS, value: 'COMPLETED', diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.test.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.test.ts new file mode 100644 index 00000000000..d73fd91f7b3 --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.test.ts @@ -0,0 +1,156 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { WebhookHandler } from './webhook-handler'; + +const mocks = vi.hoisted(() => ({ + generateSummary: vi.fn(), + syncBotRecording: vi.fn(), + transformWebhookData: vi.fn(), + fetchTranscript: vi.fn(), + meetingBaasConstructor: vi.fn(), +})); + +vi.mock('./generate-summary', () => ({ + generateSummary: mocks.generateSummary, +})); + +vi.mock('./twenty-sync-service', () => ({ + syncBotRecording: mocks.syncBotRecording, +})); + +vi.mock('./meeting-baas-api-client', () => ({ + MeetingBaasApiClient: class { + constructor(apiKey: string) { + mocks.meetingBaasConstructor(apiKey); + } + + transformWebhookData(...args: unknown[]) { + return mocks.transformWebhookData(...args); + } + + fetchTranscript(...args: unknown[]) { + return mocks.fetchTranscript(...args); + } + }, +})); + +const completedPayload = { + event: 'bot.completed', + data: { + bot_id: 'bot-123', + duration_seconds: 1800, + video: 'https://example.com/recording.mp4', + joined_at: '2026-04-14T10:00:00Z', + participants: [], + }, + extra: { + meeting_url: 'https://meet.google.com/abc-defg-hij', + meeting_title: 'Test Recording', + calendarEventId: 'calendar-event-1', + workspaceMemberId: 'workspace-member-1', + }, +} as const; + +describe('WebhookHandler', () => { + beforeEach(() => { + vi.clearAllMocks(); + process.env.MEETING_BAAS_API_KEY = 'secret-123'; + + mocks.transformWebhookData.mockReturnValue({ + botId: 'bot-123', + title: 'Test Recording', + date: '2026-04-14T10:00:00Z', + duration: 1800, + transcript: '', + mp4Url: 'https://example.com/recording.mp4', + meetingUrl: 'https://meet.google.com/abc-defg-hij', + platform: 'GOOGLE_MEET', + extra: { + calendarEventId: 'calendar-event-1', + workspaceMemberId: 'workspace-member-1', + }, + }); + mocks.fetchTranscript.mockResolvedValue('Speaker A: Hello\nSpeaker B: Hi'); + mocks.generateSummary.mockResolvedValue('Summary text'); + mocks.syncBotRecording.mockResolvedValue('recording-123'); + }); + + it('processes a completed webhook authenticated with x-mb-secret', async () => { + const handler = new WebhookHandler(); + + const result = await handler.handle( + { body: completedPayload }, + { 'x-mb-secret': 'secret-123' }, + ); + + expect(result).toEqual({ + success: true, + errors: [], + durationMinutes: 30, + recordingId: 'recording-123', + }); + expect(mocks.meetingBaasConstructor).toHaveBeenCalledWith('secret-123'); + expect(mocks.fetchTranscript).toHaveBeenCalledOnce(); + expect(mocks.generateSummary).toHaveBeenCalledWith('Speaker A: Hello\nSpeaker B: Hi'); + expect(mocks.syncBotRecording).toHaveBeenCalledWith( + expect.objectContaining({ + botId: 'bot-123', + title: 'Test Recording', + transcript: 'Speaker A: Hello\nSpeaker B: Hi', + summary: 'Summary text', + calendarEventId: 'calendar-event-1', + workspaceMemberId: 'workspace-member-1', + }), + expect.any(Object), + ); + }); + + it('rejects webhooks with mismatched auth headers', async () => { + const handler = new WebhookHandler(); + + const result = await handler.handle(completedPayload, { + 'x-mb-secret': 'wrong-secret', + }); + + expect(result).toEqual({ + success: false, + errors: ['Invalid webhook API key'], + }); + expect(mocks.syncBotRecording).not.toHaveBeenCalled(); + }); + + it('reports bot.failed events as errors', async () => { + const handler = new WebhookHandler(); + + const result = await handler.handle( + { + event: 'bot.failed', + data: { + bot_id: 'bot-123', + error_message: 'timeout', + error_code: 'TIMEOUT', + }, + }, + { 'x-mb-secret': 'secret-123' }, + ); + + expect(result.success).toBe(false); + expect(result.errors).toContain('Meeting BaaS bot failed: timeout'); + expect(mocks.syncBotRecording).not.toHaveBeenCalled(); + }); + + it('propagates sync errors to the result', async () => { + mocks.syncBotRecording.mockImplementation((_data: unknown, syncResult: { errors: { botId: string; error: string }[] }) => { + syncResult.errors.push({ botId: 'bot-123', error: '500: Internal Server Error' }); + return null; + }); + + const handler = new WebhookHandler(); + + const result = await handler.handle(completedPayload, { + 'x-mb-secret': 'secret-123', + }); + + expect(result.success).toBe(false); + expect(result.errors?.[0]).toContain('Failed to sync recording'); + }); +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts index 70f4ff1f64f..49ea10b6a3d 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-handler.ts @@ -1,12 +1,13 @@ import { createLogger } from './logger'; +import { generateSummary } from './generate-summary'; import { MeetingBaasApiClient } from './meeting-baas-api-client'; -import { WebhookEvent, type MeetingBaasWebhookPayload, type ProcessResult, type SyncResult } from './types'; +import type { BotWebhookCompletedData, ProcessResult, SyncResult } from './types'; import { getApiKeyFingerprint, - isValidMeetingBaasPayload, + parseWebhookPayload, verifyWebhookApiKey } from './webhook-validator'; -import { detectPlatform, syncBotRecording } from './twenty-sync-service'; +import { syncBotRecording } from './twenty-sync-service'; declare const process: { env: Record }; @@ -26,82 +27,59 @@ export class WebhookHandler { try { this.logger.debug('invoked'); + // MEETING_BAAS_API_KEY is injected by Twenty's logic function executor + // from the app's encrypted applicationVariable (set in UI under Settings > Variables) const meetingBaasApiKey = process.env.MEETING_BAAS_API_KEY || ''; if (!meetingBaasApiKey) { this.logger.critical('MEETING_BAAS_API_KEY not configured'); throw new Error('MEETING_BAAS_API_KEY environment variable is required'); } - const { payload, extractedHeaders } = this.parsePayload(params); + const { payload, extractedHeaders } = parseWebhookPayload(params); const finalHeaders = extractedHeaders || headers; this.logger.debug(`payload event=${payload.event} bot_id=${payload.data.bot_id}`); this.logger.debug(`API key fingerprint=${getApiKeyFingerprint(meetingBaasApiKey)}`); + this.logger.debug(`headers received: ${JSON.stringify(Object.keys(finalHeaders ?? {}))}`); this.verifyApiKey(finalHeaders, meetingBaasApiKey); this.logger.debug('API key verification: ok'); - if (payload.event === WebhookEvent.FAILED) { + if (payload.event === 'bot.failed') { const failedData = payload.data; this.logger.error(`bot failed: ${failedData.error_message} (${failedData.error_code})`); - - const extra = (payload.extra ?? {}) as Record; - const calendarEventId = extra.calendarEventId as string | undefined; - const workspaceMemberId = extra.workspaceMemberId as string | undefined; - const meetingUrl = (extra.meeting_url as string) || ''; - const meetingTitle = (extra.meeting_title as string) || ''; - - const failedSyncResult: SyncResult = { - recordingsProcessed: 0, - recordingsCreated: 0, - recordingsUpdated: 0, - errors: [], - }; - - const recordingId = await syncBotRecording( - { - botId: failedData.bot_id, - title: meetingTitle || `Failed recording — ${failedData.error_code}`, - date: new Date().toISOString(), - duration: 0, - transcript: `Bot failed: ${failedData.error_message} (${failedData.error_code})`, - mp4Url: '', - meetingUrl, - platform: detectPlatform(meetingUrl), - calendarEventId, - workspaceMemberId, - status: 'FAILED', - }, - failedSyncResult, - ); - - if (recordingId) { - result.recordingId = recordingId; - this.logger.debug(`created FAILED recording id=${recordingId}`); - } - - result.success = true; - return result; + throw new Error(`Meeting BaaS bot failed: ${failedData.error_message}`); } - if (payload.event === WebhookEvent.STATUS_CHANGE) { - this.logger.debug(`${WebhookEvent.STATUS_CHANGE} event received - no action needed`); - result.success = true; - return result; + if (payload.event === 'bot.status_change') { + this.logger.debug(`bot status change: ${JSON.stringify(payload.data)}`); + return { success: true }; } - // Transform webhook data - const completedData = payload.data; + // bot.completed — transform, fetch transcript, summarize, sync + const completedData = payload.data as BotWebhookCompletedData; const meetingBaasClient = new MeetingBaasApiClient(meetingBaasApiKey); - const recordingData = await meetingBaasClient.transformWebhookData( + const recordingData = meetingBaasClient.transformWebhookData( completedData, payload.extra ?? undefined, ); result.durationMinutes = Math.round(recordingData.duration / 60); - // calendarEventId and workspaceMemberId are passed via extra - // when schedule-bot.ts creates the scheduled bot + const transcript = await meetingBaasClient.fetchTranscript(recordingData); + if (transcript) { + recordingData.transcript = transcript; + this.logger.debug(`transcript fetched (${transcript.length} chars)`); + } + + // Generate AI summary from transcript (non-fatal if it fails) + const summary = await generateSummary(recordingData.transcript); + if (summary) { + this.logger.debug('AI summary generated'); + } + + // calendarEventId and workspaceMemberId come from the bot's `extra` field, + // set by schedule-bot.ts when creating the scheduled bot const extra = recordingData.extra; const calendarEventId = extra.calendarEventId as string | undefined; const workspaceMemberId = extra.workspaceMemberId as string | undefined; @@ -120,6 +98,7 @@ export class WebhookHandler { date: recordingData.date, duration: recordingData.duration, transcript: recordingData.transcript, + summary: summary ?? undefined, mp4Url: recordingData.mp4Url, meetingUrl: recordingData.meetingUrl, platform: recordingData.platform, @@ -129,6 +108,12 @@ export class WebhookHandler { syncResult, ); + if (syncResult.errors.length > 0) { + const errMsgs = syncResult.errors.map((e) => `${e.botId}: ${e.error}`); + this.logger.error(`sync failed: ${errMsgs.join('; ')}`); + throw new Error(`Failed to sync recording: ${errMsgs.join('; ')}`); + } + if (recordingId) { result.recordingId = recordingId; this.logger.debug(`upserted recording id=${recordingId}`); @@ -144,64 +129,13 @@ export class WebhookHandler { return result; } - private parsePayload(params: unknown): { payload: MeetingBaasWebhookPayload; extractedHeaders?: Record } { - let normalizedParams = params; - let extractedHeaders: Record | undefined; - - if (typeof normalizedParams === 'string') { - try { - normalizedParams = JSON.parse(normalizedParams); - } catch { - throw new Error('Invalid or missing webhook payload'); - } - } - - let payload: MeetingBaasWebhookPayload | undefined; - if (isValidMeetingBaasPayload(normalizedParams)) { - payload = normalizedParams as MeetingBaasWebhookPayload; - } else if (normalizedParams && typeof normalizedParams === 'object') { - const wrapper = normalizedParams as Record; - - if (wrapper.headers && typeof wrapper.headers === 'object' && !Array.isArray(wrapper.headers)) { - extractedHeaders = wrapper.headers as Record; - } - - for (const key of ['params', 'payload', 'body', 'data', 'event']) { - const candidate = wrapper[key]; - if (isValidMeetingBaasPayload(candidate)) { - payload = candidate as MeetingBaasWebhookPayload; - break; - } - } - - if (!payload && typeof wrapper['event'] === 'string' && wrapper['data']) { - const reconstructed = { event: wrapper['event'], data: wrapper['data'] }; - if (isValidMeetingBaasPayload(reconstructed)) { - payload = reconstructed as MeetingBaasWebhookPayload; - } - } - } - - if (!payload) { - throw new Error('Invalid or missing webhook payload'); - } - - return { payload, extractedHeaders }; - } - private verifyApiKey( headers: Record | undefined, expectedApiKey: string ): void { - const normalizedHeaders = headers || {}; - - const providedKey = Object.entries(normalizedHeaders).find( - ([key]) => key.toLowerCase() === 'x-meeting-baas-api-key', - )?.[1]; - - const verification = verifyWebhookApiKey(providedKey, expectedApiKey); + const verification = verifyWebhookApiKey(headers, expectedApiKey); if (!verification.isValid) { - this.logger.critical('Invalid webhook API key - potential security threat'); + this.logger.critical(`Webhook auth failed: ${verification.reason}`); throw new Error('Invalid webhook API key'); } } diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.test.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.test.ts new file mode 100644 index 00000000000..4dcea9180ab --- /dev/null +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, it } from 'vitest'; +import { + getApiKeyFingerprint, + parseWebhookPayload, + verifyWebhookApiKey, +} from './webhook-validator'; + +describe('verifyWebhookApiKey', () => { + it('accepts the V2 x-mb-secret header case-insensitively', () => { + const result = verifyWebhookApiKey( + { 'X-MB-SECRET': 'secret-123' }, + 'secret-123', + ); + + expect(result).toEqual({ isValid: true }); + }); + + it('rejects mismatched webhook secrets', () => { + const result = verifyWebhookApiKey( + { 'x-mb-secret': 'wrong-secret' }, + 'expected-secret', + ); + + expect(result).toEqual({ + isValid: false, + reason: 'x-mb-secret mismatch', + }); + }); + + it('rejects requests without the x-mb-secret header', () => { + const result = verifyWebhookApiKey({ 'content-type': 'application/json' }, 'expected-secret'); + + expect(result).toEqual({ + isValid: false, + reason: 'missing x-mb-secret header', + }); + }); +}); + +describe('parseWebhookPayload', () => { + it('parses a valid bot.completed payload', () => { + const { payload } = parseWebhookPayload({ + event: 'bot.completed', + data: { bot_id: 'bot-123', duration_seconds: 1800 }, + }); + + expect(payload.event).toBe('bot.completed'); + expect(payload.data.bot_id).toBe('bot-123'); + }); + + it('parses a valid bot.failed payload', () => { + const { payload } = parseWebhookPayload({ + event: 'bot.failed', + data: { bot_id: 'bot-456', error_message: 'timeout', error_code: 'TIMEOUT' }, + }); + + expect(payload.event).toBe('bot.failed'); + expect(payload.data.bot_id).toBe('bot-456'); + }); + + it('unwraps payload from a wrapper object with headers', () => { + const { payload, extractedHeaders } = parseWebhookPayload({ + headers: { 'x-mb-secret': 'secret-123' }, + body: { + event: 'bot.completed', + data: { bot_id: 'bot-123' }, + }, + }); + + expect(payload.event).toBe('bot.completed'); + expect(extractedHeaders).toEqual({ 'x-mb-secret': 'secret-123' }); + }); + + it('rejects payloads with unsupported events', () => { + expect(() => + parseWebhookPayload({ + event: 'bot.unknown', + data: { bot_id: 'bot-123' }, + }), + ).toThrow('Invalid or missing webhook payload'); + }); + + it('rejects payloads missing bot_id', () => { + expect(() => + parseWebhookPayload({ + event: 'bot.completed', + data: {}, + }), + ).toThrow('Invalid or missing webhook payload'); + }); + + it('parses JSON string payloads', () => { + const { payload } = parseWebhookPayload( + JSON.stringify({ + event: 'bot.completed', + data: { bot_id: 'bot-123' }, + }), + ); + + expect(payload.event).toBe('bot.completed'); + }); +}); + +describe('getApiKeyFingerprint', () => { + it('returns a stable short fingerprint', () => { + expect(getApiKeyFingerprint('secret-123')).toHaveLength(8); + expect(getApiKeyFingerprint('secret-123')).toBe( + getApiKeyFingerprint('secret-123'), + ); + }); +}); diff --git a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.ts b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.ts index 8e53afb6c0e..c85ce7416a6 100644 --- a/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.ts +++ b/packages/twenty-apps/community/meeting-baas-recorder/src/webhook-validator.ts @@ -1,63 +1,146 @@ import { createHash } from 'crypto'; -import { WebhookEvent, type MeetingBaasWebhookPayload } from './types'; +import { z } from 'zod'; -const VALID_WEBHOOK_EVENTS: MeetingBaasWebhookPayload['event'][] = [ - WebhookEvent.COMPLETED, - WebhookEvent.FAILED, - WebhookEvent.STATUS_CHANGE, -]; +// V2 webhook event types +const WebhookEventSchema = z.enum(['bot.completed', 'bot.failed', 'bot.status_change']); + +// bot.completed payload +const CompletedDataSchema = z.object({ + bot_id: z.string().min(1), + duration_seconds: z.number().optional(), + video: z.string().optional(), + joined_at: z.string().optional(), + participants: z.array(z.unknown()).optional(), + transcription: z.string().optional(), + diarization: z.string().optional(), +}); + +const CompletedPayloadSchema = z.object({ + event: z.literal('bot.completed'), + data: CompletedDataSchema, + extra: z.record(z.string(), z.unknown()).optional().nullable(), +}); + +// bot.failed payload +const FailedDataSchema = z.object({ + bot_id: z.string().min(1), + error_message: z.string().optional(), + error_code: z.string().optional(), +}); + +const FailedPayloadSchema = z.object({ + event: z.literal('bot.failed'), + data: FailedDataSchema, + extra: z.record(z.string(), z.unknown()).optional().nullable(), +}); + +// bot.status_change payload +const StatusChangeDataSchema = z.object({ + bot_id: z.string().min(1), + status: z.string().optional(), +}); + +const StatusChangePayloadSchema = z.object({ + event: z.literal('bot.status_change'), + data: StatusChangeDataSchema, + extra: z.record(z.string(), z.unknown()).optional().nullable(), +}); + +// Union of all V2 webhook payloads +export const WebhookPayloadSchema = z.discriminatedUnion('event', [ + CompletedPayloadSchema, + FailedPayloadSchema, + StatusChangePayloadSchema, +]); + +export type ParsedWebhookPayload = z.infer; export type SignatureVerificationResult = { isValid: boolean; reason?: string; }; -// Meeting BaaS authenticates webhooks by including the API key -// in the x-meeting-baas-api-key header. We verify it matches our configured key. +// V2 authenticates callbacks via x-mb-secret header (the secret from callback_config) export const verifyWebhookApiKey = ( - providedKey: string | undefined, + headers: Record | undefined, expectedKey: string ): SignatureVerificationResult => { - if (!providedKey) { - return { isValid: false, reason: 'missing x-meeting-baas-api-key header' }; - } - if (!expectedKey) { return { isValid: false, reason: 'MEETING_BAAS_API_KEY not configured' }; } - const isValid = providedKey === expectedKey; + if (!headers) { + return { isValid: false, reason: 'no headers provided' }; + } + + const normalized: Record = {}; + for (const [k, v] of Object.entries(headers)) { + normalized[k.toLowerCase()] = v; + } + + const mbSecret = normalized['x-mb-secret']; + if (mbSecret) { + return mbSecret === expectedKey + ? { isValid: true } + : { isValid: false, reason: 'x-mb-secret mismatch' }; + } - return { isValid, reason: isValid ? undefined : 'API key mismatch' }; + return { isValid: false, reason: 'missing x-mb-secret header' }; }; export const getApiKeyFingerprint = (apiKey: string): string => { return createHash('sha256').update(apiKey).digest('hex').substring(0, 8); }; -export const isValidMeetingBaasPayload = ( +// Parse and validate webhook payload using zod. Handles: +// - Direct payload: { event, data } +// - Wrapped payload (Twenty logic function): { params: { event, data }, headers: {...} } +// - String JSON payloads +export const parseWebhookPayload = ( params: unknown -): params is MeetingBaasWebhookPayload => { - if (!params || typeof params !== 'object') { - return false; - } - - const payload = params as Record; +): { payload: ParsedWebhookPayload; extractedHeaders?: Record } => { + let normalizedParams = params; + let extractedHeaders: Record | undefined; - // Must have 'event' field with valid V2 event type - if (typeof payload['event'] !== 'string' || payload['event'].length === 0) { - return false; + if (typeof normalizedParams === 'string') { + try { + normalizedParams = JSON.parse(normalizedParams); + } catch { + throw new Error('Invalid or missing webhook payload'); + } } - if (!VALID_WEBHOOK_EVENTS.includes(payload['event'] as MeetingBaasWebhookPayload['event'])) { - return false; + // Try direct parse first + const directResult = WebhookPayloadSchema.safeParse(normalizedParams); + if (directResult.success) { + return { payload: directResult.data }; } - // Must have 'data' object with bot_id - if (!payload['data'] || typeof payload['data'] !== 'object') { - return false; + // Try unwrapping from a wrapper object (Twenty logic function envelope) + if (normalizedParams && typeof normalizedParams === 'object') { + const wrapper = normalizedParams as Record; + + if (wrapper.headers && typeof wrapper.headers === 'object' && !Array.isArray(wrapper.headers)) { + extractedHeaders = wrapper.headers as Record; + } + + for (const key of ['params', 'payload', 'body', 'data', 'event']) { + const candidate = wrapper[key]; + const wrappedResult = WebhookPayloadSchema.safeParse(candidate); + if (wrappedResult.success) { + return { payload: wrappedResult.data, extractedHeaders }; + } + } + + // Try reconstructing from top-level event + data fields + if (typeof wrapper['event'] === 'string' && wrapper['data']) { + const reconstructed = { event: wrapper['event'], data: wrapper['data'], extra: wrapper['extra'] }; + const reconstructedResult = WebhookPayloadSchema.safeParse(reconstructed); + if (reconstructedResult.success) { + return { payload: reconstructedResult.data, extractedHeaders }; + } + } } - const data = payload['data'] as Record; - return typeof data['bot_id'] === 'string' && data['bot_id'].length > 0; + throw new Error('Invalid or missing webhook payload'); };