@wfgraph/plugins 0.2.0 → 1.0.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/index.js CHANGED
@@ -1,938 +1,13 @@
1
- import { n as createAcuityClient, r as createAcuitySdk, t as callAcuity } from "./client-DkPUX0ps.js";
2
1
  import { n as getClerkApiErrorMessage, r as toClerkApiUser, t as createClerkBackendClient } from "./client-BEcFnbOB.js";
3
2
  import { t as describeLinearFailure } from "./errors-C4i6-DQE.js";
4
3
  import { i as sendResendEmail, t as describeResendFailure } from "./client-C2NIQxhb.js";
5
4
  import { n as describeSlackFailure, t as callSlack } from "./client-DDDCNLId.js";
6
- import { n as describeTwilioFailure, t as createTwilioMessage } from "./client-DJGI9f53.js";
5
+ import { n as describeTwilioFailure, t as createTwilioMessage } from "./client-LRwmIiPU.js";
7
6
  import { StepFailure, defineIntegration, readAs } from "@wfgraph/core/plugin";
8
- import { Effect, Result, Schema, SchemaTransformation } from "effect";
9
7
  import { omitBy } from "es-toolkit/object";
10
8
  import { isNil } from "es-toolkit/predicate";
9
+ import { Effect, Result, Schema, SchemaTransformation } from "effect";
11
10
  import { LinearClient } from "@linear/sdk";
