@hostwebhook/node-types 1.69.0 → 1.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bucket-operations.d.ts +88 -0
  2. package/dist/bucket-operations.js +140 -0
  3. package/dist/calendar-toolkit.d.ts +1 -1
  4. package/dist/connections.d.ts +1 -1
  5. package/dist/connections.js +1 -0
  6. package/dist/discord-operations.d.ts +1 -1
  7. package/dist/discord-operations.js +4 -4
  8. package/dist/discord-toolkit.d.ts +1 -1
  9. package/dist/discord-toolkit.js +3 -3
  10. package/dist/dispatch.d.ts +1 -1
  11. package/dist/dispatch.js +1 -0
  12. package/dist/docs-toolkit.d.ts +1 -1
  13. package/dist/drive-toolkit.d.ts +1 -1
  14. package/dist/esm/bucket-operations.d.ts +88 -0
  15. package/dist/esm/bucket-operations.js +136 -0
  16. package/dist/esm/calendar-operations.d.ts +67 -0
  17. package/dist/esm/calendar-operations.js +176 -0
  18. package/dist/esm/calendar-toolkit.d.ts +50 -0
  19. package/dist/esm/calendar-toolkit.js +152 -0
  20. package/dist/esm/capacidad-de-credencial.d.ts +75 -0
  21. package/dist/esm/capacidad-de-credencial.js +68 -0
  22. package/dist/esm/connections.d.ts +17 -0
  23. package/dist/esm/connections.js +107 -0
  24. package/dist/esm/credentials.d.ts +144 -0
  25. package/dist/esm/credentials.js +199 -0
  26. package/dist/esm/discord-operations.d.ts +104 -0
  27. package/dist/esm/discord-operations.js +407 -0
  28. package/dist/esm/discord-toolkit.d.ts +64 -0
  29. package/dist/esm/discord-toolkit.js +260 -0
  30. package/dist/esm/dispatch.d.ts +16 -0
  31. package/dist/esm/dispatch.js +77 -0
  32. package/dist/esm/docs-operations.d.ts +99 -0
  33. package/dist/esm/docs-operations.js +127 -0
  34. package/dist/esm/docs-toolkit.d.ts +42 -0
  35. package/dist/esm/docs-toolkit.js +97 -0
  36. package/dist/esm/drive-operations.d.ts +103 -0
  37. package/dist/esm/drive-operations.js +319 -0
  38. package/dist/esm/drive-toolkit.d.ts +54 -0
  39. package/dist/esm/drive-toolkit.js +177 -0
  40. package/dist/esm/github-operations.d.ts +147 -0
  41. package/dist/esm/github-operations.js +551 -0
  42. package/dist/esm/gmail-operations.d.ts +190 -0
  43. package/dist/esm/gmail-operations.js +560 -0
  44. package/dist/esm/google-analytics-operations.d.ts +99 -0
  45. package/dist/esm/google-analytics-operations.js +222 -0
  46. package/dist/esm/google-contacts-operations.d.ts +93 -0
  47. package/dist/esm/google-contacts-operations.js +363 -0
  48. package/dist/esm/index.d.ts +68 -0
  49. package/dist/esm/index.js +51 -0
  50. package/dist/esm/jira-operations.d.ts +164 -0
  51. package/dist/esm/jira-operations.js +470 -0
  52. package/dist/esm/llm-models.d.ts +101 -0
  53. package/dist/esm/llm-models.js +230 -0
  54. package/dist/esm/mailchimp-operations.d.ts +56 -0
  55. package/dist/esm/mailchimp-operations.js +155 -0
  56. package/dist/esm/mongo-operations.d.ts +61 -0
  57. package/dist/esm/mongo-operations.js +120 -0
  58. package/dist/esm/notion-operations.d.ts +125 -0
  59. package/dist/esm/notion-operations.js +511 -0
  60. package/dist/esm/openrouter.d.ts +142 -0
  61. package/dist/esm/openrouter.js +99 -0
  62. package/dist/esm/package.json +4 -0
  63. package/dist/esm/postgres-operations.d.ts +60 -0
  64. package/dist/esm/postgres-operations.js +75 -0
  65. package/dist/esm/registry.d.ts +160 -0
  66. package/dist/esm/registry.js +885 -0
  67. package/dist/esm/sheets-operations.d.ts +78 -0
  68. package/dist/esm/sheets-operations.js +150 -0
  69. package/dist/esm/sheets-toolkit.d.ts +49 -0
  70. package/dist/esm/sheets-toolkit.js +127 -0
  71. package/dist/esm/shopify-operations.d.ts +95 -0
  72. package/dist/esm/shopify-operations.js +259 -0
  73. package/dist/esm/slack-operations.d.ts +81 -0
  74. package/dist/esm/slack-operations.js +244 -0
  75. package/dist/esm/slack-toolkit.d.ts +60 -0
  76. package/dist/esm/slack-toolkit.js +235 -0
  77. package/dist/esm/telegram-operations.d.ts +104 -0
  78. package/dist/esm/telegram-operations.js +351 -0
  79. package/dist/esm/telegram-toolkit.d.ts +52 -0
  80. package/dist/esm/telegram-toolkit.js +183 -0
  81. package/dist/esm/types.d.ts +149 -0
  82. package/dist/esm/types.js +8 -0
  83. package/dist/esm/ui.d.ts +13 -0
  84. package/dist/esm/ui.js +152 -0
  85. package/dist/esm/whatsapp-operations.d.ts +23 -0
  86. package/dist/esm/whatsapp-operations.js +36 -0
  87. package/dist/index.d.ts +68 -66
  88. package/dist/index.js +193 -187
  89. package/dist/openrouter.d.ts +1 -1
  90. package/dist/registry.d.ts +4 -4
  91. package/dist/registry.js +540 -210
  92. package/dist/sheets-toolkit.d.ts +1 -1
  93. package/dist/slack-operations.d.ts +1 -1
  94. package/dist/slack-operations.js +4 -4
  95. package/dist/slack-toolkit.d.ts +1 -1
  96. package/dist/slack-toolkit.js +3 -3
  97. package/dist/telegram-toolkit.d.ts +1 -1
  98. package/dist/types.d.ts +1 -1
  99. package/dist/ui.d.ts +1 -1
  100. package/dist/ui.js +2 -0
  101. package/package.json +21 -3
