gufi-cli 0.1.49 → 0.1.51
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/commands/docs.js +1 -5
- package/dist/lib/docs-resolver.d.ts +8 -0
- package/dist/lib/docs-resolver.js +27 -0
- package/dist/lib/security.js +5 -0
- package/dist/mcp.js +56 -43
- package/docs/dev-guide/1-01-architecture.md +358 -0
- package/docs/dev-guide/1-02-multi-tenant.md +415 -0
- package/docs/dev-guide/1-03-column-types.md +594 -0
- package/docs/dev-guide/1-04-json-config.md +442 -0
- package/docs/dev-guide/1-05-authentication.md +427 -0
- package/docs/dev-guide/2-01-api-reference.md +564 -0
- package/docs/dev-guide/2-02-automations.md +508 -0
- package/docs/dev-guide/2-03-gufi-cli.md +568 -0
- package/docs/dev-guide/2-04-realtime.md +401 -0
- package/docs/dev-guide/2-05-permissions.md +497 -0
- package/docs/dev-guide/2-06-integrations-overview.md +104 -0
- package/docs/dev-guide/2-07-stripe.md +173 -0
- package/docs/dev-guide/2-08-nayax.md +297 -0
- package/docs/dev-guide/2-09-ourvend.md +226 -0
- package/docs/dev-guide/2-10-tns.md +177 -0
- package/docs/dev-guide/2-11-custom-http.md +268 -0
- package/docs/dev-guide/3-01-custom-views.md +555 -0
- package/docs/dev-guide/3-02-webhooks-api.md +446 -0
- package/docs/mcp/00-overview.md +329 -0
- package/docs/mcp/01-architecture.md +226 -0
- package/docs/mcp/02-modules.md +285 -0
- package/docs/mcp/03-fields.md +357 -0
- package/docs/mcp/04-views.md +613 -0
- package/docs/mcp/05-automations.md +461 -0
- package/docs/mcp/06-api.md +531 -0
- package/docs/mcp/07-packages.md +246 -0
- package/docs/mcp/08-common-errors.md +284 -0
- package/docs/mcp/09-examples.md +453 -0
- package/docs/mcp/README.md +71 -0
- package/docs/mcp/tool-descriptions.json +64 -0
- package/package.json +3 -2
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: automations
|
|
3
|
+
title: "Automations System"
|
|
4
|
+
description: "Automate business logic with scripts"
|
|
5
|
+
icon: Zap
|
|
6
|
+
category: dev
|
|
7
|
+
part: 2
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Automations System
|
|
11
|
+
|
|
12
|
+
Automate business logic with scripts
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
Gufi's automation system lets you run JavaScript code in response to events. Automations can:
|
|
17
|
+
|
|
18
|
+
- Send emails and notifications
|
|
19
|
+
- Update records across entities
|
|
20
|
+
- Call external APIs
|
|
21
|
+
- Perform calculations
|
|
22
|
+
- Enforce business rules
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
### Components
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
┌─────────────────────────────────────────────────────────┐
|
|
30
|
+
│ Automation System │
|
|
31
|
+
├─────────────────────────────────────────────────────────┤
|
|
32
|
+
│ │
|
|
33
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
34
|
+
│ │ Trigger │───▶│ Queue │───▶│ Worker │ │
|
|
35
|
+
│ │ Events │ │ (pg-boss) │ │ (Node) │ │
|
|
36
|
+
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
37
|
+
│ │ │ │
|
|
38
|
+
│ │ │ │
|
|
39
|
+
│ ▼ ▼ │
|
|
40
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
41
|
+
│ │ core.automation_scripts │ │
|
|
42
|
+
│ │ (JavaScript code) │ │
|
|
43
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
44
|
+
│ │ │
|
|
45
|
+
│ ▼ │
|
|
46
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
47
|
+
│ │ core.automation_executions │ │
|
|
48
|
+
│ │ (execution history) │ │
|
|
49
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
50
|
+
│ │
|
|
51
|
+
└───────────────────────────────────────────────────────┘
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Data Model
|
|
55
|
+
|
|
56
|
+
**core.automation_scripts** - The code
|
|
57
|
+
|
|
58
|
+
```sql
|
|
59
|
+
id, company_id, name, code, created_at, updated_at
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**core.entities.automations** - Triggers per entity (JSONB column)
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
[
|
|
66
|
+
{
|
|
67
|
+
"trigger": "insert",
|
|
68
|
+
"function_name": "send_welcome_email",
|
|
69
|
+
"script_id": 42,
|
|
70
|
+
"label": "Send Welcome Email",
|
|
71
|
+
"enabled": true
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**core.automation_meta** - Worker index (synced from entities.automations)
|
|
77
|
+
|
|
78
|
+
**core.automation_executions** - Execution log
|
|
79
|
+
|
|
80
|
+
## Trigger Types
|
|
81
|
+
|
|
82
|
+
### insert
|
|
83
|
+
|
|
84
|
+
Fires when a new record is created.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"trigger": "insert",
|
|
89
|
+
"function_name": "on_order_created"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Context available:**
|
|
94
|
+
- `row` - The new record
|
|
95
|
+
- `old_row` - null
|
|
96
|
+
|
|
97
|
+
### update
|
|
98
|
+
|
|
99
|
+
Fires when a record is modified.
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"trigger": "update",
|
|
104
|
+
"function_name": "on_status_change"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Context available:**
|
|
109
|
+
- `row` - The updated record
|
|
110
|
+
- `old_row` - Previous values
|
|
111
|
+
|
|
112
|
+
### delete
|
|
113
|
+
|
|
114
|
+
Fires when a record is deleted.
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"trigger": "delete",
|
|
119
|
+
"function_name": "on_customer_deleted"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Context available:**
|
|
124
|
+
- `row` - The deleted record
|
|
125
|
+
- `old_row` - Same as row
|
|
126
|
+
|
|
127
|
+
### click
|
|
128
|
+
|
|
129
|
+
Manual button trigger (user clicks).
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"trigger": "click",
|
|
134
|
+
"function_name": "send_invoice",
|
|
135
|
+
"label": "Send Invoice"
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Requires `label` for the UI button.
|
|
140
|
+
|
|
141
|
+
### scheduled
|
|
142
|
+
|
|
143
|
+
Cron-based scheduling.
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"trigger": "scheduled",
|
|
148
|
+
"function_name": "daily_report",
|
|
149
|
+
"cron_pattern": "0 8 * * *"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Cron patterns: `minute hour day month weekday`
|
|
154
|
+
|
|
155
|
+
## Writing Automation Scripts
|
|
156
|
+
|
|
157
|
+
### Basic Structure
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
async function my_automation(gufi) {
|
|
161
|
+
// Your code here
|
|
162
|
+
|
|
163
|
+
return { success: true };
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Context Object
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
const {
|
|
171
|
+
company_id, // Company ID
|
|
172
|
+
module_id, // Module ID
|
|
173
|
+
entity_id, // Entity ID
|
|
174
|
+
table, // Physical table name
|
|
175
|
+
row, // Current record data
|
|
176
|
+
old_row, // Previous data (update only)
|
|
177
|
+
input, // Custom input (click trigger)
|
|
178
|
+
env // Environment variables
|
|
179
|
+
} = context;
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### API Object
|
|
183
|
+
|
|
184
|
+
The `api` object provides:
|
|
185
|
+
|
|
186
|
+
#### Query Database
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
// IMPORTANT: api.query returns rows directly, NOT { rows }
|
|
190
|
+
const customers = await gufi.query(
|
|
191
|
+
"SELECT * FROM customers WHERE status = $1",
|
|
192
|
+
["active"]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Access results directly
|
|
196
|
+
if (customers.length === 0) {
|
|
197
|
+
throw new Error("No customers found");
|
|
198
|
+
}
|
|
199
|
+
const firstCustomer = customers[0];
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### Send Email
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
await gufi.integrations.notifications.email({
|
|
206
|
+
to: "customer@example.com",
|
|
207
|
+
subject: "Order Confirmation",
|
|
208
|
+
body: "Your order has been confirmed.",
|
|
209
|
+
html: "<h1>Order Confirmed</h1><p>Thank you!</p>"
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### HTTP Requests
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
const response = await gufi.http({
|
|
217
|
+
method: "POST",
|
|
218
|
+
url: "https://external-api.com/webhook",
|
|
219
|
+
headers: {
|
|
220
|
+
"Authorization": "Bearer " + context.env.API_KEY
|
|
221
|
+
},
|
|
222
|
+
body: { order_id: context.row.id }
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Logger Object
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
logger.info("Processing order", { orderId: row.id });
|
|
230
|
+
logger.warn("Low stock detected");
|
|
231
|
+
logger.error("Failed to send email", { error: err.message });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Logs appear in execution history.
|
|
235
|
+
|
|
236
|
+
## Complete Examples
|
|
237
|
+
|
|
238
|
+
### Send Email on Order
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
async function send_order_confirmation(gufi) {
|
|
242
|
+
const { row, env } = gufi.context;
|
|
243
|
+
|
|
244
|
+
gufi.logger.info("Sending order confirmation", { orderId: row.id });
|
|
245
|
+
|
|
246
|
+
// Get customer details
|
|
247
|
+
const customers = await gufi.query(
|
|
248
|
+
"SELECT * FROM customers WHERE id = $1",
|
|
249
|
+
[row.customer_id]
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (customers.length === 0) {
|
|
253
|
+
throw new Error("Customer not found");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const customer = customers[0];
|
|
257
|
+
|
|
258
|
+
// Send email
|
|
259
|
+
await gufi.integrations.notifications.email({
|
|
260
|
+
to: customer.email,
|
|
261
|
+
subject: `Order #${row.id} Confirmed`,
|
|
262
|
+
html: `
|
|
263
|
+
<h1>Thank you for your order!</h1>
|
|
264
|
+
<p>Order #${row.id} has been confirmed.</p>
|
|
265
|
+
<p>Total: ${row.total.currency} ${row.total.amount}</p>
|
|
266
|
+
`
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
gufi.logger.info("Email sent successfully");
|
|
270
|
+
|
|
271
|
+
return { success: true, emailSent: true };
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Update Inventory on Sale
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
async function update_stock_on_sale(gufi) {
|
|
279
|
+
const { row } = gufi.context;
|
|
280
|
+
|
|
281
|
+
// Get order items
|
|
282
|
+
const items = await gufi.query(
|
|
283
|
+
"SELECT * FROM order_items WHERE order_id = $1",
|
|
284
|
+
[row.id]
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Update each product's stock
|
|
288
|
+
for (const item of items) {
|
|
289
|
+
await gufi.query(
|
|
290
|
+
"UPDATE products SET stock = stock - $1 WHERE id = $2",
|
|
291
|
+
[item.quantity, item.product_id]
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
gufi.logger.info("Updated stock", {
|
|
295
|
+
productId: item.product_id,
|
|
296
|
+
quantity: item.quantity
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return { success: true, itemsUpdated: items.length };
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Call External API
|
|
305
|
+
|
|
306
|
+
```javascript
|
|
307
|
+
async function sync_to_external_system(gufi) {
|
|
308
|
+
const { row, env } = gufi.context;
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const response = await gufi.http({
|
|
312
|
+
method: "POST",
|
|
313
|
+
url: env.EXTERNAL_API_URL + "/customers",
|
|
314
|
+
headers: {
|
|
315
|
+
"Authorization": "Bearer " + env.EXTERNAL_API_KEY,
|
|
316
|
+
"Content-Type": "application/json"
|
|
317
|
+
},
|
|
318
|
+
body: {
|
|
319
|
+
external_id: row.id,
|
|
320
|
+
name: row.name,
|
|
321
|
+
email: row.email
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
gufi.logger.info("Synced to external system", {
|
|
326
|
+
externalId: response.body.id
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return { success: true, externalId: response.body.id };
|
|
330
|
+
|
|
331
|
+
} catch (error) {
|
|
332
|
+
gufi.logger.error("Sync failed", { error: error.message });
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Scheduled Daily Report
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
async function daily_sales_report(gufi) {
|
|
342
|
+
const { env } = gufi.context;
|
|
343
|
+
|
|
344
|
+
// Get yesterday's sales
|
|
345
|
+
const sales = await gufi.query(`
|
|
346
|
+
SELECT
|
|
347
|
+
COUNT(*) as count,
|
|
348
|
+
SUM((total->>'amount')::numeric) as total
|
|
349
|
+
FROM orders
|
|
350
|
+
WHERE created_at >= CURRENT_DATE - INTERVAL '1 day'
|
|
351
|
+
AND created_at < CURRENT_DATE
|
|
352
|
+
`);
|
|
353
|
+
|
|
354
|
+
const report = sales[0];
|
|
355
|
+
|
|
356
|
+
// Send report email
|
|
357
|
+
await gufi.integrations.notifications.email({
|
|
358
|
+
to: env.REPORT_EMAIL,
|
|
359
|
+
subject: "Daily Sales Report",
|
|
360
|
+
html: `
|
|
361
|
+
<h1>Daily Sales Report</h1>
|
|
362
|
+
<p>Orders: ${report.count}</p>
|
|
363
|
+
<p>Total Revenue: €${report.total || 0}</p>
|
|
364
|
+
`
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
gufi.logger.info("Report sent", report);
|
|
368
|
+
|
|
369
|
+
return { success: true, ...report };
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Managing Automations
|
|
374
|
+
|
|
375
|
+
### CLI Commands
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
# List scripts
|
|
379
|
+
gufi automations -c 146
|
|
380
|
+
|
|
381
|
+
# View script code
|
|
382
|
+
gufi automation 42
|
|
383
|
+
|
|
384
|
+
# Edit script
|
|
385
|
+
gufi automation 42 --edit
|
|
386
|
+
|
|
387
|
+
# Create script
|
|
388
|
+
gufi automation:create send_email script.js -c 146
|
|
389
|
+
|
|
390
|
+
# View entity triggers
|
|
391
|
+
gufi entity:automations 4589
|
|
392
|
+
|
|
393
|
+
# Edit triggers
|
|
394
|
+
gufi entity:automations 4589 --edit
|
|
395
|
+
|
|
396
|
+
# View execution history
|
|
397
|
+
gufi automations:executions -c 146 --limit 50
|
|
398
|
+
|
|
399
|
+
# Filter by script
|
|
400
|
+
gufi automations:executions -c 146 --script send_email
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### API Endpoints
|
|
404
|
+
|
|
405
|
+
```http
|
|
406
|
+
# List scripts
|
|
407
|
+
GET /automations/scripts?company_id=146
|
|
408
|
+
|
|
409
|
+
# Create/update script
|
|
410
|
+
POST /automations/scripts
|
|
411
|
+
{ "company_id": 146, "name": "my_script", "code": "..." }
|
|
412
|
+
|
|
413
|
+
# Get entity automations
|
|
414
|
+
GET /entities/:entityId/automations
|
|
415
|
+
|
|
416
|
+
# Update entity automations
|
|
417
|
+
PUT /entities/:entityId/automations
|
|
418
|
+
[{ "trigger": "insert", "function_name": "my_script" }]
|
|
419
|
+
|
|
420
|
+
# Run click automation
|
|
421
|
+
POST /automations/click
|
|
422
|
+
{ "company_id": 146, "table_id": 4589, "function_name": "send_email", "input": {...} }
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Environment Variables
|
|
426
|
+
|
|
427
|
+
Store secrets and configuration:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
# Set variable
|
|
431
|
+
gufi env:set SMTP_HOST smtp.gmail.com
|
|
432
|
+
gufi env:set API_KEY sk_live_xxxxx
|
|
433
|
+
|
|
434
|
+
# Access in automation
|
|
435
|
+
const { env } = gufi.context;
|
|
436
|
+
const apiKey = env.API_KEY;
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Error Handling
|
|
440
|
+
|
|
441
|
+
### Best Practices
|
|
442
|
+
|
|
443
|
+
```javascript
|
|
444
|
+
async function robust_automation(gufi) {
|
|
445
|
+
try {
|
|
446
|
+
// Main logic
|
|
447
|
+
const result = await gufi.query("...");
|
|
448
|
+
|
|
449
|
+
if (!result.length) {
|
|
450
|
+
gufi.logger.warn("No data found, skipping");
|
|
451
|
+
return { success: true, skipped: true };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Continue processing...
|
|
455
|
+
|
|
456
|
+
} catch (error) {
|
|
457
|
+
gufi.logger.error("Automation failed", {
|
|
458
|
+
error: error.message,
|
|
459
|
+
stack: error.stack
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Re-throw to mark execution as failed
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Retry Configuration
|
|
469
|
+
|
|
470
|
+
Failed automations retry automatically:
|
|
471
|
+
|
|
472
|
+
| Attempt | Delay |
|
|
473
|
+
|---|---|
|
|
474
|
+
| 1 | Immediate |
|
|
475
|
+
| 2 | 1 minute |
|
|
476
|
+
| 3 | 5 minutes |
|
|
477
|
+
| 4 | 30 minutes |
|
|
478
|
+
| 5 | Failed permanently |
|
|
479
|
+
|
|
480
|
+
## Security
|
|
481
|
+
|
|
482
|
+
### Sandboxed Execution
|
|
483
|
+
|
|
484
|
+
Automations run in isolated-vm:
|
|
485
|
+
|
|
486
|
+
- No filesystem access
|
|
487
|
+
- No require/import
|
|
488
|
+
- Limited memory (128MB)
|
|
489
|
+
- Timeout (30 seconds)
|
|
490
|
+
|
|
491
|
+
### What's Available
|
|
492
|
+
|
|
493
|
+
| Available | Not Available |
|
|
494
|
+
|---|---|
|
|
495
|
+
| api.query | require() |
|
|
496
|
+
| api.email | import |
|
|
497
|
+
| api.http | fs, path |
|
|
498
|
+
| logger | process |
|
|
499
|
+
| context | child_process |
|
|
500
|
+
| JSON, Date, Math | eval |
|
|
501
|
+
|
|
502
|
+
### Data Access
|
|
503
|
+
|
|
504
|
+
Automations can only access:
|
|
505
|
+
|
|
506
|
+
- Data within the same company
|
|
507
|
+
- Tables the trigger is attached to
|
|
508
|
+
- Environment variables set for that company
|