12
- //#region src/acuity/payloads.ts
13
- /**
14
- * The wire shapes Acuity's API answers with.
15
- *
16
- * Modelled on what the API sends rather than on the SDK's types: the SDK casts the
17
- * response without validating it, so these describe a system's JSON directly. This
18
- * layer is owned by no single action -- every action whose output nests an
19
- * appointment, an appointment type, or an availability slot reads from here.
20
- */
21
- /**
22
- * A field the SDK types as `unknown`, kept rather than trimmed.
23
- *
24
- * The encode that puts a step's answer on the wire keeps only what the schema
25
- * admits, so a field left undeclared would not reach the run log at all. There is
26
- * nothing to say about this one's shape, which is what `Unknown` says; encoding it
27
- * still refuses a value that is not JSON, so a memoized step result stays safe.
28
- * The picker has no path to offer for it and drops it, which is correct: nothing
29
- * downstream can address a shape nobody described.
30
- */
31
- const opaqueJson = Schema.optional(Schema.Unknown);
32
- /**
33
- * A whole number the editor offers, described so the picker can show it.
34
- *
35
- * A bare `Schema.Number` describes itself as a number or one of the strings
36
- * "Infinity", "-Infinity" and "NaN", which the field reader cannot use, so a
37
- * numeric field written without the check drops out of the derived list.
38
- */
39
- function describedNumber(description) {
40
- return Schema.Number.annotate({ description }).check(Schema.isFinite());
41
- }
42
- /**
43
- * A field of a system's payload, as an output schema may describe it.
44
- *
45
- * `optionalKey(NullOr(...))` is the one spelling that survives everything a system
46
- * sends: an absent key, an explicit `null`, or the value. `optional(X)` refuses the
47
- * null and `NullishOr(X)` refuses the absent key, and either refusal fails the
48
- * encode and so the whole step. The picker reads the same `nullable` off this as it
49
- * did off the stricter spellings, so nothing is lost by tolerating both.
50
- */
51
- function externalField(field) {
52
- return Schema.optionalKey(Schema.NullOr(field));
53
- }
54
- /** The same tolerance for a whole number the picker offers. */
55
- function externalNumber(description) {
56
- return externalField(describedNumber(description));
57
- }
58
- /** The same tolerance for a string the picker offers. */
59
- function externalText(description) {
60
- return externalField(Schema.String.annotate({ description }));
61
- }
62
- /**
63
- * One answer a client gave on an intake form.
64
- *
65
- * Modelled on what the API sends rather than on the SDK's type: the SDK casts the
66
- * response without validating it, and it is missing a level. `value` is text, a
67
- * list of text, or nothing, which is one union too many for the picker to name a
68
- * path for; it is declared anyway, because the encode keeps only what the schema
69
- * admits and an answer nobody can address by path is still an answer somebody reads
70
- * in the run log.
71
- */
72
- const formAnswerSchema = Schema.Struct({
73
- id: externalNumber("Answer ID"),
74
- fieldID: externalNumber("Form field ID"),
75
- name: externalText("Question the client answered"),
76
- value: Schema.optionalKey(Schema.NullOr(Schema.Union([Schema.String, Schema.mutable(Schema.Array(Schema.String))])))
77
- });
78
- /**
79
- * One intake form on an appointment, holding the answers given on it.
80
- *
81
- * The nesting is what the wire has and the SDK's type does not: `forms` is a list
82
- * of forms, each carrying its own `values` list of answers. A schema describing
83
- * those answers one level up fails its encode, and so fails the step, on every
84
- * appointment that has an intake form.
85
- */
86
- const appointmentFormSchema = Schema.Struct({
87
- id: externalNumber("Form ID"),
88
- name: externalText("Form name"),
89
- values: Schema.optionalKey(Schema.NullOr(Schema.mutable(Schema.Array(formAnswerSchema)).annotate({ description: "Answers given on this form" })))
90
- });
91
- /**
92
- * Acuity's appointment, as the API sends it.
93
- *
94
- * Two fields are required, because the actions read them to answer with and a
95
- * payload without them is not an appointment: everything else is an `externalField`.
96
- * That is deliberate rather than lazy. This describes somebody else's JSON, the SDK
97
- * validates none of it, and a field this schema insists on that a real payload
98
- * omits fails the encode and so fails the step.
99
- */
100
- const appointmentSchema = Schema.Struct({
101
- id: describedNumber("Appointment ID"),
102
- datetime: Schema.String.annotate({ description: "Appointment start, ISO 8601 with offset" }),
103
- firstName: externalText("Client first name"),
104
- lastName: externalText("Client last name"),
105
- email: externalText("Client email address"),
106
- phone: externalText("Client phone number"),
107
- date: externalText("Appointment date, as Acuity writes it for people"),
108
- endDate: externalText("Appointment end date, as Acuity writes it for people"),
109
- time: externalText("Appointment start time"),
110
- endTime: externalText("Appointment end time"),
111
- duration: externalText("Duration in minutes"),
112
- timezone: externalText("The appointment's IANA timezone"),
113
- calendarTimezone: externalText("IANA timezone of the calendar that owns it"),
114
- type: externalText("Appointment type name"),
115
- appointmentTypeID: externalNumber("Appointment type ID"),
116
- calendar: externalText("Calendar name"),
117
- calendarID: externalNumber("Calendar ID"),
118
- price: Schema.optionalKey(Schema.NullOr(Schema.Union([Schema.String, Schema.Finite]))),
119
- paid: externalText("Whether the appointment is paid"),
120
- notes: externalText("Appointment notes"),
121
- forms: Schema.optionalKey(Schema.NullOr(Schema.mutable(Schema.Array(appointmentFormSchema)).annotate({ description: "Intake forms on the appointment" }))),
122
- noShow: externalField(Schema.Boolean.annotate({ description: "Marked as a no-show" })),
123
- canceled: externalField(Schema.Boolean.annotate({ description: "Whether the appointment is canceled" })),
124
- certificate: opaqueJson,
125
- package: opaqueJson,
126
- scheduledBy: externalText("Who scheduled it, or null for a client not logged in")
127
- });
128
- /**
129
- * Acuity's appointment type, as the API sends it.
130
- *
131
- * Same rule as the appointment above: `id` is what identifies one, and everything
132
- * else tolerates an absent key and an explicit null, because this describes a
133
- * system's JSON. `price` arrives as a string on some types and a number on others,
134
- * which the picker has no single type for and drops; it is declared so that the
135
- * encode keeps it.
136
- */
137
- const appointmentTypeSchema = Schema.Struct({
138
- id: describedNumber("Appointment type ID"),
139
- name: externalText("Appointment type name"),
140
- active: externalField(Schema.Boolean.annotate({ description: "Whether the type is bookable" })),
141
- description: externalText("Appointment type description"),
142
- duration: externalNumber("Duration in minutes"),
143
- category: externalText("Category name"),
144
- color: externalText("Calendar colour"),
145
- private: externalField(Schema.Boolean.annotate({ description: "Whether the type is hidden from the public scheduler" })),
146
- type: externalField(Schema.Literals([
147
- "service",
148
- "class",
149
- "series"
150
- ]).annotate({ description: "Kind of type: service, class, or series" })),
151
- classSize: externalNumber("Seats in a class, if it is one"),
152
- paddingAfter: externalNumber("Minutes of padding after the appointment"),
153
- paddingBefore: externalNumber("Minutes of padding before the appointment"),
154
- calendarIDs: Schema.optionalKey(Schema.NullOr(Schema.mutable(Schema.Array(Schema.Finite)).annotate({ description: "Calendars offering this type" }))),
155
- price: Schema.optionalKey(Schema.NullOr(Schema.Union([Schema.String, Schema.Finite])))
156
- });
157
- const availabilityDateSchema = Schema.Struct({ date: Schema.String.annotate({ description: "Available date, YYYY-MM-DD" }) });
158
- const availabilityTimeSlotSchema = Schema.Struct({ time: Schema.String.annotate({ description: "Bookable slot, ISO 8601 with offset" }) });
159
- //#endregion
160
- //#region src/acuity/shared.ts
161
- /**
162
- * Turning what a config field holds into what Acuity's API takes.
163
- *
164
- * Acuity wants whole numbers, real booleans and a list of form answers where
165
- * the editor gives a step text, so each of these reads one field and says, in
166
- * the words the run log shows, what is wrong with it. They answer an `Effect`,
167
- * so a step names the field and moves on -- `yield*` is the whole of the
168
- * handling, where a result object needed a check and an early return after
169
- * every single one.
170
- *
171
- * The messages are the reason the reading stays here rather than moving into
172
- * the input schemas: a decode that fails inside `defineStep` reports itself as
173
- * an invalid configuration for the action, and what an author needs to read is
174
- * which box holds what. The two schema declarations at the top are the other
175
- * half of the same boundary -- what an input schema says a config field may
176
- * hold, before one of these parsers reads it.
177
- */
178
- /** Every config field arrives as text, so this is what most of them look like. */
179
- const optionalText = Schema.optionalKey(Schema.String);
180
- /**
181
- * A number a config field carries.
182
- *
183
- * A `number` config field may be stored as a number and a template resolves to
184
- * text, so both arrive and the step parses whichever it got.
185
- */
186
- const optionalNumeric = Schema.optionalKey(Schema.Union([Schema.String, Schema.Finite]));
187
- /** A config value as text, or nothing when the field was left empty. */
188
- function normalizeRawValue(value) {
189
- if (value === void 0) return;
190
- if (typeof value === "number") return String(value);
191
- const trimmed = value.trim();
192
- return trimmed.length > 0 ? trimmed : void 0;
193
- }
194
- function positiveIntegerOr(normalized, fieldLabel) {
195
- const parsed = Number(normalized);
196
- return Number.isInteger(parsed) && parsed > 0 ? Effect.succeed(parsed) : Effect.fail(new StepFailure({ message: `${fieldLabel} must be a positive integer.` }));
197
- }
198
- function requiredInteger(value, fieldLabel) {
199
- const normalized = normalizeRawValue(value);
200
- return normalized ? positiveIntegerOr(normalized, fieldLabel) : Effect.fail(new StepFailure({ message: `${fieldLabel} is required.` }));
201
- }
202
- function optionalInteger(value, fieldLabel) {
203
- const normalized = normalizeRawValue(value);
204
- return normalized ? positiveIntegerOr(normalized, fieldLabel) : Effect.succeed(void 0);
205
- }
206
- /**
207
- * A select whose options are "", "true" and "false", read as the boolean Acuity
208
- * takes. An empty choice is the absent one, which is how a step says "leave
209
- * Acuity's own default alone".
210
- */
211
- function optionalBoolean(value, fieldLabel) {
212
- if (value === void 0 || value === "") return Effect.succeed(void 0);
213
- const normalized = value.trim().toLowerCase();
214
- if (normalized === "true") return Effect.succeed(true);
215
- if (normalized === "false") return Effect.succeed(false);
216
- return Effect.fail(new StepFailure({ message: `${fieldLabel} must be true, false, or empty.` }));
217
- }
218
- function optionalIntegerList(value, fieldLabel) {
219
- const normalized = normalizeRawValue(value);
220
- if (!normalized) return Effect.succeed(void 0);
221
- const entries = normalized.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
222
- if (entries.length === 0) return Effect.succeed(void 0);
223
- const parsed = entries.map((entry) => Number(entry));
224
- return parsed.some((entry) => !Number.isInteger(entry) || entry <= 0) ? Effect.fail(new StepFailure({ message: `${fieldLabel} must contain only positive integers (comma separated).` })) : Effect.succeed(parsed);
225
- }
226
- /**
227
- * Acuity takes the answers to a booking form's custom questions as fieldID/value
228
- * pairs. Workflow authors type those pairs as a JSON string into the node config,
229
- * so this schema is the boundary between that text and the Acuity client.
230
- *
231
- * `errors: "all"` because what the decode says about the text is what the author
232
- * reads back in the step's failure: naming one mistake at a time would send them
233
- * round the loop once per mistake.
234
- */
235
- const acuityCustomFieldsSchema = Schema.mutable(Schema.Array(Schema.Struct({
236
- fieldID: Schema.Finite.check(Schema.isInt(), Schema.isGreaterThan(0)),
237
- value: Schema.Union([Schema.String, Schema.mutable(Schema.Array(Schema.String))])
238
- })));
239
- const decodeCustomFields = Schema.decodeUnknownResult(acuityCustomFieldsSchema, { errors: "all" });
240
- function optionalCustomFields(value) {
241
- const normalized = normalizeRawValue(value);
242
- if (!normalized) return Effect.succeed(void 0);
243
- let parsed;
244
- try {
245
- parsed = JSON.parse(normalized);
246
- } catch {
247
- return Effect.fail(new StepFailure({ message: "Custom Fields JSON must be valid JSON in the format [{\"fieldID\":1234,\"value\":\"text\"}]." }));
248
- }
249
- const result = decodeCustomFields(parsed);
250
- return Result.isFailure(result) ? Effect.fail(new StepFailure({ message: `Custom Fields JSON must be an array of objects with numeric fieldID and value (string or string[]). ${result.failure.message}` })) : Effect.succeed(result.success);
251
- }
252
- //#endregion
253
- //#region src/acuity/index.ts
254
- const acuityCredentialFields = {
255
- ACUITY_USER_ID: {
256
- label: "User ID",
257
- type: "text",
258
- placeholder: "12345678",
259
- helpText: "Your Acuity User ID used as Basic auth username."
260
- },
261
- ACUITY_API_KEY: {
262
- label: "API Key",
263
- type: "password",
264
- placeholder: "••••••••",
265
- helpText: "Your Acuity API key used as Basic auth password."
266
- }
267
- };
268
- /** What a tri-state Acuity flag offers: leave it alone, or say yes or no. */
269
- const yesNoSelect = [
270
- {
271
- value: "",
272
- label: "Default"
273
- },
274
- {
275
- value: "true",
276
- label: "Yes"
277
- },
278
- {
279
- value: "false",
280
- label: "No"
281
- }
282
- ];
283
- /**
284
- * The two flags every mutating Acuity action takes, written once.
285
- *
286
- * `satisfies` with the keys spelled out is what keeps each `key` a literal type:
287
- * an action checks a field's key against its input schema, and a widened `string`
288
- * names no key at all. All three actions that use this group declare both.
289
- */
290
- const mutationFlagsGroup = {
291
- type: "group",
292
- label: "Mutation Flags",
293
- fields: [{
294
- key: "admin",
295
- label: "Run as Admin",
296
- type: "select",
297
- defaultValue: "",
298
- options: yesNoSelect
299
- }, {
300
- key: "noEmail",
301
- label: "Suppress Acuity Emails",
302
- type: "select",
303
- defaultValue: "",
304
- options: yesNoSelect
305
- }]
306
- };
307
- const listAppointmentTypesInput = Schema.Struct({});
308
- const listAppointmentTypesOutput = Schema.Struct({
309
- appointmentTypes: Schema.mutable(Schema.Array(appointmentTypeSchema)).annotate({ description: "Array of appointment types" }),
310
- count: describedNumber("Number of appointment types returned")
311
- });
312
- const listAppointmentsInput = Schema.Struct({
313
- appointmentTypeId: optionalText,
314
- calendarId: optionalText,
315
- minDate: optionalText,
316
- maxDate: optionalText,
317
- timezone: optionalText,
318
- email: optionalText,
319
- phone: optionalText,
320
- canceled: optionalText,
321
- showAll: optionalText,
322
- limit: optionalNumeric,
323
- page: optionalNumeric
324
- });
325
- const listAppointmentsOutput = Schema.Struct({
326
- appointments: Schema.mutable(Schema.Array(appointmentSchema)).annotate({ description: "Array of appointments" }),
327
- count: describedNumber("Number of appointments returned")
328
- });
329
- const getAppointmentInput = Schema.Struct({
330
- appointmentId: Schema.String,
331
- pastFormAnswers: optionalText
332
- });
333
- const getAppointmentOutput = Schema.Struct({
334
- appointment: appointmentSchema.annotate({ description: "The appointment details" }),
335
- id: describedNumber("Appointment ID"),
336
- datetime: Schema.String.annotate({ description: "Appointment datetime" })
337
- });
338
- const getAvailabilityDatesInput = Schema.Struct({
339
- month: Schema.String,
340
- appointmentTypeId: Schema.String,
341
- calendarId: optionalText,
342
- timezone: optionalText
343
- });
344
- const getAvailabilityDatesOutput = Schema.Struct({
345
- dates: Schema.mutable(Schema.Array(availabilityDateSchema)).annotate({ description: "Available dates" }),
346
- count: describedNumber("Number of dates returned")
347
- });
348
- const getAvailabilityTimesInput = Schema.Struct({
349
- date: Schema.String,
350
- appointmentTypeId: Schema.String,
351
- calendarId: optionalText,
352
- timezone: optionalText,
353
- /** Comma-separated, which is how a single text field carries a list. */
354
- ignoreAppointmentIds: optionalText
355
- });
356
- const getAvailabilityTimesOutput = Schema.Struct({
357
- slots: Schema.mutable(Schema.Array(availabilityTimeSlotSchema)).annotate({ description: "Available time slots" }),
358
- count: describedNumber("Number of slots returned")
359
- });
360
- const createAppointmentInput = Schema.Struct({
361
- datetime: Schema.String,
362
- appointmentTypeId: Schema.String,
363
- firstName: Schema.String,
364
- lastName: Schema.String,
365
- email: Schema.String,
366
- phone: Schema.String,
367
- calendarId: optionalText,
368
- notes: optionalText,
369
- smsOptIn: optionalText,
370
- /** JSON the workflow author typed, parsed by the step. */
371
- customFieldsJson: optionalText,
372
- admin: optionalText,
373
- noEmail: optionalText
374
- });
375
- const createAppointmentOutput = Schema.Struct({
376
- appointment: appointmentSchema.annotate({ description: "Created appointment payload" }),
377
- id: describedNumber("Created appointment ID"),
378
- datetime: Schema.String.annotate({ description: "Created appointment datetime" })
379
- });
380
- const rescheduleAppointmentInput = Schema.Struct({
381
- appointmentId: Schema.String,
382
- datetime: Schema.String,
383
- calendarId: optionalText,
384
- admin: optionalText,
385
- noEmail: optionalText
386
- });
387
- const rescheduleAppointmentOutput = Schema.Struct({
388
- appointment: appointmentSchema.annotate({ description: "Rescheduled appointment payload" }),
389
- id: describedNumber("Appointment ID"),
390
- datetime: Schema.String.annotate({ description: "New appointment datetime" })
391
- });
392
- const cancelAppointmentInput = Schema.Struct({
393
- appointmentId: Schema.String,
394
- cancelNote: optionalText,
395
- noShow: optionalText,
396
- admin: optionalText,
397
- noEmail: optionalText
398
- });
399
- const cancelAppointmentOutput = Schema.Struct({
400
- appointment: appointmentSchema.annotate({ description: "Canceled appointment payload" }),
401
- id: describedNumber("Canceled appointment ID"),
402
- canceled: Schema.optional(Schema.Boolean.annotate({ description: "Cancellation flag" }))
403
- });
404
- function createAcuity(createSdk) {
405
- return defineIntegration({
406
- type: "acuity",
407
- label: "Acuity",
408
- description: "Manage appointments and availability in Acuity Scheduling",
409
- credentials: acuityCredentialFields,
410
- test: async () => {
411
- const { testAcuity } = await import("./test-DzstoPiR.js");
412
- return (creds) => testAcuity(creds, createSdk);
413
- },
414
- actions: {
415
- "list-appointment-types": {
416
- label: "List Appointment Types",
417
- description: "Fetch appointment types configured in Acuity",
418
- input: listAppointmentTypesInput,
419
- output: listAppointmentTypesOutput,
420
- configFields: [],
421
- /** The action takes no configuration, so the whole of it is the read. */
422
- handler: Effect.fn(function* (bag) {
423
- const client = yield* createAcuityClient(bag, createSdk);
424
- const appointmentTypes = yield* bag.step.run("list-types", callAcuity("Failed to list appointment types.", () => client.appointments.types()));
425
- return {
426
- appointmentTypes,
427
- count: appointmentTypes.length
428
- };
429
- })
430
- },
431
- "list-appointments": {
432
- label: "List Appointments",
433
- description: "List appointments with optional filters",
434
- input: listAppointmentsInput,
435
- output: listAppointmentsOutput,
436
- configFields: [
437
- {
438
- key: "appointmentTypeId",
439
- label: "Appointment Type ID",
440
- type: "template-input",
441
- placeholder: "12345"
442
- },
443
- {
444
- key: "calendarId",
445
- label: "Calendar ID",
446
- type: "template-input",
447
- placeholder: "67890"
448
- },
449
- {
450
- key: "minDate",
451
- label: "Min Date (YYYY-MM-DD)",
452
- type: "template-input",
453
- placeholder: "2026-03-01"
454
- },
455
- {
456
- key: "maxDate",
457
- label: "Max Date (YYYY-MM-DD)",
458
- type: "template-input",
459
- placeholder: "2026-03-31"
460
- },
461
- {
462
- key: "timezone",
463
- label: "Timezone",
464
- type: "template-input",
465
- placeholder: "America/New_York"
466
- },
467
- {
468
- type: "group",
469
- label: "Additional Filters",
470
- fields: [
471
- {
472
- key: "email",
473
- label: "Client Email",
474
- type: "template-input",
475
- placeholder: "person@example.com"
476
- },
477
- {
478
- key: "phone",
479
- label: "Client Phone",
480
- type: "template-input",
481
- placeholder: "+15551234567"
482
- },
483
- {
484
- key: "canceled",
485
- label: "Only Canceled",
486
- type: "select",
487
- defaultValue: "",
488
- options: [
489
- {
490
- value: "",
491
- label: "No filter"
492
- },
493
- {
494
- value: "true",
495
- label: "Yes"
496
- },
497
- {
498
- value: "false",
499
- label: "No"
500
- }
501
- ]
502
- },
503
- {
504
- key: "showAll",
505
- label: "Include Inactive",
506
- type: "select",
507
- defaultValue: "",
508
- options: yesNoSelect
509
- },
510
- {
511
- key: "limit",
512
- label: "Limit",
513
- type: "number",
514
- min: 1,
515
- defaultValue: "50"
516
- },
517
- {
518
- key: "page",
519
- label: "Page",
520
- type: "number",
521
- min: 1,
522
- defaultValue: "1"
523
- }
524
- ]
525
- }
526
- ],
527
- /**
528
- * What this step decides is which filters the config asked for, and each
529
- * field says for itself what is wrong with it.
530
- */
531
- handler: Effect.fn(function* (bag) {
532
- const { input } = bag;
533
- const client = yield* createAcuityClient(bag, createSdk);
534
- const appointmentTypeID = yield* optionalInteger(input.appointmentTypeId, "Appointment Type ID");
535
- const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
536
- const limit = yield* optionalInteger(input.limit, "Limit");
537
- const page = yield* optionalInteger(input.page, "Page");
538
- const canceled = yield* optionalBoolean(input.canceled, "Only Canceled");
539
- const showall = yield* optionalBoolean(input.showAll, "Include Inactive");
540
- const params = {
541
- appointmentTypeID,
542
- calendarID,
543
- minDate: input.minDate,
544
- maxDate: input.maxDate,
545
- timezone: input.timezone,
546
- email: input.email,
547
- phone: input.phone,
548
- canceled,
549
- showall,
550
- limit,
551
- page
552
- };
553
- const appointments = yield* bag.step.run("list-appointments", callAcuity("Failed to list appointments.", () => client.appointments.list(params)));
554
- return {
555
- appointments,
556
- count: appointments.length
557
- };
558
- })
559
- },
560
- "get-appointment": {
561
- label: "Get Appointment",
562
- description: "Fetch one appointment by ID",
563
- input: getAppointmentInput,
564
- output: getAppointmentOutput,
565
- configFields: [{
566
- key: "appointmentId",
567
- label: "Appointment ID",
568
- type: "template-input",
569
- placeholder: "123456789",
570
- required: true
571
- }, {
572
- key: "pastFormAnswers",
573
- label: "Include Past Form Answers",
574
- type: "select",
575
- defaultValue: "false",
576
- options: [{
577
- value: "false",
578
- label: "No"
579
- }, {
580
- value: "true",
581
- label: "Yes"
582
- }]
583
- }],
584
- handler: Effect.fn(function* (bag) {
585
- const { input } = bag;
586
- const client = yield* createAcuityClient(bag, createSdk);
587
- const appointmentId = yield* requiredInteger(input.appointmentId, "Appointment ID");
588
- const pastFormAnswers = yield* optionalBoolean(input.pastFormAnswers, "Include Past Form Answers");
589
- const appointment = yield* bag.step.run("get-appointment", callAcuity("Failed to fetch appointment.", () => client.appointments.get(appointmentId, { pastFormAnswers })));
590
- return {
591
- appointment,
592
- id: appointment.id,
593
- datetime: appointment.datetime
594
- };
595
- })
596
- },
597
- "get-availability-dates": {
598
- label: "Get Availability Dates",
599
- description: "List dates that still have available slots",
600
- input: getAvailabilityDatesInput,
601
- output: getAvailabilityDatesOutput,
602
- configFields: [
603
- {
604
- key: "month",
605
- label: "Month (YYYY-MM)",
606
- type: "template-input",
607
- placeholder: "2026-03",
608
- required: true
609
- },
610
- {
611
- key: "appointmentTypeId",
612
- label: "Appointment Type ID",
613
- type: "template-input",
614
- placeholder: "12345",
615
- required: true
616
- },
617
- {
618
- key: "calendarId",
619
- label: "Calendar ID",
620
- type: "template-input",
621
- placeholder: "67890"
622
- },
623
- {
624
- key: "timezone",
625
- label: "Timezone",
626
- type: "template-input",
627
- placeholder: "America/New_York"
628
- }
629
- ],
630
- handler: Effect.fn(function* (bag) {
631
- const { input } = bag;
632
- const client = yield* createAcuityClient(bag, createSdk);
633
- const appointmentTypeID = yield* requiredInteger(input.appointmentTypeId, "Appointment Type ID");
634
- const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
635
- if (!input.month.trim()) return yield* new StepFailure({ message: "Month is required and must use YYYY-MM format." });
636
- const params = {
637
- month: input.month,
638
- appointmentTypeID,
639
- calendarID,
640
- timezone: input.timezone
641
- };
642
- const dates = yield* bag.step.run("get-dates", callAcuity("Failed to fetch availability dates.", () => client.availability.dates(params)));
643
- return {
644
- dates,
645
- count: dates.length
646
- };
647
- })
648
- },
649
- "get-availability-times": {
650
- label: "Get Availability Times",
651
- description: "List available time slots for a date",
652
- input: getAvailabilityTimesInput,
653
- output: getAvailabilityTimesOutput,
654
- configFields: [
655
- {
656
- key: "date",
657
- label: "Date (YYYY-MM-DD)",
658
- type: "template-input",
659
- placeholder: "2026-03-15",
660
- required: true
661
- },
662
- {
663
- key: "appointmentTypeId",
664
- label: "Appointment Type ID",
665
- type: "template-input",
666
- placeholder: "12345",
667
- required: true
668
- },
669
- {
670
- key: "calendarId",
671
- label: "Calendar ID",
672
- type: "template-input",
673
- placeholder: "67890"
674
- },
675
- {
676
- key: "timezone",
677
- label: "Timezone",
678
- type: "template-input",
679
- placeholder: "America/New_York"
680
- },
681
- {
682
- key: "ignoreAppointmentIds",
683
- label: "Ignore Appointment IDs (comma separated)",
684
- type: "template-input",
685
- placeholder: "111,222"
686
- }
687
- ],
688
- handler: Effect.fn(function* (bag) {
689
- const { input } = bag;
690
- const client = yield* createAcuityClient(bag, createSdk);
691
- const appointmentTypeID = yield* requiredInteger(input.appointmentTypeId, "Appointment Type ID");
692
- const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
693
- const ignoreAppointmentIDs = yield* optionalIntegerList(input.ignoreAppointmentIds, "Ignore Appointment IDs");
694
- if (!input.date.trim()) return yield* new StepFailure({ message: "Date is required and must use YYYY-MM-DD format." });
695
- const params = {
696
- date: input.date,
697
- appointmentTypeID,
698
- calendarID,
699
- timezone: input.timezone,
700
- ignoreAppointmentIDs
701
- };
702
- const slots = yield* bag.step.run("get-times", callAcuity("Failed to fetch availability times.", () => client.availability.times(params)));
703
- return {
704
- slots,
705
- count: slots.length
706
- };
707
- })
708
- },
709
- "create-appointment": {
710
- label: "Create Appointment",
711
- description: "Book a new appointment in Acuity",
712
- input: createAppointmentInput,
713
- output: createAppointmentOutput,
714
- configFields: [
715
- {
716
- key: "datetime",
717
- label: "Datetime (ISO 8601)",
718
- type: "template-input",
719
- placeholder: "2026-03-15T15:00:00-04:00",
720
- required: true
721
- },
722
- {
723
- key: "appointmentTypeId",
724
- label: "Appointment Type ID",
725
- type: "template-input",
726
- placeholder: "12345",
727
- required: true
728
- },
729
- {
730
- key: "firstName",
731
- label: "First Name",
732
- type: "template-input",
733
- placeholder: "Alice",
734
- required: true
735
- },
736
- {
737
- key: "lastName",
738
- label: "Last Name",
739
- type: "template-input",
740
- placeholder: "Johnson",
741
- required: true
742
- },
743
- {
744
- key: "email",
745
- label: "Email",
746
- type: "template-input",
747
- placeholder: "alice@example.com",
748
- required: true
749
- },
750
- {
751
- key: "phone",
752
- label: "Phone",
753
- type: "template-input",
754
- placeholder: "+15551234567",
755
- required: true
756
- },
757
- {
758
- type: "group",
759
- label: "Optional Appointment Fields",
760
- fields: [
761
- {
762
- key: "calendarId",
763
- label: "Calendar ID",
764
- type: "template-input",
765
- placeholder: "67890"
766
- },
767
- {
768
- key: "notes",
769
- label: "Notes",
770
- type: "template-textarea",
771
- placeholder: "Optional internal notes",
772
- rows: 3
773
- },
774
- {
775
- key: "smsOptIn",
776
- label: "SMS Opt-In",
777
- type: "select",
778
- defaultValue: "",
779
- options: yesNoSelect
780
- },
781
- {
782
- key: "customFieldsJson",
783
- label: "Custom Fields JSON",
784
- type: "template-textarea",
785
- rows: 5,
786
- placeholder: "[{\"fieldID\":1234,\"value\":\"Some answer\"},{\"fieldID\":5678,\"value\":[\"A\",\"B\"]}]"
787
- }
788
- ]
789
- },
790
- mutationFlagsGroup
791
- ],
792
- /**
793
- * What this step decides is what a booking looks like, and each field says
794
- * for itself what is wrong with it.
795
- */
796
- handler: Effect.fn(function* (bag) {
797
- const { input } = bag;
798
- const client = yield* createAcuityClient(bag, createSdk);
799
- const appointmentTypeID = yield* requiredInteger(input.appointmentTypeId, "Appointment Type ID");
800
- const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
801
- const smsOptIn = yield* optionalBoolean(input.smsOptIn, "SMS Opt-In");
802
- const admin = yield* optionalBoolean(input.admin, "Run as Admin");
803
- const noEmail = yield* optionalBoolean(input.noEmail, "Suppress Acuity Emails");
804
- const fields = yield* optionalCustomFields(input.customFieldsJson);
805
- if (!input.datetime.trim()) return yield* new StepFailure({ message: "Datetime is required (ISO 8601 format)." });
806
- if (!(input.firstName.trim() && input.lastName.trim())) return yield* new StepFailure({ message: "First Name and Last Name are required." });
807
- if (!(input.email.trim() && input.phone.trim())) return yield* new StepFailure({ message: "Email and Phone are required." });
808
- const payload = {
809
- datetime: input.datetime,
810
- appointmentTypeID,
811
- firstName: input.firstName,
812
- lastName: input.lastName,
813
- email: input.email,
814
- phone: input.phone,
815
- calendarID,
816
- notes: input.notes,
817
- smsOptIn,
818
- fields
819
- };
820
- const appointment = yield* bag.step.run("create-appointment", callAcuity("Failed to create appointment.", () => client.appointments.create(payload, {
821
- admin,
822
- noEmail
823
- })));
824
- return {
825
- appointment,
826
- id: appointment.id,
827
- datetime: appointment.datetime
828
- };
829
- })
830
- },
831
- "reschedule-appointment": {
832
- label: "Reschedule Appointment",
833
- description: "Move an appointment to a new datetime",
834
- input: rescheduleAppointmentInput,
835
- output: rescheduleAppointmentOutput,
836
- configFields: [
837
- {
838
- key: "appointmentId",
839
- label: "Appointment ID",
840
- type: "template-input",
841
- placeholder: "123456789",
842
- required: true
843
- },
844
- {
845
- key: "datetime",
846
- label: "New Datetime (ISO 8601)",
847
- type: "template-input",
848
- placeholder: "2026-03-16T10:00:00-04:00",
849
- required: true
850
- },
851
- {
852
- key: "calendarId",
853
- label: "Calendar ID",
854
- type: "template-input",
855
- placeholder: "67890"
856
- },
857
- mutationFlagsGroup
858
- ],
859
- handler: Effect.fn(function* (bag) {
860
- const { input } = bag;
861
- const client = yield* createAcuityClient(bag, createSdk);
862
- const appointmentId = yield* requiredInteger(input.appointmentId, "Appointment ID");
863
- if (!input.datetime.trim()) return yield* new StepFailure({ message: "New Datetime is required (ISO 8601 format)." });
864
- const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
865
- const admin = yield* optionalBoolean(input.admin, "Run as Admin");
866
- const noEmail = yield* optionalBoolean(input.noEmail, "Suppress Acuity Emails");
867
- const appointment = yield* bag.step.run("reschedule-appointment", callAcuity("Failed to reschedule appointment.", () => client.appointments.reschedule(appointmentId, {
868
- datetime: input.datetime,
869
- calendarID
870
- }, {
871
- admin,
872
- noEmail
873
- })));
874
- return {
875
- appointment,
876
- id: appointment.id,
877
- datetime: appointment.datetime
878
- };
879
- })
880
- },
881
- "cancel-appointment": {
882
- label: "Cancel Appointment",
883
- description: "Cancel an appointment in Acuity",
884
- input: cancelAppointmentInput,
885
- output: cancelAppointmentOutput,
886
- configFields: [
887
- {
888
- key: "appointmentId",
889
- label: "Appointment ID",
890
- type: "template-input",
891
- placeholder: "123456789",
892
- required: true
893
- },
894
- {
895
- key: "cancelNote",
896
- label: "Cancel Note",
897
- type: "template-textarea",
898
- rows: 3,
899
- placeholder: "Optional cancellation reason"
900
- },
901
- {
902
- key: "noShow",
903
- label: "Mark as No-Show",
904
- type: "select",
905
- defaultValue: "",
906
- options: yesNoSelect
907
- },
908
- mutationFlagsGroup
909
- ],
910
- handler: Effect.fn(function* (bag) {
911
- const { input } = bag;
912
- const client = yield* createAcuityClient(bag, createSdk);
913
- const appointmentId = yield* requiredInteger(input.appointmentId, "Appointment ID");
914
- const noShow = yield* optionalBoolean(input.noShow, "Mark as No-Show");
915
- const admin = yield* optionalBoolean(input.admin, "Run as Admin");
916
- const noEmail = yield* optionalBoolean(input.noEmail, "Suppress Acuity Emails");
917
- const appointment = yield* bag.step.run("cancel-appointment", callAcuity("Failed to cancel appointment.", () => client.appointments.cancel(appointmentId, {
918
- cancelNote: input.cancelNote,
919
- noShow
920
- }, {
921
- admin,
922
- noEmail
923
- })));
924
- return {
925
- appointment,
926
- id: appointment.id,
927
- canceled: appointment.canceled
928
- };
929
- })
930
- }
931
- }
932
- });
933
- }
934
- const acuity = createAcuity(createAcuitySdk);
935
- //#endregion
936
11
  //#region src/clerk/metadata.ts
