@hostwebhook/node-types 1.82.0 → 1.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/airtable-operations.d.ts +90 -0
- package/dist/airtable-operations.js +274 -0
- package/dist/calendly-operations.d.ts +76 -0
- package/dist/calendly-operations.js +289 -0
- package/dist/connections.js +2 -0
- package/dist/credentials.d.ts +6 -0
- package/dist/credentials.js +20 -0
- package/dist/dispatch.js +2 -0
- package/dist/esm/airtable-operations.d.ts +90 -0
- package/dist/esm/airtable-operations.js +270 -0
- package/dist/esm/calendly-operations.d.ts +76 -0
- package/dist/esm/calendly-operations.js +285 -0
- package/dist/esm/connections.js +2 -0
- package/dist/esm/credentials.d.ts +6 -0
- package/dist/esm/credentials.js +20 -0
- package/dist/esm/dispatch.js +2 -0
- package/dist/esm/gmail-operations.d.ts +12 -0
- package/dist/esm/gmail-operations.js +29 -7
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/registry.js +35 -0
- package/dist/esm/types.d.ts +1 -1
- package/dist/esm/ui.js +4 -0
- package/dist/gmail-operations.d.ts +12 -0
- package/dist/gmail-operations.js +29 -7
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -2
- package/dist/registry.js +35 -0
- package/dist/types.d.ts +1 -1
- package/dist/ui.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El nodo de Airtable: leer y escribir records de una tabla.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué existe
|
|
5
|
+
*
|
|
6
|
+
* [2026-09-15] Airtable expone una API REST con OAuth 2.0 (PKCE obligatorio,
|
|
7
|
+
* refresh que rota) y webhooks. Es «la hoja de cálculo con tipos» que mucha
|
|
8
|
+
* gente usa de base de datos ligera, así que este nodo se parece más a
|
|
9
|
+
* Postgres/Sheets que a un servicio de mensajería: casi todo es CRUD sobre
|
|
10
|
+
* una tabla.
|
|
11
|
+
*
|
|
12
|
+
* ## Cómo se nombra todo
|
|
13
|
+
*
|
|
14
|
+
* Una base es `appXXXXXXXXXXXXXX`, una tabla `tblXXXXXXXXXXXXXX`, una vista
|
|
15
|
+
* `viwXXXXXXXXXXXXXX`, un record `recXXXXXXXXXXXXXX`. Base y tabla se eligen
|
|
16
|
+
* en desplegables que lee el esquema (`GET /v0/meta/bases`,
|
|
17
|
+
* `GET /v0/meta/bases/{baseId}/tables`), y **los campos se escriben por
|
|
18
|
+
* nombre**, que es como los ve el usuario en Airtable. El esquema trae tipo
|
|
19
|
+
* y opciones de cada campo, y el editor de campos los usa para ofrecer lo
|
|
20
|
+
* que cabe: las opciones de un select, el formato de una fecha.
|
|
21
|
+
*
|
|
22
|
+
* ## Uno o un lote
|
|
23
|
+
*
|
|
24
|
+
* Cada operación de escritura trabaja sobre UN record —el caso normal, con
|
|
25
|
+
* el Loop node repitiendo si hacen falta varios— y tiene en Advanced un
|
|
26
|
+
* «Batch» que acepta un array del payload. Airtable admite 10 por llamada y
|
|
27
|
+
* 5 llamadas por segundo por base; el servicio trocea y espera, el usuario
|
|
28
|
+
* no tiene que saberlo.
|
|
29
|
+
*
|
|
30
|
+
* ## Por qué ocho
|
|
31
|
+
*
|
|
32
|
+
* Las seis de records (listar, uno, crear, editar, upsert, borrar) más las
|
|
33
|
+
* dos de esquema que un flujo puede necesitar (bases, tablas y campos).
|
|
34
|
+
* Fuera a propósito: comentarios, crear tablas o campos, y los webhooks,
|
|
35
|
+
* que son del trigger.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ El orden de este array es el orden del desplegable.
|
|
38
|
+
*/
|
|
39
|
+
export const AIRTABLE_OPERATIONS = [
|
|
40
|
+
"listRecords",
|
|
41
|
+
"getRecord",
|
|
42
|
+
"createRecord",
|
|
43
|
+
"updateRecord",
|
|
44
|
+
"upsertRecord",
|
|
45
|
+
"deleteRecord",
|
|
46
|
+
"listBases",
|
|
47
|
+
"getBaseSchema",
|
|
48
|
+
];
|
|
49
|
+
export function isAirtableOperation(value) {
|
|
50
|
+
return (typeof value === "string" &&
|
|
51
|
+
AIRTABLE_OPERATIONS.includes(value));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Las que devuelven una COLECCIÓN sobre la que el nodo de abajo repite. Cada
|
|
55
|
+
* una itera SU campo (`iterateField`): `records`, `bases`, `tables`. La
|
|
56
|
+
* salida lleva `_meta.iterable: true`.
|
|
57
|
+
*/
|
|
58
|
+
export const AIRTABLE_ITERABLE_OPERATIONS = [
|
|
59
|
+
"listRecords",
|
|
60
|
+
"listBases",
|
|
61
|
+
"getBaseSchema",
|
|
62
|
+
];
|
|
63
|
+
const baseId = () => ({
|
|
64
|
+
name: "baseId",
|
|
65
|
+
label: "Base",
|
|
66
|
+
type: "base",
|
|
67
|
+
required: true,
|
|
68
|
+
description: "One of the bases the credential was granted access to.",
|
|
69
|
+
});
|
|
70
|
+
const tableId = () => ({
|
|
71
|
+
name: "tableId",
|
|
72
|
+
label: "Table",
|
|
73
|
+
type: "table",
|
|
74
|
+
required: true,
|
|
75
|
+
description: "A table in that base.",
|
|
76
|
+
});
|
|
77
|
+
const recordId = () => ({
|
|
78
|
+
name: "recordId",
|
|
79
|
+
label: "Record",
|
|
80
|
+
type: "template",
|
|
81
|
+
required: true,
|
|
82
|
+
placeholder: "{{payload.recordId}}",
|
|
83
|
+
description: "The record's ID (rec…), usually from an Airtable trigger or an earlier step.",
|
|
84
|
+
});
|
|
85
|
+
/* Los valores por nombre de campo. Cadenas con plantilla; el resto (números,
|
|
86
|
+
checkboxes, arrays de un multi-select o de linked records) como JSON. */
|
|
87
|
+
const fields = () => ({
|
|
88
|
+
name: "fields",
|
|
89
|
+
label: "Fields",
|
|
90
|
+
type: "fields",
|
|
91
|
+
description: "The values to write, by field name. Strings may use {{payload.x}}; select options, dates and linked records follow the field's type.",
|
|
92
|
+
});
|
|
93
|
+
/* `typecast` convierte "Sí" en un checkbox y "Done" en la opción de un
|
|
94
|
+
select aunque no exista todavía. Activado por defecto porque es lo que
|
|
95
|
+
uno espera al escribir desde un flujo; se puede apagar. */
|
|
96
|
+
const typecast = () => ({
|
|
97
|
+
name: "typecast",
|
|
98
|
+
label: "Convert values to the field's type",
|
|
99
|
+
type: "booleanSelect",
|
|
100
|
+
options: [
|
|
101
|
+
{ value: "true", label: "Yes — text becomes the right type, new select options are created" },
|
|
102
|
+
{ value: "false", label: "No — values must already match the field's type" },
|
|
103
|
+
],
|
|
104
|
+
default: "true",
|
|
105
|
+
advanced: true,
|
|
106
|
+
description: "Airtable's typecast: with Yes, \"Yes\" fills a checkbox and an unknown option is added to a select.",
|
|
107
|
+
});
|
|
108
|
+
export const AIRTABLE_OPERATION_SPECS = {
|
|
109
|
+
listRecords: {
|
|
110
|
+
label: "List records",
|
|
111
|
+
description: "Records of a table, optionally through a view and a filter formula. Comes back as the table shows them: fields by name.",
|
|
112
|
+
apiRoute: "GET /v0/{baseId}/{tableId}",
|
|
113
|
+
iterateField: "records",
|
|
114
|
+
params: [
|
|
115
|
+
baseId(),
|
|
116
|
+
tableId(),
|
|
117
|
+
{
|
|
118
|
+
name: "viewId",
|
|
119
|
+
label: "View",
|
|
120
|
+
type: "view",
|
|
121
|
+
description: "Only the records in this view, in the view's order. Empty = the whole table.",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "filterByFormula",
|
|
125
|
+
label: "Filter formula",
|
|
126
|
+
type: "textarea",
|
|
127
|
+
placeholder: '{Status} = "Done"',
|
|
128
|
+
description: "An Airtable formula; only records where it is true come back. Field names in braces. Supports {{payload.x}}.",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "fields",
|
|
132
|
+
label: "Only these fields",
|
|
133
|
+
type: "fieldList",
|
|
134
|
+
advanced: true,
|
|
135
|
+
description: "Fields to include in each record. Empty = every field.",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "sortField",
|
|
139
|
+
label: "Sort by",
|
|
140
|
+
type: "field",
|
|
141
|
+
advanced: true,
|
|
142
|
+
description: "A field to order by. Empty = the view's order, or the table's.",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "sortDirection",
|
|
146
|
+
label: "Direction",
|
|
147
|
+
type: "select",
|
|
148
|
+
options: [
|
|
149
|
+
{ value: "asc", label: "Ascending" },
|
|
150
|
+
{ value: "desc", label: "Descending" },
|
|
151
|
+
],
|
|
152
|
+
default: "asc",
|
|
153
|
+
advanced: true,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "maxRecords",
|
|
157
|
+
label: "Max records",
|
|
158
|
+
type: "number",
|
|
159
|
+
min: 1,
|
|
160
|
+
max: 1000,
|
|
161
|
+
default: 100,
|
|
162
|
+
advanced: true,
|
|
163
|
+
description: "How many to bring into the flow, at most (Airtable pages by 100; the node keeps asking). Default 100.",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
getRecord: {
|
|
168
|
+
label: "Get record",
|
|
169
|
+
description: "One record by its ID, with every field.",
|
|
170
|
+
apiRoute: "GET /v0/{baseId}/{tableId}/{recordId}",
|
|
171
|
+
params: [baseId(), tableId(), recordId()],
|
|
172
|
+
},
|
|
173
|
+
createRecord: {
|
|
174
|
+
label: "Create record",
|
|
175
|
+
description: "Adds a record with the values you give it. For many at once, put an array in Batch (Advanced) — Airtable takes 10 per call and the node paces it.",
|
|
176
|
+
apiRoute: "POST /v0/{baseId}/{tableId}",
|
|
177
|
+
params: [
|
|
178
|
+
baseId(),
|
|
179
|
+
tableId(),
|
|
180
|
+
fields(),
|
|
181
|
+
{
|
|
182
|
+
name: "records",
|
|
183
|
+
label: "Batch",
|
|
184
|
+
type: "json",
|
|
185
|
+
placeholder: "{{payload.rows}}",
|
|
186
|
+
advanced: true,
|
|
187
|
+
description: "An array of objects, each { \"Field\": value, … } — e.g. {{payload.rows}}. When set, Fields above is ignored.",
|
|
188
|
+
},
|
|
189
|
+
typecast(),
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
updateRecord: {
|
|
193
|
+
label: "Update record",
|
|
194
|
+
description: "Changes the fields you give it and leaves the rest as they are. For many at once, put an array in Batch (Advanced).",
|
|
195
|
+
apiRoute: "PATCH /v0/{baseId}/{tableId}",
|
|
196
|
+
params: [
|
|
197
|
+
baseId(),
|
|
198
|
+
tableId(),
|
|
199
|
+
recordId(),
|
|
200
|
+
fields(),
|
|
201
|
+
{
|
|
202
|
+
name: "records",
|
|
203
|
+
label: "Batch",
|
|
204
|
+
type: "json",
|
|
205
|
+
placeholder: "{{payload.rows}}",
|
|
206
|
+
advanced: true,
|
|
207
|
+
description: "An array of { \"id\": \"rec…\", \"fields\": { … } } objects. When set, Record and Fields above are ignored.",
|
|
208
|
+
},
|
|
209
|
+
typecast(),
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
upsertRecord: {
|
|
213
|
+
label: "Upsert record",
|
|
214
|
+
description: "Finds a record by the fields you merge on and updates it; creates it when there is none. Fails if more than one matches.",
|
|
215
|
+
apiRoute: "PATCH /v0/{baseId}/{tableId} (performUpsert)",
|
|
216
|
+
params: [
|
|
217
|
+
baseId(),
|
|
218
|
+
tableId(),
|
|
219
|
+
{
|
|
220
|
+
name: "mergeOn",
|
|
221
|
+
label: "Match on",
|
|
222
|
+
type: "fieldList",
|
|
223
|
+
required: true,
|
|
224
|
+
description: "One to three fields that identify the record — an email, an external ID. Text, number, select or date fields; not formulas.",
|
|
225
|
+
},
|
|
226
|
+
fields(),
|
|
227
|
+
{
|
|
228
|
+
name: "records",
|
|
229
|
+
label: "Batch",
|
|
230
|
+
type: "json",
|
|
231
|
+
placeholder: "{{payload.rows}}",
|
|
232
|
+
advanced: true,
|
|
233
|
+
description: "An array of objects, each { \"Field\": value, … }. When set, Fields above is ignored.",
|
|
234
|
+
},
|
|
235
|
+
typecast(),
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
deleteRecord: {
|
|
239
|
+
label: "Delete record",
|
|
240
|
+
description: "Deletes a record. For many at once, put their IDs in Batch (Advanced).",
|
|
241
|
+
apiRoute: "DELETE /v0/{baseId}/{tableId}",
|
|
242
|
+
params: [
|
|
243
|
+
baseId(),
|
|
244
|
+
tableId(),
|
|
245
|
+
recordId(),
|
|
246
|
+
{
|
|
247
|
+
name: "recordIds",
|
|
248
|
+
label: "Batch",
|
|
249
|
+
type: "template",
|
|
250
|
+
placeholder: "{{payload.ids}}",
|
|
251
|
+
advanced: true,
|
|
252
|
+
description: "Record IDs to delete — an array from the payload, or comma-separated. When set, Record above is ignored.",
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
listBases: {
|
|
257
|
+
label: "List bases",
|
|
258
|
+
description: "The bases this credential can reach, with their IDs and your permission level in each.",
|
|
259
|
+
apiRoute: "GET /v0/meta/bases",
|
|
260
|
+
iterateField: "bases",
|
|
261
|
+
params: [],
|
|
262
|
+
},
|
|
263
|
+
getBaseSchema: {
|
|
264
|
+
label: "Get base schema",
|
|
265
|
+
description: "The tables of a base with their fields (name, type, options) and views. What a flow reads before writing into a table it does not know.",
|
|
266
|
+
apiRoute: "GET /v0/meta/bases/{baseId}/tables",
|
|
267
|
+
iterateField: "tables",
|
|
268
|
+
params: [baseId()],
|
|
269
|
+
},
|
|
270
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El nodo de Calendly: lo que pasa alrededor de una reserva.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué existe
|
|
5
|
+
*
|
|
6
|
+
* [2026-09-15] Calendly expone una API v2 con OAuth 2.0 (PKCE + secret,
|
|
7
|
+
* refresh de un solo uso) y webhooks firmados. La reserva en sí la hace el
|
|
8
|
+
* invitado en Calendly; lo que HostWebhook necesita es leerla (el trigger la
|
|
9
|
+
* trae; este nodo la consulta), cancelarla, marcar el no-show, y las dos
|
|
10
|
+
* que cierran el círculo: un link de reserva que caduca al usarse y los
|
|
11
|
+
* huecos libres de un tipo de evento, para que un agente los ofrezca.
|
|
12
|
+
*
|
|
13
|
+
* ## Cómo se nombra todo
|
|
14
|
+
*
|
|
15
|
+
* Calendly identifica cada cosa por su URI completa
|
|
16
|
+
* (`https://api.calendly.com/scheduled_events/ABC…`), no por un id corto.
|
|
17
|
+
* Los parámetros que piden una la aceptan entera o sólo el uuid final; el
|
|
18
|
+
* servicio completa el prefijo. Los que piden «Mine / Whole organization»
|
|
19
|
+
* resuelven al `owner` y `organization` que la credencial guardó al
|
|
20
|
+
* conectar, así que el usuario no escribe URIs nunca.
|
|
21
|
+
*
|
|
22
|
+
* ## Por qué nueve
|
|
23
|
+
*
|
|
24
|
+
* Eventos (listar, uno, invitados, cancelar), no-show (poner y quitar),
|
|
25
|
+
* tipos de evento (listar), huecos libres y link de un solo uso. Fuera a
|
|
26
|
+
* propósito: crear tipos de evento, contactos, routing forms, recaps y toda
|
|
27
|
+
* la gestión de organización —nada de eso es «lo que pasa alrededor de una
|
|
28
|
+
* reserva». Los webhooks van en el trigger, no aquí.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ El orden de este array es el orden del desplegable.
|
|
31
|
+
*/
|
|
32
|
+
export declare const CALENDLY_OPERATIONS: readonly ["listEvents", "getEvent", "listInvitees", "cancelEvent", "markNoShow", "undoNoShow", "listEventTypes", "getAvailableTimes", "createSchedulingLink"];
|
|
33
|
+
export type CalendlyOperation = (typeof CALENDLY_OPERATIONS)[number];
|
|
34
|
+
export declare function isCalendlyOperation(value: unknown): value is CalendlyOperation;
|
|
35
|
+
/**
|
|
36
|
+
* Las que devuelven una COLECCIÓN sobre la que el nodo de abajo repite. Cada
|
|
37
|
+
* una itera SU campo (`iterateField` en la spec): `events`, `invitees`,
|
|
38
|
+
* `eventTypes`, `slots`. La salida lleva `_meta.iterable: true`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CALENDLY_ITERABLE_OPERATIONS: ReadonlyArray<CalendlyOperation>;
|
|
41
|
+
/**
|
|
42
|
+
* Tipos de parámetro. Los seis primeros son los de Apify. Los dos nuevos:
|
|
43
|
+
*
|
|
44
|
+
* - `datetime`: una fecha con hora, ISO 8601, en la zona horaria del
|
|
45
|
+
* usuario; el dashboard la pinta con el selector de Calendar y admite
|
|
46
|
+
* plantilla (`{{payload.startTime}}`).
|
|
47
|
+
* - `eventType`: un desplegable que lista los tipos de evento de la
|
|
48
|
+
* credencial (`GET /calendly-actions/event-types`); el valor es la URI del
|
|
49
|
+
* tipo. Admite plantilla para cuando viene del payload.
|
|
50
|
+
*/
|
|
51
|
+
export type CalendlyParamType = "template" | "textarea" | "json" | "select" | "number" | "booleanSelect" | "datetime" | "eventType";
|
|
52
|
+
export interface CalendlyParamSpec {
|
|
53
|
+
name: string;
|
|
54
|
+
label: string;
|
|
55
|
+
type: CalendlyParamType;
|
|
56
|
+
required?: boolean;
|
|
57
|
+
description?: string;
|
|
58
|
+
placeholder?: string;
|
|
59
|
+
default?: string | number;
|
|
60
|
+
options?: ReadonlyArray<{
|
|
61
|
+
value: string;
|
|
62
|
+
label: string;
|
|
63
|
+
}>;
|
|
64
|
+
min?: number;
|
|
65
|
+
max?: number;
|
|
66
|
+
advanced?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface CalendlyOperationSpec {
|
|
69
|
+
label: string;
|
|
70
|
+
description: string;
|
|
71
|
+
apiRoute: string;
|
|
72
|
+
params: CalendlyParamSpec[];
|
|
73
|
+
/** El campo de la salida que se itera; sólo en las iterables. */
|
|
74
|
+
iterateField?: string;
|
|
75
|
+
}
|
|
76
|
+
export declare const CALENDLY_OPERATION_SPECS: Record<CalendlyOperation, CalendlyOperationSpec>;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El nodo de Calendly: lo que pasa alrededor de una reserva.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué existe
|
|
5
|
+
*
|
|
6
|
+
* [2026-09-15] Calendly expone una API v2 con OAuth 2.0 (PKCE + secret,
|
|
7
|
+
* refresh de un solo uso) y webhooks firmados. La reserva en sí la hace el
|
|
8
|
+
* invitado en Calendly; lo que HostWebhook necesita es leerla (el trigger la
|
|
9
|
+
* trae; este nodo la consulta), cancelarla, marcar el no-show, y las dos
|
|
10
|
+
* que cierran el círculo: un link de reserva que caduca al usarse y los
|
|
11
|
+
* huecos libres de un tipo de evento, para que un agente los ofrezca.
|
|
12
|
+
*
|
|
13
|
+
* ## Cómo se nombra todo
|
|
14
|
+
*
|
|
15
|
+
* Calendly identifica cada cosa por su URI completa
|
|
16
|
+
* (`https://api.calendly.com/scheduled_events/ABC…`), no por un id corto.
|
|
17
|
+
* Los parámetros que piden una la aceptan entera o sólo el uuid final; el
|
|
18
|
+
* servicio completa el prefijo. Los que piden «Mine / Whole organization»
|
|
19
|
+
* resuelven al `owner` y `organization` que la credencial guardó al
|
|
20
|
+
* conectar, así que el usuario no escribe URIs nunca.
|
|
21
|
+
*
|
|
22
|
+
* ## Por qué nueve
|
|
23
|
+
*
|
|
24
|
+
* Eventos (listar, uno, invitados, cancelar), no-show (poner y quitar),
|
|
25
|
+
* tipos de evento (listar), huecos libres y link de un solo uso. Fuera a
|
|
26
|
+
* propósito: crear tipos de evento, contactos, routing forms, recaps y toda
|
|
27
|
+
* la gestión de organización —nada de eso es «lo que pasa alrededor de una
|
|
28
|
+
* reserva». Los webhooks van en el trigger, no aquí.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ El orden de este array es el orden del desplegable.
|
|
31
|
+
*/
|
|
32
|
+
export const CALENDLY_OPERATIONS = [
|
|
33
|
+
"listEvents",
|
|
34
|
+
"getEvent",
|
|
35
|
+
"listInvitees",
|
|
36
|
+
"cancelEvent",
|
|
37
|
+
"markNoShow",
|
|
38
|
+
"undoNoShow",
|
|
39
|
+
"listEventTypes",
|
|
40
|
+
"getAvailableTimes",
|
|
41
|
+
"createSchedulingLink",
|
|
42
|
+
];
|
|
43
|
+
export function isCalendlyOperation(value) {
|
|
44
|
+
return (typeof value === "string" &&
|
|
45
|
+
CALENDLY_OPERATIONS.includes(value));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Las que devuelven una COLECCIÓN sobre la que el nodo de abajo repite. Cada
|
|
49
|
+
* una itera SU campo (`iterateField` en la spec): `events`, `invitees`,
|
|
50
|
+
* `eventTypes`, `slots`. La salida lleva `_meta.iterable: true`.
|
|
51
|
+
*/
|
|
52
|
+
export const CALENDLY_ITERABLE_OPERATIONS = [
|
|
53
|
+
"listEvents",
|
|
54
|
+
"listInvitees",
|
|
55
|
+
"listEventTypes",
|
|
56
|
+
"getAvailableTimes",
|
|
57
|
+
];
|
|
58
|
+
/* «Mine» es el usuario que conectó; «Whole organization» exige ser admin u
|
|
59
|
+
owner en Calendly, y si no lo es la API contesta 403 y el nodo lo dice. */
|
|
60
|
+
const owner = () => ({
|
|
61
|
+
name: "owner",
|
|
62
|
+
label: "Whose",
|
|
63
|
+
type: "select",
|
|
64
|
+
options: [
|
|
65
|
+
{ value: "mine", label: "Mine — the connected user" },
|
|
66
|
+
{ value: "organization", label: "Whole organization (admins only)" },
|
|
67
|
+
],
|
|
68
|
+
default: "mine",
|
|
69
|
+
description: "Mine uses the account that connected the credential. Whole organization needs an organization admin or owner.",
|
|
70
|
+
});
|
|
71
|
+
/* Un evento se nombra por su URI o por el uuid del final; da igual. */
|
|
72
|
+
const eventUri = () => ({
|
|
73
|
+
name: "eventUri",
|
|
74
|
+
label: "Event",
|
|
75
|
+
type: "template",
|
|
76
|
+
required: true,
|
|
77
|
+
placeholder: "{{payload.event.uri}}",
|
|
78
|
+
description: "The scheduled event — its URI, or just the ID at the end of it. Usually {{payload.event.uri}} from a Calendly trigger.",
|
|
79
|
+
});
|
|
80
|
+
const eventType = () => ({
|
|
81
|
+
name: "eventType",
|
|
82
|
+
label: "Event type",
|
|
83
|
+
type: "eventType",
|
|
84
|
+
required: true,
|
|
85
|
+
description: "One of the event types on the connected account (e.g. “30 min intro call”).",
|
|
86
|
+
});
|
|
87
|
+
const pageLimit = (max, fallback) => ({
|
|
88
|
+
name: "limit",
|
|
89
|
+
label: "Max results",
|
|
90
|
+
type: "number",
|
|
91
|
+
min: 1,
|
|
92
|
+
max,
|
|
93
|
+
default: fallback,
|
|
94
|
+
advanced: true,
|
|
95
|
+
description: `How many to bring into the flow, at most. Default ${fallback}.`,
|
|
96
|
+
});
|
|
97
|
+
export const CALENDLY_OPERATION_SPECS = {
|
|
98
|
+
listEvents: {
|
|
99
|
+
label: "List events",
|
|
100
|
+
description: "Scheduled events in a time window — yours or the whole organization's. Each one comes with its event type, hosts and location; invitees are a separate step.",
|
|
101
|
+
apiRoute: "GET /scheduled_events",
|
|
102
|
+
iterateField: "events",
|
|
103
|
+
params: [
|
|
104
|
+
owner(),
|
|
105
|
+
{
|
|
106
|
+
name: "status",
|
|
107
|
+
label: "Status",
|
|
108
|
+
type: "select",
|
|
109
|
+
options: [
|
|
110
|
+
{ value: "active", label: "Active — upcoming and past, not canceled" },
|
|
111
|
+
{ value: "canceled", label: "Canceled" },
|
|
112
|
+
{ value: "all", label: "All" },
|
|
113
|
+
],
|
|
114
|
+
default: "active",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "minStartTime",
|
|
118
|
+
label: "Starting from",
|
|
119
|
+
type: "datetime",
|
|
120
|
+
placeholder: "{{payload.from}}",
|
|
121
|
+
description: "Only events that start at or after this moment. Empty = no lower bound.",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "maxStartTime",
|
|
125
|
+
label: "Starting before",
|
|
126
|
+
type: "datetime",
|
|
127
|
+
placeholder: "{{payload.to}}",
|
|
128
|
+
description: "Only events that start before this moment. Empty = no upper bound.",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "inviteeEmail",
|
|
132
|
+
label: "Invitee email",
|
|
133
|
+
type: "template",
|
|
134
|
+
placeholder: "{{payload.email}}",
|
|
135
|
+
advanced: true,
|
|
136
|
+
description: "Only events booked by this email. Empty = everyone.",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "sort",
|
|
140
|
+
label: "Order",
|
|
141
|
+
type: "select",
|
|
142
|
+
options: [
|
|
143
|
+
{ value: "start_time:asc", label: "Soonest first" },
|
|
144
|
+
{ value: "start_time:desc", label: "Latest first" },
|
|
145
|
+
],
|
|
146
|
+
default: "start_time:asc",
|
|
147
|
+
advanced: true,
|
|
148
|
+
},
|
|
149
|
+
pageLimit(500, 100),
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
getEvent: {
|
|
153
|
+
label: "Get event",
|
|
154
|
+
description: "One scheduled event by its URI: name, start and end, event type, hosts, location, and how many invitees it has.",
|
|
155
|
+
apiRoute: "GET /scheduled_events/{uuid}",
|
|
156
|
+
params: [eventUri()],
|
|
157
|
+
},
|
|
158
|
+
listInvitees: {
|
|
159
|
+
label: "List invitees",
|
|
160
|
+
description: "Who booked an event: name, email, timezone, their answers to the booking questions, and whether they canceled or rescheduled.",
|
|
161
|
+
apiRoute: "GET /scheduled_events/{uuid}/invitees",
|
|
162
|
+
iterateField: "invitees",
|
|
163
|
+
params: [
|
|
164
|
+
eventUri(),
|
|
165
|
+
{
|
|
166
|
+
name: "status",
|
|
167
|
+
label: "Status",
|
|
168
|
+
type: "select",
|
|
169
|
+
options: [
|
|
170
|
+
{ value: "active", label: "Active" },
|
|
171
|
+
{ value: "canceled", label: "Canceled" },
|
|
172
|
+
{ value: "all", label: "All" },
|
|
173
|
+
],
|
|
174
|
+
default: "all",
|
|
175
|
+
advanced: true,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "inviteeEmail",
|
|
179
|
+
label: "Invitee email",
|
|
180
|
+
type: "template",
|
|
181
|
+
placeholder: "{{payload.email}}",
|
|
182
|
+
advanced: true,
|
|
183
|
+
description: "Only the invitee with this email. Empty = all of them.",
|
|
184
|
+
},
|
|
185
|
+
pageLimit(100, 100),
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
cancelEvent: {
|
|
189
|
+
label: "Cancel event",
|
|
190
|
+
description: "Cancels a scheduled event and notifies its invitees, the same way the host would from Calendly.",
|
|
191
|
+
apiRoute: "POST /scheduled_events/{uuid}/cancellation",
|
|
192
|
+
params: [
|
|
193
|
+
eventUri(),
|
|
194
|
+
{
|
|
195
|
+
name: "reason",
|
|
196
|
+
label: "Reason",
|
|
197
|
+
type: "textarea",
|
|
198
|
+
placeholder: "Sorry — something came up. Please rebook with the link below.",
|
|
199
|
+
description: "Shown to the invitees in the cancellation email. Supports {{payload.x}}. Empty = no reason.",
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
markNoShow: {
|
|
204
|
+
label: "Mark invitee as no-show",
|
|
205
|
+
description: "Marks an invitee as a no-show on a past event. Returns the no-show record; keep its URI if you may need to undo it.",
|
|
206
|
+
apiRoute: "POST /invitee_no_shows",
|
|
207
|
+
params: [
|
|
208
|
+
{
|
|
209
|
+
name: "inviteeUri",
|
|
210
|
+
label: "Invitee",
|
|
211
|
+
type: "template",
|
|
212
|
+
required: true,
|
|
213
|
+
placeholder: "{{payload.invitee.uri}}",
|
|
214
|
+
description: "The invitee — its URI, from List invitees or a Calendly trigger.",
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
undoNoShow: {
|
|
219
|
+
label: "Undo no-show",
|
|
220
|
+
description: "Removes a no-show mark. Takes the URI that Mark invitee as no-show returned.",
|
|
221
|
+
apiRoute: "DELETE /invitee_no_shows/{uuid}",
|
|
222
|
+
params: [
|
|
223
|
+
{
|
|
224
|
+
name: "noShowUri",
|
|
225
|
+
label: "No-show",
|
|
226
|
+
type: "template",
|
|
227
|
+
required: true,
|
|
228
|
+
placeholder: "{{payload.uri}}",
|
|
229
|
+
description: "The no-show record to remove — its URI, or just the ID at the end.",
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
listEventTypes: {
|
|
234
|
+
label: "List event types",
|
|
235
|
+
description: "The event types you (or the organization) offer: name, duration, scheduling URL, and whether they are active. What you need before asking for available times or a link.",
|
|
236
|
+
apiRoute: "GET /event_types",
|
|
237
|
+
iterateField: "eventTypes",
|
|
238
|
+
params: [
|
|
239
|
+
owner(),
|
|
240
|
+
{
|
|
241
|
+
name: "activeOnly",
|
|
242
|
+
label: "Active only",
|
|
243
|
+
type: "booleanSelect",
|
|
244
|
+
options: [
|
|
245
|
+
{ value: "true", label: "Yes — only event types that can be booked" },
|
|
246
|
+
{ value: "false", label: "No — include hidden and inactive ones" },
|
|
247
|
+
],
|
|
248
|
+
default: "true",
|
|
249
|
+
advanced: true,
|
|
250
|
+
},
|
|
251
|
+
pageLimit(100, 100),
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
getAvailableTimes: {
|
|
255
|
+
label: "Get available times",
|
|
256
|
+
description: "The free slots of an event type in a window of up to 7 days, each with its start time and a scheduling URL that books exactly that slot. Made for an agent that offers times.",
|
|
257
|
+
apiRoute: "GET /event_type_available_times",
|
|
258
|
+
iterateField: "slots",
|
|
259
|
+
params: [
|
|
260
|
+
eventType(),
|
|
261
|
+
{
|
|
262
|
+
name: "startTime",
|
|
263
|
+
label: "From",
|
|
264
|
+
type: "datetime",
|
|
265
|
+
required: true,
|
|
266
|
+
placeholder: "{{payload.from}}",
|
|
267
|
+
description: "Start of the window. Must be in the future.",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "endTime",
|
|
271
|
+
label: "To",
|
|
272
|
+
type: "datetime",
|
|
273
|
+
required: true,
|
|
274
|
+
placeholder: "{{payload.to}}",
|
|
275
|
+
description: "End of the window — at most 7 days after From. A longer window is cut to 7 days.",
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
createSchedulingLink: {
|
|
280
|
+
label: "Create single-use scheduling link",
|
|
281
|
+
description: "A booking link for one event type that stops working after one booking. Send it to someone instead of your public link.",
|
|
282
|
+
apiRoute: "POST /scheduling_links",
|
|
283
|
+
params: [eventType()],
|
|
284
|
+
},
|
|
285
|
+
};
|
package/dist/esm/connections.js
CHANGED
|
@@ -75,6 +75,8 @@ export const NODE_CONNECTIONS = {
|
|
|
75
75
|
jiraAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
|
|
76
76
|
bucketAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
|
|
77
77
|
apifyAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
|
|
78
|
+
calendlyAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
|
|
79
|
+
airtableAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
|
|
78
80
|
googleContactsAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
|
|
79
81
|
googleAnalyticsAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
|
|
80
82
|
notionAction: { acceptsInputFrom: ACTION_INPUTS, canOutputTo: PROCESSING_OUTPUTS },
|
|
@@ -72,6 +72,12 @@ export declare const CREDENTIAL_TYPES: readonly [{
|
|
|
72
72
|
readonly type: "firecrawl";
|
|
73
73
|
}, {
|
|
74
74
|
readonly type: "apify";
|
|
75
|
+
}, {
|
|
76
|
+
readonly type: "calendly_oauth2";
|
|
77
|
+
}, {
|
|
78
|
+
readonly type: "calendly";
|
|
79
|
+
}, {
|
|
80
|
+
readonly type: "airtable_oauth2";
|
|
75
81
|
}, {
|
|
76
82
|
readonly type: "http_auth";
|
|
77
83
|
}, {
|
package/dist/esm/credentials.js
CHANGED
|
@@ -53,6 +53,26 @@ export const CREDENTIAL_TYPES = [
|
|
|
53
53
|
/* [2026-09-13] Apify no tiene OAuth para terceros: es un API token, como
|
|
54
54
|
Firecrawl. Lo lee el nodo `apifyAction`. */
|
|
55
55
|
{ type: 'apify' },
|
|
56
|
+
/* [2026-09-15] El OAuth de HostWebhook contra Calendly. Se anota ANTES de
|
|
57
|
+
escribir el servicio, por lo que cuenta el bloque de Mailchimp de abajo:
|
|
58
|
+
sin esta línea el `create` de la credencial muere en runtime con un enum
|
|
59
|
+
de Mongoose que TypeScript no ve. Lleva `_oauth2` como Mailchimp y
|
|
60
|
+
Shopify. Guarda, además de los tokens, el `owner` y la `organization`
|
|
61
|
+
(URIs) que el token endpoint devuelve: cada lista y cada webhook los
|
|
62
|
+
necesita. El refresh token es de UN solo uso: se sobreescribe en el
|
|
63
|
+
mismo write que lo gasta. */
|
|
64
|
+
{ type: 'calendly_oauth2' },
|
|
65
|
+
/* El personal access token de Calendly (Integrations → API & webhooks),
|
|
66
|
+
para quien no quiera OAuth. Va aparte de `calendly_oauth2` por lo mismo
|
|
67
|
+
que `discord_bot` va aparte de `discord_oauth`: lo que guardan es
|
|
68
|
+
distinto (aquí un secreto suyo que no caduca y ninguna URI hasta que se
|
|
69
|
+
valida con `GET /users/me`). */
|
|
70
|
+
{ type: 'calendly' },
|
|
71
|
+
/* El OAuth de HostWebhook contra Airtable. PKCE obligatorio, secret por
|
|
72
|
+
Basic auth, access token de 60 minutos y refresh de 60 días que rota
|
|
73
|
+
en cada uso. El usuario elige en la pantalla de Airtable QUÉ bases
|
|
74
|
+
concede; los desplegables sólo listan ésas. */
|
|
75
|
+
{ type: 'airtable_oauth2' },
|
|
56
76
|
{ type: 'http_auth' },
|
|
57
77
|
{ type: 'aws_s3' },
|
|
58
78
|
/* [2026-09-14] `memory_contextwindow` se retiró: el backend de memoria
|