@@ -0,0 +1,885 @@
1
+ /**
2
+ * Centralized NODE_REGISTRY — single source of truth for all node metadata.
3
+ *
4
+ * When adding a new node type, add ONE entry here. The dashboard derives
5
+ * all per-node maps (colors, URLs, Redux keys, palette, delete, etc.)
6
+ * from this registry. No more 18+ touch points.
7
+ */
8
+ export const NODE_REGISTRY = {
9
+ // ── Sources ──
10
+ webhook: {
11
+ type: "webhook",
12
+ prefix: "wh",
13
+ label: "Webhook",
14
+ group: "Sources",
15
+ detailPath: "/dashboard/webhooks",
16
+ apiPath: "/webhooks",
17
+ stateKey: "webhooks",
18
+ allStateKey: "allWebhooks",
19
+ color: "#a78bfa",
20
+ testable: false,
21
+ },
22
+ scheduledWorkflow: {
23
+ type: "scheduledWorkflow",
24
+ prefix: "swf",
25
+ label: "Scheduled Workflow",
26
+ group: "Sources",
27
+ detailPath: "/dashboard/scheduled-workflows",
28
+ apiPath: "/scheduled-workflows",
29
+ stateKey: "scheduledWorkflows",
30
+ allStateKey: "allScheduledWorkflows",
31
+ color: "#2dd4bf",
32
+ testable: true,
33
+ },
34
+ chatTrigger: {
35
+ type: "chatTrigger",
36
+ prefix: "chat",
37
+ label: "Chat Trigger",
38
+ group: "Sources",
39
+ detailPath: "/dashboard/chat-triggers",
40
+ apiPath: "/chat-triggers",
41
+ stateKey: "chatTriggers",
42
+ allStateKey: "allChatTriggers",
43
+ color: "#06b6d4",
44
+ testable: false,
45
+ },
46
+ trigger: {
47
+ type: "trigger",
48
+ prefix: "trg",
49
+ label: "Trigger",
50
+ group: "Sources",
51
+ detailPath: "/dashboard/triggers",
52
+ apiPath: "/triggers",
53
+ stateKey: "triggers",
54
+ allStateKey: "allTriggers",
55
+ color: "#ef4444",
56
+ testable: false,
57
+ },
58
+ voiceAgent: {
59
+ type: "voiceAgent",
60
+ prefix: "va",
61
+ label: "Voice Agent",
62
+ group: "Sources",
63
+ detailPath: "/dashboard/voice-agents",
64
+ apiPath: "/voice-agents",
65
+ stateKey: "voiceAgents",
66
+ allStateKey: "allVoiceAgents",
67
+ color: "#a855f7",
68
+ testable: false,
69
+ },
70
+ // ── Flow Control ──
71
+ filter: {
72
+ type: "filter",
73
+ prefix: "fi",
74
+ label: "Filter",
75
+ group: "Flow Control",
76
+ detailPath: "/dashboard/filter-nodes",
77
+ apiPath: "/filter-nodes",
78
+ stateKey: "filterNodes",
79
+ allStateKey: "allFilterNodes",
80
+ color: "#facc15",
81
+ testable: true,
82
+ },
83
+ transform: {
84
+ type: "transform",
85
+ prefix: "tr",
86
+ label: "Transform",
87
+ group: "Flow Control",
88
+ detailPath: "/dashboard/transform-nodes",
89
+ apiPath: "/transform-nodes",
90
+ stateKey: "transformNodes",
91
+ allStateKey: "allTransformNodes",
92
+ color: "#fbbf24",
93
+ testable: true,
94
+ },
95
+ schemaValidator: {
96
+ type: "schemaValidator",
97
+ prefix: "sv",
98
+ label: "Schema Validator",
99
+ group: "Flow Control",
100
+ detailPath: "/dashboard/schema-validators",
101
+ apiPath: "/schema-validators",
102
+ stateKey: "schemaValidators",
103
+ allStateKey: "allSchemaValidators",
104
+ color: "#818cf8",
105
+ testable: true,
106
+ },
107
+ conditional: {
108
+ type: "conditional",
109
+ prefix: "cond",
110
+ label: "Conditional",
111
+ group: "Flow Control",
112
+ detailPath: "/dashboard/conditional-nodes",
113
+ apiPath: "/conditional-nodes",
114
+ stateKey: "conditionalNodes",
115
+ allStateKey: "allConditionalNodes",
116
+ color: "#c084fc",
117
+ testable: true,
118
+ },
119
+ delay: {
120
+ type: "delay",
121
+ prefix: "delay",
122
+ label: "Delay",
123
+ group: "Flow Control",
124
+ detailPath: "/dashboard/delay-nodes",
125
+ apiPath: "/delay-nodes",
126
+ stateKey: "delayNodes",
127
+ allStateKey: "allDelayNodes",
128
+ color: "#94a3b8",
129
+ testable: true,
130
+ },
131
+ rateLimiter: {
132
+ type: "rateLimiter",
133
+ prefix: "rl",
134
+ label: "Rate Limiter",
135
+ group: "Flow Control",
136
+ detailPath: "/dashboard/rate-limiter-nodes",
137
+ apiPath: "/rate-limiter-nodes",
138
+ stateKey: "rateLimiterNodes",
139
+ allStateKey: "allRateLimiterNodes",
140
+ color: "#f97316",
141
+ testable: false,
142
+ },
143
+ aggregator: {
144
+ type: "aggregator",
145
+ prefix: "agg",
146
+ label: "Aggregator",
147
+ group: "Flow Control",
148
+ detailPath: "/dashboard/aggregator-nodes",
149
+ apiPath: "/aggregator-nodes",
150
+ stateKey: "aggregatorNodes",
151
+ allStateKey: "allAggregatorNodes",
152
+ color: "#84cc16",
153
+ testable: true,
154
+ },
155
+ cache: {
156
+ type: "cache",
157
+ prefix: "cache",
158
+ label: "Cache",
159
+ group: "Flow Control",
160
+ detailPath: "/dashboard/cache-nodes",
161
+ apiPath: "/cache-nodes",
162
+ stateKey: "cacheNodes",
163
+ allStateKey: "allCacheNodes",
164
+ color: "#d946ef",
165
+ testable: true,
166
+ },
167
+ code: {
168
+ type: "code",
169
+ prefix: "code",
170
+ label: "Code",
171
+ group: "Flow Control",
172
+ detailPath: "/dashboard/code-nodes",
173
+ apiPath: "/code-nodes",
174
+ stateKey: "codeNodes",
175
+ allStateKey: "allCodeNodes",
176
+ color: "#a855f7",
177
+ testable: true,
178
+ },
179
+ ai: {
180
+ type: "ai",
181
+ prefix: "ai",
182
+ label: "AI",
183
+ /* 'Actions' y no 'Flow Control': un nodo de IA no encamina ni retiene un
184
+ evento —eso es control de flujo— sino que HACE algo con él y devuelve un
185
+ resultado, igual que enviar un correo o escribir en una hoja. Además es
186
+ lo que decide qué se ofrece como acción en el flujo guiado del primer
187
+ flujo (`FIRST_FLOW_ACTIONS` en el dashboard), y ahí tiene que estar. */
188
+ group: "Actions",
189
+ detailPath: "/dashboard/ai-nodes",
190
+ apiPath: "/ai-nodes",
191
+ stateKey: "aiNodes",
192
+ allStateKey: "allAiNodes",
193
+ color: "#d946ef",
194
+ testable: true,
195
+ },
196
+ split: {
197
+ type: "split",
198
+ prefix: "split",
199
+ label: "Split",
200
+ group: "Flow Control",
201
+ detailPath: "/dashboard/split-nodes",
202
+ apiPath: "/split-nodes",
203
+ stateKey: "splitNodes",
204
+ allStateKey: "allSplitNodes",
205
+ color: "#38bdf8",
206
+ testable: true,
207
+ },
208
+ merge: {
209
+ type: "merge",
210
+ prefix: "merge",
211
+ label: "Merge",
212
+ group: "Flow Control",
213
+ detailPath: "/dashboard/merge-nodes",
214
+ apiPath: "/merge-nodes",
215
+ stateKey: "mergeNodes",
216
+ allStateKey: "allMergeNodes",
217
+ color: "#94a3b8",
218
+ testable: false,
219
+ },
220
+ approval: {
221
+ type: "approval",
222
+ prefix: "appr",
223
+ label: "Approval",
224
+ group: "Flow Control",
225
+ detailPath: "/dashboard/approval-nodes",
226
+ apiPath: "/approval-nodes",
227
+ stateKey: "approvalNodes",
228
+ allStateKey: "allApprovalNodes",
229
+ color: "#94a3b8",
230
+ testable: true,
231
+ },
232
+ loop: {
233
+ type: "loop",
234
+ prefix: "loop",
235
+ label: "Loop",
236
+ group: "Flow Control",
237
+ detailPath: "/dashboard/loop-nodes",
238
+ apiPath: "/loop-nodes",
239
+ stateKey: "loopNodes",
240
+ allStateKey: "allLoopNodes",
241
+ color: "#f59e0b",
242
+ testable: true,
243
+ },
244
+ markdown: {
245
+ type: "markdown",
246
+ prefix: "md",
247
+ label: "Markdown",
248
+ group: "Flow Control",
249
+ detailPath: "/dashboard/markdown-nodes",
250
+ apiPath: "/markdown-nodes",
251
+ stateKey: "markdownNodes",
252
+ allStateKey: "allMarkdownNodes",
253
+ color: "#6366f1",
254
+ testable: true,
255
+ },
256
+ fileTransform: {
257
+ type: "fileTransform",
258
+ prefix: "ft",
259
+ label: "File Transform",
260
+ group: "Flow Control",
261
+ detailPath: "/dashboard/file-transform-nodes",
262
+ apiPath: "/file-transform-nodes",
263
+ stateKey: "fileTransformNodes",
264
+ allStateKey: "allFileTransformNodes",
265
+ color: "#f97316",
266
+ testable: true,
267
+ },
268
+ limit: {
269
+ type: "limit",
270
+ prefix: "lim",
271
+ label: "Limit",
272
+ group: "Flow Control",
273
+ detailPath: "/dashboard/limit-nodes",
274
+ apiPath: "/limit-nodes",
275
+ stateKey: "limitNodes",
276
+ allStateKey: "allLimitNodes",
277
+ // Amber matches filter / aggregator family — visually groups
278
+ // "array-shape transformations" together on the canvas.
279
+ color: "#fbbf24",
280
+ testable: true,
281
+ },
282
+ // ── Routing ──
283
+ router: {
284
+ type: "router",
285
+ prefix: "ro",
286
+ label: "Router",
287
+ group: "Flow Control",
288
+ detailPath: "/dashboard/routers",
289
+ apiPath: "/routers",
290
+ stateKey: "routers",
291
+ allStateKey: "allRouters",
292
+ color: "#38bdf8",
293
+ testable: true,
294
+ },
295
+ // ── Actions ──
296
+ /**
297
+ * Manda correo por Resend, y sólo por Resend.
298
+ *
299
+ * Tuvo un campo `provider` con Gmail y un Outlook que nunca llegó. Gmail se
300
+ * fue a su propio nodo —ver `gmailAction`— y con él su OAuth, sus dieciséis
301
+ * operaciones y el toolkit de IA, que era suyo: aquí sólo quedan enviar y
302
+ * el modo de IA no tiene dieciséis herramientas que ofrecer.
303
+ */
304
+ emailAction: {
305
+ type: "emailAction",
306
+ prefix: "ea",
307
+ label: "Email Action",
308
+ group: "Actions",
309
+ detailPath: "/dashboard/email-actions",
310
+ apiPath: "/email-actions",
311
+ stateKey: "emailActions",
312
+ allStateKey: "allEmailActions",
313
+ color: "#f472b6",
314
+ testable: true,
315
+ /**
316
+ * Sigue siendo herramienta: hay una ruta native + aiEnabled donde el nodo
317
+ * AI genera el correo y este lo manda por Resend —en produccion existe un
318
+ * «using native email as tool»—. Se la quite al partir Gmail dando por
319
+ * hecho que el toolkit era solo suyo, y sin esto el nodo recupera sus
320
+ * handles en modo herramienta: justo el mal enrutado que este flag evita.
321
+ */
322
+ isToolOnly: (e) => e.aiEnabled === true,
323
+ },
324
+ /**
325
+ * Gmail salio del nodo Email, que ahora manda SOLO por Resend.
326
+ *
327
+ * Estaban juntos bajo un campo `provider`, y eso obligaba a que un nodo de
328
+ * dos operaciones —enviar por Resend— cargara con las dieciseis de Gmail, su
329
+ * OAuth, su toolkit de IA y su send-and-wait. Partidos, cada uno se explica
330
+ * solo y el de Email vuelve a caber en una pantalla.
331
+ */
332
+ gmailAction: {
333
+ type: "gmailAction",
334
+ prefix: "gm",
335
+ label: "Gmail",
336
+ group: "Actions",
337
+ detailPath: "/dashboard/gmail-actions",
338
+ apiPath: "/gmail-actions",
339
+ stateKey: "gmailActions",
340
+ allStateKey: "allGmailActions",
341
+ color: "#ea4335",
342
+ testable: true,
343
+ // El toolkit de IA se vino con Gmail: son sus dieciseis operaciones las
344
+ // que el LLM llama, no las dos de Resend. Ver el comentario de arriba.
345
+ isToolOnly: (e) => e.aiEnabled === true,
346
+ },
347
+ httpAction: {
348
+ type: "httpAction",
349
+ prefix: "ha",
350
+ label: "HTTP Action",
351
+ group: "Actions",
352
+ detailPath: "/dashboard/http-actions",
353
+ apiPath: "/http-actions",
354
+ stateKey: "httpActions",
355
+ allStateKey: "allHttpActions",
356
+ color: "#3b82f6",
357
+ testable: true,
358
+ },
359
+ mongoAction: {
360
+ type: "mongoAction",
361
+ prefix: "ma",
362
+ label: "MongoDB Action",
363
+ group: "Actions",
364
+ detailPath: "/dashboard/mongo-actions",
365
+ apiPath: "/mongo-actions",
366
+ stateKey: "mongoActions",
367
+ allStateKey: "allMongoActions",
368
+ color: "#34d399",
369
+ testable: true,
370
+ },
371
+ postgresAction: {
372
+ type: "postgresAction",
373
+ prefix: "pg",
374
+ label: "Postgres Action",
375
+ group: "Actions",
376
+ detailPath: "/dashboard/postgres-actions",
377
+ apiPath: "/postgres-actions",
378
+ stateKey: "postgresActions",
379
+ allStateKey: "allPostgresActions",
380
+ color: "#336791",
381
+ testable: true,
382
+ },
383
+ notificationAction: {
384
+ type: "notificationAction",
385
+ prefix: "na",
386
+ label: "Notification",
387
+ group: "Actions",
388
+ detailPath: "/dashboard/notification-actions",
389
+ apiPath: "/notification-actions",
390
+ stateKey: "notificationActions",
391
+ allStateKey: "allNotificationActions",
392
+ color: "#f472b6",
393
+ testable: true,
394
+ },
395
+ sheetsAction: {
396
+ type: "sheetsAction",
397
+ prefix: "sheet",
398
+ label: "Google Sheets Action",
399
+ group: "Actions",
400
+ detailPath: "/dashboard/sheets-actions",
401
+ apiPath: "/sheets-actions",
402
+ stateKey: "sheetsActions",
403
+ allStateKey: "allSheetsActions",
404
+ color: "#34d399",
405
+ testable: true,
406
+ },
407
+ calendarAction: {
408
+ type: "calendarAction",
409
+ prefix: "cal",
410
+ label: "Google Calendar Action",
411
+ group: "Actions",
412
+ detailPath: "/dashboard/calendar-actions",
413
+ apiPath: "/calendar-actions",
414
+ stateKey: "calendarActions",
415
+ allStateKey: "allCalendarActions",
416
+ color: "#60a5fa",
417
+ testable: true,
418
+ // AI Toolkit mode wires the node through the upstream AI Node's
419
+ // tools[] list, not via canvas edges — same pattern as emailAction.
420
+ isToolOnly: (e) => e.aiEnabled === true,
421
+ },
422
+ docsAction: {
423
+ type: "docsAction",
424
+ prefix: "doc",
425
+ label: "Google Docs",
426
+ group: "Actions",
427
+ detailPath: "/dashboard/docs-actions",
428
+ apiPath: "/docs-actions",
429
+ stateKey: "docsActions",
430
+ allStateKey: "allDocsActions",
431
+ color: "#4285F4",
432
+ testable: true,
433
+ },
434
+ driveAction: {
435
+ type: "driveAction",
436
+ prefix: "drv",
437
+ label: "Google Drive",
438
+ group: "Actions",
439
+ detailPath: "/dashboard/drive-actions",
440
+ apiPath: "/drive-actions",
441
+ stateKey: "driveActions",
442
+ allStateKey: "allDriveActions",
443
+ color: "#22c55e",
444
+ testable: true,
445
+ // AI Toolkit mode wires the node through the upstream AI Node's
446
+ // tools[] list, not via canvas edges — same pattern as emailAction.
447
+ isToolOnly: (e) => e.aiEnabled === true,
448
+ },
449
+ firecrawlAction: {
450
+ type: "firecrawlAction",
451
+ prefix: "fc",
452
+ label: "Firecrawl",
453
+ group: "Actions",
454
+ detailPath: "/dashboard/firecrawl-actions",
455
+ apiPath: "/firecrawl-actions",
456
+ stateKey: "firecrawlActions",
457
+ allStateKey: "allFirecrawlActions",
458
+ color: "#f97316",
459
+ testable: true,
460
+ },
461
+ mailchimpAction: {
462
+ type: "mailchimpAction",
463
+ prefix: "mc",
464
+ label: "Mailchimp",
465
+ group: "Actions",
466
+ detailPath: "/dashboard/mailchimp-actions",
467
+ apiPath: "/mailchimp-actions",
468
+ stateKey: "mailchimpActions",
469
+ allStateKey: "allMailchimpActions",
470
+ // Mailchimp's Cavendish yellow, darkened until it holds contrast on a
471
+ // light canvas. The brand value (#ffe01b) reads as a highlighter there.
472
+ color: "#c9a227",
473
+ testable: true,
474
+ },
475
+ shopifyAction: {
476
+ type: "shopifyAction",
477
+ prefix: "shp",
478
+ label: "Shopify",
479
+ group: "Actions",
480
+ detailPath: "/dashboard/shopify-actions",
481
+ apiPath: "/shopify-actions",
482
+ stateKey: "shopifyActions",
483
+ allStateKey: "allShopifyActions",
484
+ // Shopify's green, abierto un punto. El de marca (#008060) es más oscuro
485
+ // que cualquier otro color de este registro y se hunde contra el lienzo
486
+ // en modo oscuro; éste sigue leyéndose como Shopify y aguanta en los dos
487
+ // temas. Mismo criterio que el amarillo de Mailchimp, al revés.
488
+ color: "#0a9e70",
489
+ testable: true,
490
+ },
491
+ githubAction: {
492
+ type: "githubAction",
493
+ prefix: "gh",
494
+ label: "GitHub",
495
+ group: "Actions",
496
+ detailPath: "/dashboard/github-actions",
497
+ apiPath: "/github-actions",
498
+ stateKey: "githubActions",
499
+ allStateKey: "allGithubActions",
500
+ // El morado de "merged" de GitHub. Su marca real es acromática, y ahí no
501
+ // hay elección posible: en el minimapa lo único que distingue a un nodo es
502
+ // el color, el gris cálido ya lo ocupa Notion y el frío lo llevan tres.
503
+ //
504
+ // ⚠️ Es la banda MÁS disputada de este registro — ocho nodos en morado,
505
+ // con #7c3aed y #a855f7 como vecinos inmediatos. Se eligió por ser el
506
+ // color que de verdad dice GitHub, pero **la decisión se cierra mirando el
507
+ // minimapa**, no leyendo este comentario. Si choca, el plan B es un
508
+ // grafito claro, sabiendo que compite con Notion.
509
+ color: "#8957e5",
510
+ testable: true,
511
+ /* Sin isToolOnly: la v1 no trae modo toolkit de IA, y aquí pesa más que en
512
+ ningún otro nodo — el MCP oficial de GitHub ya cubre ese caso dentro del
513
+ AI Node. Los rieles son este nodo; la improvisación es el MCP. */
514
+ },
515
+ jiraAction: {
516
+ type: "jiraAction",
517
+ prefix: "jr",
518
+ label: "Jira",
519
+ group: "Actions",
520
+ detailPath: "/dashboard/jira-actions",
521
+ apiPath: "/jira-actions",
522
+ stateKey: "jiraActions",
523
+ allStateKey: "allJiraActions",
524
+ // El azul de marca de Jira, y estaba libre. La banda azul es la segunda
525
+ // mas poblada de este registro —nueve nodos— pero ninguno tan profundo:
526
+ // #3b82f6 y #60a5fa son claramente mas claros, y el #336791 de Postgres es
527
+ // mas apagado. En el minimapa es el azul oscuro, y eso lo distingue.
528
+ //
529
+ // Sigue valiendo lo que escribio GitHub aqui al lado: **la decision se
530
+ // cierra mirando el minimapa**, no leyendo este comentario.
531
+ color: "#0052CC",
532
+ testable: true,
533
+ /* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive dos
534
+ veces —specs del dashboard para el AI Node y specs de la api para MCP— y
535
+ esa duplicacion no se paga antes de ver el nodo en pantalla. Ademas
536
+ Atlassian tiene servidor MCP propio, ya registrado en
537
+ `MCP_OAUTH_PRESETS.atlassian`, que cubre la cola larga dentro del AI
538
+ Node. Los rieles son este nodo. */
539
+ },
540
+ bucketAction: {
541
+ type: "bucketAction",
542
+ prefix: "bkt",
543
+ label: "Bucket",
544
+ group: "Actions",
545
+ detailPath: "/dashboard/bucket-actions",
546
+ apiPath: "/bucket-actions",
547
+ stateKey: "bucketActions",
548
+ allStateKey: "allBucketActions",
549
+ // Teal profundo, y la banda estaba VACIA por abajo. Los cuatro vecinos de
550
+ // esa familia son claramente mas claros: `#2dd4bf` y `#34d399` son pasteles,
551
+ // `#06b6d4` tira a cian brillante y `#0a9e70` es verde. En el minimapa este
552
+ // es el unico oscuro de la banda, y eso lo distingue.
553
+ //
554
+ // Se eligio neutro a proposito y no un color de marca: el nodo no es de
555
+ // Cloudflare. Pintarlo del naranja de R2 seria mentir en cuanto alguien lo
556
+ // apunte a AWS, a MinIO o a Backblaze — que es justo para lo que se llama
557
+ // `bucketAction` y no `r2Action`.
558
+ //
559
+ // Vale lo que escribieron GitHub y Jira aqui al lado: **la decision se
560
+ // cierra mirando el minimapa**, no leyendo este comentario.
561
+ color: "#0f766e",
562
+ testable: true,
563
+ /* Sin `isToolOnly`: la v1 no trae modo toolkit de IA. Cada toolkit vive dos
564
+ veces —specs del dashboard para el AI Node y specs de la api para MCP— y
565
+ esa duplicacion no se paga antes de ver el nodo en pantalla. */
566
+ },
567
+ telegramAction: {
568
+ type: "telegramAction",
569
+ prefix: "tg",
570
+ label: "Telegram Action",
571
+ group: "Actions",
572
+ detailPath: "/dashboard/telegram-actions",
573
+ apiPath: "/telegram-actions",
574
+ stateKey: "telegramActions",
575
+ allStateKey: "allTelegramActions",
576
+ color: "#0ea5e9",
577
+ testable: true,
578
+ // AI Toolkit mode — same pattern as emailAction. One aiEnabled
579
+ // telegramAction expands into N MCP tools (one per operation) in
580
+ // the upstream AI Node's tool list, so we hide canvas handles to
581
+ // prevent accidental misrouting that would silently fire with
582
+ // empty fields.
583
+ isToolOnly: (e) => e.aiEnabled === true,
584
+ },
585
+ whatsappAction: {
586
+ type: "whatsappAction",
587
+ prefix: "wa",
588
+ label: "WhatsApp Action",
589
+ group: "Actions",
590
+ detailPath: "/dashboard/whatsapp-actions",
591
+ apiPath: "/whatsapp-actions",
592
+ stateKey: "whatsappActions",
593
+ allStateKey: "allWhatsappActions",
594
+ // WhatsApp brand green (#25d366) — instantly recognisable on the canvas.
595
+ color: "#25d366",
596
+ testable: true,
597
+ // Same toolkit pattern as telegramAction — when aiEnabled the
598
+ // entity stops being a manually-wired single-op action and becomes
599
+ // a multi-op tool surface for an upstream AI Node. Hide canvas
600
+ // handles to prevent accidental wiring that would fire with empty
601
+ // fields the LLM is supposed to populate.
602
+ isToolOnly: (e) => e.aiEnabled === true,
603
+ },
604
+ discordAction: {
605
+ type: "discordAction",
606
+ prefix: "dc",
607
+ label: "Discord Action",
608
+ group: "Actions",
609
+ detailPath: "/dashboard/discord-actions",
610
+ apiPath: "/discord-actions",
611
+ stateKey: "discordActions",
612
+ allStateKey: "allDiscordActions",
613
+ // Discord brand "Blurple" (#5865F2).
614
+ color: "#5865F2",
615
+ testable: true,
616
+ // Same toolkit pattern as telegramAction / whatsappAction. Hide
617
+ // canvas handles when aiEnabled so the user can't manually wire a
618
+ // multi-op tool surface — the LLM picks the operation per call.
619
+ isToolOnly: (e) => e.aiEnabled === true,
620
+ },
621
+ slackAction: {
622
+ type: "slackAction",
623
+ prefix: "sl",
624
+ label: "Slack Action",
625
+ group: "Actions",
626
+ detailPath: "/dashboard/slack-actions",
627
+ apiPath: "/slack-actions",
628
+ stateKey: "slackActions",
629
+ allStateKey: "allSlackActions",
630
+ // Slack brand purple ("aubergine"-derived family). Matches the
631
+ // accent color used by SlackConfigSection / ServiceTriggerNode for
632
+ // visual continuity across Slack-related nodes.
633
+ color: "#7c3aed",
634
+ testable: true,
635
+ // Same AI-tool gating as Telegram/Discord/WhatsApp/Email/Calendar/
636
+ // Contacts. When aiEnabled the node fans out into 16 MCP tools
637
+ // (one per SLACK_OPERATIONS entry); hide canvas handles to prevent
638
+ // accidental wiring with empty fields the LLM is supposed to fill.
639
+ isToolOnly: (e) => e.aiEnabled === true,
640
+ },
641
+ googleContactsAction: {
642
+ type: "googleContactsAction",
643
+ prefix: "gc",
644
+ label: "Google Contacts",
645
+ group: "Actions",
646
+ detailPath: "/dashboard/google-contacts-actions",
647
+ apiPath: "/google-contacts-actions",
648
+ stateKey: "googleContactsActions",
649
+ allStateKey: "allGoogleContactsActions",
650
+ // Google "blue" used by Google Drive / Docs (#4285F4) — same family.
651
+ color: "#4285F4",
652
+ testable: true,
653
+ // AI toolkit mode mirrors emailAction / calendarAction — when
654
+ // aiEnabled the node expands into N MCP tools (one per People API
655
+ // operation) in the upstream AI Node's tool list. Hide canvas
656
+ // handles to keep manual + tool modes from being mixed.
657
+ isToolOnly: (e) => e.aiEnabled === true,
658
+ },
659
+ googleAnalyticsAction: {
660
+ type: "googleAnalyticsAction",
661
+ prefix: "gaa",
662
+ label: "Google Analytics",
663
+ group: "Actions",
664
+ detailPath: "/dashboard/google-analytics-actions",
665
+ apiPath: "/google-analytics-actions",
666
+ stateKey: "googleAnalyticsActions",
667
+ allStateKey: "allGoogleAnalyticsActions",
668
+ // Naranja de marca de Google Analytics (#E37400). No reutiliza el
669
+ // #ff9933 de RSS ni el orange-500 de Tailwind que ya llevan tres nodos:
670
+ // en el minimapa lo unico que distingue a un nodo es el color.
671
+ color: "#E37400",
672
+ testable: true,
673
+ /* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive
674
+ dos veces --specs del dashboard para el AI Node y specs de la api para
675
+ MCP, sin nada que las ate-- y esa duplicacion no se paga antes de ver
676
+ el nodo funcionando en pantalla. */
677
+ },
678
+ notionAction: {
679
+ type: "notionAction",
680
+ prefix: "ntn",
681
+ label: "Notion",
682
+ group: "Actions",
683
+ detailPath: "/dashboard/notion-actions",
684
+ apiPath: "/notion-actions",
685
+ stateKey: "notionActions",
686
+ allStateKey: "allNotionActions",
687
+ // Notion es monocromo de marca (su negro es #37352F), y un punto negro
688
+ // en el minimapa desaparece en modo oscuro. Este stone-500 es el gris
689
+ // CÁLIDO más cercano a esa marca, y no choca con el #94a3b8 (slate, gris
690
+ // frío) que ya llevan tres nodos: en el minimapa lo único que distingue
691
+ // a un nodo es el color.
692
+ color: "#78716c",
693
+ testable: true,
694
+ /* Sin isToolOnly: la v1 no trae modo toolkit de IA. Cada toolkit vive dos
695
+ veces --specs del dashboard para el AI Node y specs de la api para
696
+ MCP-- y esa duplicación no se paga antes de ver el nodo en pantalla. */
697
+ },
698
+ vectorStore: {
699
+ type: "vectorStore",
700
+ prefix: "vs",
701
+ label: "Vector Store",
702
+ group: "Actions",
703
+ detailPath: "/dashboard/vector-store-nodes",
704
+ apiPath: "/vector-store-nodes",
705
+ stateKey: "vectorStoreNodes",
706
+ allStateKey: "allVectorStoreNodes",
707
+ color: "#06b6d4",
708
+ testable: true,
709
+ },
710
+ rssAction: {
711
+ type: "rssAction",
712
+ prefix: "rssa",
713
+ label: "RSS Fetch",
714
+ group: "Actions",
715
+ detailPath: "/dashboard/rss-actions",
716
+ apiPath: "/rss-actions",
717
+ stateKey: "rssActions",
718
+ allStateKey: "allRssActions",
719
+ // Classic RSS feed orange (#ff9933) — distinguishes from rateLimiter /
720
+ // fileTransform / firecrawl which sit on Tailwind orange-500.
721
+ color: "#ff9933",
722
+ testable: true,
723
+ },
724
+ socialMediaAction: {
725
+ type: "socialMediaAction",
726
+ prefix: "sm",
727
+ label: "Social Media",
728
+ group: "Actions",
729
+ detailPath: "/dashboard/social-media-actions",
730
+ apiPath: "/social-media-actions",
731
+ stateKey: "socialMediaActions",
732
+ allStateKey: "allSocialMediaActions",
733
+ // Pink/magenta (#ec4899) — neutral default; the canvas card overrides
734
+ // with the picked provider's brandColor at render time so each
735
+ // platform reads at a glance (Bluesky #0085ff, Mastodon #6364ff, ...).
736
+ color: "#ec4899",
737
+ testable: true,
738
+ // AI toolkit mode (Sprint 4) wires through tools[] like the other
739
+ // toolkit-capable actions. Manual + toolkit modes share the same
740
+ // detail page; the segmented control hides ops-config when toolkit.
741
+ isToolOnly: (e) => e.aiEnabled === true,
742
+ },
743
+ // ── Annotations ──
744
+ stickyNote: {
745
+ type: "stickyNote",
746
+ prefix: "note",
747
+ label: "Sticky Note",
748
+ group: "Annotations",
749
+ detailPath: "",
750
+ apiPath: "/sticky-notes",
751
+ stateKey: "stickyNotes",
752
+ allStateKey: "allStickyNotes",
753
+ color: "#fbbf24",
754
+ testable: false,
755
+ },
756
+ };
757
+ // ── Derived helpers ──
758
+ /** Get registry entry by canonical type */
759
+ export function getNodeRegistryEntry(type) {
760
+ return NODE_REGISTRY[type];
761
+ }
762
+ /** type → detail page path */
763
+ export const NODE_DETAIL_PATHS = Object.fromEntries(Object.values(NODE_REGISTRY)
764
+ .filter((n) => n.detailPath)
765
+ .map((n) => [n.type, n.detailPath]));
766
+ /** type → minimap color */
767
+ export const NODE_COLORS = Object.fromEntries(Object.values(NODE_REGISTRY).map((n) => [n.type, n.color]));
768
+ /** type → [stateKey, allStateKey] */
769
+ export const NODE_STATE_KEYS = Object.fromEntries(Object.values(NODE_REGISTRY).map((n) => [
770
+ n.type,
771
+ [n.stateKey, n.allStateKey],
772
+ ]));
773
+ /** prefix → type */
774
+ export const PREFIX_TO_NODE_TYPE = Object.fromEntries(Object.values(NODE_REGISTRY).map((n) => [n.prefix, n.type]));
775
+ /** type → prefix */
776
+ export const NODE_TYPE_TO_PREFIX = Object.fromEntries(Object.values(NODE_REGISTRY).map((n) => [n.type, n.prefix]));
777
+ function specsFor(type, catalog) {
778
+ return catalog[type]?.versions;
779
+ }
780
+ /** True when this type ships more than one version. False for every type today. */
781
+ export function isVersioned(type, catalog = NODE_REGISTRY) {
782
+ return (specsFor(type, catalog)?.length ?? 0) > 0;
783
+ }
784
+ /**
785
+ * Every declared version, newest first — the order a picker wants.
786
+ * Empty for a type that does not version.
787
+ */
788
+ export function versionsOf(type, catalog = NODE_REGISTRY) {
789
+ const declared = specsFor(type, catalog);
790
+ if (!declared)
791
+ return [];
792
+ return [...declared].sort((a, b) => b.v - a.v);
793
+ }
794
+ /** One version's spec, or undefined when the type or the number is unknown. */
795
+ export function versionSpec(type, v, catalog = NODE_REGISTRY) {
796
+ return specsFor(type, catalog)?.find((s) => s.v === v);
797
+ }
798
+ /**
799
+ * The version a new node of this type is born on.
800
+ *
801
+ * Returns 1 for a type that does not version, which is also how an entity
802
+ * with no `nodeVersion` is read. A node created before its type ever
803
+ * versioned was calling the original endpoints, so calling it version 1
804
+ * is not a convention — it is the truth.
805
+ */
806
+ export function currentVersion(type, catalog = NODE_REGISTRY) {
807
+ const current = specsFor(type, catalog)?.find((s) => s.status === "current");
808
+ return current?.v ?? 1;
809
+ }
810
+ /**
811
+ * Config fields present in `from` that `to` does not declare — the exact
812
+ * list a confirmation dialog should name, and the exact list the server
813
+ * should unset.
814
+ *
815
+ * Returns `null` when the question cannot be answered: the type does not
816
+ * version, or one of the two versions is not declared. `null` is not the
817
+ * same as "nothing is lost", and callers must not treat it that way — a
818
+ * dialog should refuse to offer the switch, and the api should reject the
819
+ * request. Returning `null` rather than `[]` is what stops "I don't know"
820
+ * from being read as "nothing".
821
+ *
822
+ * This lives here rather than in each consumer because the dialog that
823
+ * ANNOUNCES the loss and the server that PERFORMS it have to give the
824
+ * same answer. Two implementations would agree right until the day they
825
+ * did not, and that day the dialog lies.
826
+ */
827
+ export function fieldsLostBetween(type, from, to, catalog = NODE_REGISTRY) {
828
+ const a = versionSpec(type, from, catalog);
829
+ const b = versionSpec(type, to, catalog);
830
+ if (!a || !b)
831
+ return null;
832
+ return fieldsLost(a, b);
833
+ }
834
+ /**
835
+ * The same answer for two specs already in hand — what `fieldsLostBetween`
836
+ * delegates to once it has resolved both, and what a caller that already
837
+ * holds them from `versionsOf()` can use without a second lookup.
838
+ */
839
+ export function fieldsLost(from, to) {
840
+ if (from.v === to.v)
841
+ return [];
842
+ if (to.cleanBreak)
843
+ return [...from.fields];
844
+ const kept = new Set(to.fields);
845
+ return from.fields.filter((f) => !kept.has(f));
846
+ }
847
+ /**
848
+ * Declaration mistakes in the version catalogs, as readable lines.
849
+ *
850
+ * A guard rather than a line in a document: what this codebase learned
851
+ * from fifteen nodes skipping a checklist point everyone had read is that
852
+ * the written rule prevented nothing and the guard did. Empty means every
853
+ * catalog is well-formed.
854
+ */
855
+ export function versionCatalogErrors(catalog = NODE_REGISTRY) {
856
+ const errors = [];
857
+ for (const entry of Object.values(catalog)) {
858
+ const versions = entry.versions;
859
+ if (!versions)
860
+ continue;
861
+ if (versions.length === 0) {
862
+ errors.push(`${entry.type}: declares an empty versions array — omit the field instead`);
863
+ continue;
864
+ }
865
+ const numbers = versions.map((s) => s.v);
866
+ const repeatedNumbers = [
867
+ ...new Set(numbers.filter((n, i) => numbers.indexOf(n) !== i)),
868
+ ];
869
+ if (repeatedNumbers.length)
870
+ errors.push(`${entry.type}: repeated version number ${repeatedNumbers.join(", ")}`);
871
+ for (const spec of versions) {
872
+ if (!Number.isInteger(spec.v) || spec.v < 1)
873
+ errors.push(`${entry.type}: version "${spec.v}" is not a positive integer`);
874
+ const repeatedFields = [
875
+ ...new Set(spec.fields.filter((f, i) => spec.fields.indexOf(f) !== i)),
876
+ ];
877
+ if (repeatedFields.length)
878
+ errors.push(`${entry.type} v${spec.v}: repeated field ${repeatedFields.join(", ")}`);
879
+ }
880
+ const current = versions.filter((s) => s.status === "current");
881
+ if (current.length !== 1)
882
+ errors.push(`${entry.type}: needs exactly one version with status "current", found ${current.length}`);
883
+ }
884
+ return errors;
885
+ }