937
12
  /**
938
13
  * The JSON a workflow author types into a metadata field, as Clerk takes it.
@@ -2089,7 +1164,7 @@ const twilio = defineIntegration({
2089
1164
  label: "Twilio",
2090
1165
  description: "Send SMS messages with Twilio Programmable Messaging",
2091
1166
  credentials: twilioCredentialFields,
2092
- test: async () => (await import("./test-S28s7Z3a.js")).testTwilio,
1167
+ test: async () => (await import("./test-DN4ZApY4.js")).testTwilio,
2093
1168
  actions: { "send-sms": {
2094
1169
  label: "Send SMS",
2095
1170
  description: "Send an SMS via Twilio",
@@ -2225,7 +1300,6 @@ const twilio = defineIntegration({
2225
1300
  //#endregion
2226
1301
  //#region src/index.ts
2227
1302
  const builtInIntegrations = [
2228
- acuity,
2229
1303
  clerk,
2230
1304
  linear,
2231
1305
  resend,
@@ -2233,4 +1307,4 @@ const builtInIntegrations = [
2233
1307
  twilio
2234
1308
  ];
2235
1309
  //#endregion
2236
- export { acuity, builtInIntegrations, clerk, linear, resend, slack, twilio };
1310
+ export { builtInIntegrations, clerk, linear, resend, slack, twilio };